From c62a48b185368f98ea512cc4593e1588df2e39fa Mon Sep 17 00:00:00 2001 From: "Derrick J. Wippler" Date: Fri, 11 Jan 2019 18:06:20 -0600 Subject: [PATCH] Added support for Templates API --- cmd/mailgun/main.go | 1 + cmd/mailgun/tag.go | 5 - cmd/mailgun/template.go | 66 +++++++++++ mailgun.go | 13 +++ mock_routes.go | 10 +- routes.go | 2 +- routes_test.go | 12 +- tags.go | 10 -- template.go | 237 ++++++++++++++++++++++++++++++++++++++ template_test.go | 92 +++++++++++++++ template_versions.go | 207 +++++++++++++++++++++++++++++++++ template_versions_test.go | 94 +++++++++++++++ 12 files changed, 722 insertions(+), 27 deletions(-) create mode 100644 cmd/mailgun/template.go create mode 100644 template.go create mode 100644 template_test.go create mode 100644 template_versions.go create mode 100644 template_versions_test.go diff --git a/cmd/mailgun/main.go b/cmd/mailgun/main.go index a7f7d4f6..8f7325b5 100644 --- a/cmd/mailgun/main.go +++ b/cmd/mailgun/main.go @@ -36,6 +36,7 @@ func main() { parser.AddCommand("tag", Tag) parser.AddCommand("events", ListEvents) parser.AddCommand("mailing-lists", MailingLists) + parser.AddCommand("templates", Templates) // Parser and set global options opts := parser.ParseOrExit(nil) diff --git a/cmd/mailgun/tag.go b/cmd/mailgun/tag.go index c00fbba7..3fc8f1e8 100644 --- a/cmd/mailgun/tag.go +++ b/cmd/mailgun/tag.go @@ -55,9 +55,6 @@ func ListTag(parser *args.ArgParser, data interface{}) (int, error) { parser.SetDesc(desc) parser.AddOption("--prefix").Alias("-p").Help("list only tags with the given prefix") parser.AddOption("--limit").Alias("-l").IsInt().Help("Limit the result set") - parser.AddOption("--tag").Alias("-t").Help("The tag that marks piviot point for the --page parameter") - parser.AddOption("--page").Alias("-pg"). - Help("The page direction based off the tag parameter; valid choices are (first, last, next, prev)") opts := parser.ParseSimple(nil) if opts == nil { @@ -71,8 +68,6 @@ func ListTag(parser *args.ArgParser, data interface{}) (int, error) { it := mg.ListTags(&mailgun.ListTagOptions{ Limit: limit, Prefix: opts.String("prefix"), - Page: opts.String("page"), - Tag: opts.String("tag"), }) var ctx = context.Background() diff --git a/cmd/mailgun/template.go b/cmd/mailgun/template.go new file mode 100644 index 00000000..cb158680 --- /dev/null +++ b/cmd/mailgun/template.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/mailgun/mailgun-go/v3" + "github.com/thrawn01/args" +) + +func Templates(parser *args.ArgParser, data interface{}) (int, error) { + mg := data.(mailgun.Mailgun) + + desc := args.Dedent(`Manage templates via the mailgun HTTP API + + Examples: + list all available templates + $ mailgun templates list`) + + parser.SetDesc(desc) + + // Commands + parser.AddCommand("list", ListTemplates) + + // Run the command chosen by our user + return parser.ParseAndRun(nil, mg) +} + +func ListTemplates(parser *args.ArgParser, data interface{}) (int, error) { + mg := data.(mailgun.Mailgun) + + desc := args.Dedent(`list templates via the mailgun HTTP API + + Examples: + list all available tags + $ mailgun templates list`) + + parser.SetDesc(desc) + parser.AddOption("--limit").Alias("-l").IsInt().Help("Limit the page size") + + opts := parser.ParseSimple(nil) + if opts == nil { + return 1, nil + } + + limit := opts.Int("limit") + + // Create the event iterator + it := mg.ListTemplates(&mailgun.ListOptions{ + Limit: limit, + }) + + var page []mailgun.Template + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + for it.Next(ctx, &page) { + for _, event := range page { + spew.Printf("%+v\n", event) + } + } + cancel() + if it.Err() != nil { + return 1, it.Err() + } + return 0, nil +} diff --git a/mailgun.go b/mailgun.go index c38cd524..bcc9ba31 100644 --- a/mailgun.go +++ b/mailgun.go @@ -126,6 +126,7 @@ const ( webhooksEndpoint = "webhooks" listsEndpoint = "lists" basicAuthUser = "api" + templatesEndpoint = "templates" ) // Mailgun defines the supported subset of the Mailgun API. @@ -229,6 +230,18 @@ type Mailgun interface { CreateExport(ctx context.Context, url string) error GetTagLimits(ctx context.Context, domain string) (TagLimits, error) + + CreateTemplate(ctx context.Context, template *Template) error + GetTemplate(ctx context.Context, id string) (Template, error) + UpdateTemplate(ctx context.Context, template *Template) error + DeleteTemplate(ctx context.Context, id string) error + ListTemplates(opts *ListOptions) *TemplatesIterator + + AddTemplateVersion(ctx context.Context, templateId string, version *TemplateVersion) error + GetTemplateVersion(ctx context.Context, templateId, versionId string) (TemplateVersion, error) + UpdateTemplateVersion(ctx context.Context, templateId string, version *TemplateVersion) error + DeleteTemplateVersion(ctx context.Context, templateId, versionId string) error + ListTemplateVersions(templateId string, opts *ListOptions) *TemplateVersionsIterator } // MailgunImpl bundles data needed by a large number of methods in order to interact with the Mailgun API. diff --git a/mock_routes.go b/mock_routes.go index 0b699f35..4f2fab9e 100644 --- a/mock_routes.go +++ b/mock_routes.go @@ -20,7 +20,7 @@ func (ms *MockServer) addRoutes(r chi.Router) { for i := 0; i < 10; i++ { ms.routeList = append(ms.routeList, Route{ - ID: randomString(10, "ID-"), + Id: randomString(10, "ID-"), Priority: 0, Description: fmt.Sprintf("Sample Route %d", i), Actions: []string{ @@ -65,7 +65,7 @@ func (ms *MockServer) listRoutes(w http.ResponseWriter, r *http.Request) { func (ms *MockServer) getRoute(w http.ResponseWriter, r *http.Request) { for _, item := range ms.routeList { - if item.ID == chi.URLParam(r, "id") { + if item.Id == chi.URLParam(r, "id") { toJSON(w, routeResponse{Route: item}) return } @@ -83,7 +83,7 @@ func (ms *MockServer) createRoute(w http.ResponseWriter, r *http.Request) { ms.routeList = append(ms.routeList, Route{ CreatedAt: RFC2822Time(time.Now().UTC()), - ID: randomString(10, "ID-"), + Id: randomString(10, "ID-"), Priority: stringToInt(r.FormValue("priority")), Description: r.FormValue("description"), Expression: r.FormValue("expression"), @@ -97,7 +97,7 @@ func (ms *MockServer) createRoute(w http.ResponseWriter, r *http.Request) { func (ms *MockServer) updateRoute(w http.ResponseWriter, r *http.Request) { for i, item := range ms.routeList { - if item.ID == chi.URLParam(r, "id") { + if item.Id == chi.URLParam(r, "id") { if r.FormValue("action") != "" { ms.routeList[i].Actions = r.Form["action"] @@ -122,7 +122,7 @@ func (ms *MockServer) updateRoute(w http.ResponseWriter, r *http.Request) { func (ms *MockServer) deleteRoute(w http.ResponseWriter, r *http.Request) { result := ms.routeList[:0] for _, item := range ms.routeList { - if item.ID == chi.URLParam(r, "id") { + if item.Id == chi.URLParam(r, "id") { continue } result = append(result, item) diff --git a/routes.go b/routes.go index 6fc108a0..668bd2ae 100644 --- a/routes.go +++ b/routes.go @@ -25,7 +25,7 @@ type Route struct { // The CreatedAt field provides a time-stamp for when the route came into existence. CreatedAt RFC2822Time `json:"created_at,omitempty"` // ID field provides a unique identifier for this route. - ID string `json:"id,omitempty"` + Id string `json:"id,omitempty"` } type routesListResponse struct { diff --git a/routes_test.go b/routes_test.go index e2793ed2..c2a66ef5 100644 --- a/routes_test.go +++ b/routes_test.go @@ -33,22 +33,22 @@ func TestRouteCRUD(t *testing.T) { }, }) ensure.Nil(t, err) - ensure.True(t, newRoute.ID != "") + ensure.True(t, newRoute.Id != "") defer func() { - ensure.Nil(t, mg.DeleteRoute(ctx, newRoute.ID)) - _, err = mg.GetRoute(ctx, newRoute.ID) + ensure.Nil(t, mg.DeleteRoute(ctx, newRoute.Id)) + _, err = mg.GetRoute(ctx, newRoute.Id) ensure.NotNil(t, err) }() newCount := countRoutes() ensure.False(t, newCount <= routeCount) - theRoute, err := mg.GetRoute(ctx, newRoute.ID) + theRoute, err := mg.GetRoute(ctx, newRoute.Id) ensure.Nil(t, err) ensure.DeepEqual(t, newRoute, theRoute) - changedRoute, err := mg.UpdateRoute(ctx, newRoute.ID, mailgun.Route{ + changedRoute, err := mg.UpdateRoute(ctx, newRoute.Id, mailgun.Route{ Priority: 2, }) ensure.Nil(t, err) @@ -90,7 +90,7 @@ func TestRoutesIterator(t *testing.T) { ensure.True(t, it.Previous(ctx, &previousPage)) ensure.True(t, len(previousPage) != 0) - ensure.DeepEqual(t, previousPage[0].ID, firstPage[0].ID) + ensure.DeepEqual(t, previousPage[0].Id, firstPage[0].Id) // First() ensure.True(t, it.First(ctx, &firstPage)) diff --git a/tags.go b/tags.go index 42cc67b0..04773cce 100644 --- a/tags.go +++ b/tags.go @@ -24,10 +24,6 @@ type ListTagOptions struct { Limit int // Return only the tags starting with the given prefix Prefix string - // The page direction based off the 'tag' parameter; valid choices are (first, last, next, prev) - Page string - // The tag that marks piviot point for the 'page' parameter - Tag string } // DeleteTag removes all counters for a particular tag, including the tag itself. @@ -68,12 +64,6 @@ func (mg *MailgunImpl) ListTags(opts *ListTagOptions) *TagIterator { if opts.Prefix != "" { req.addParameter("prefix", opts.Prefix) } - if opts.Page != "" { - req.addParameter("page", opts.Page) - } - if opts.Tag != "" { - req.addParameter("tag", opts.Tag) - } } url, err := req.generateUrlWithParameters() diff --git a/template.go b/template.go new file mode 100644 index 00000000..2cac1bc6 --- /dev/null +++ b/template.go @@ -0,0 +1,237 @@ +package mailgun + +import ( + "context" + "errors" + "strconv" +) + +type TemplateEngine string + +const ( + TemplateEngineMustache = TemplateEngine("mustache") + TemplateEngineHandlebars = TemplateEngine("handlebars") + TemplateEngineGo = TemplateEngine("go") +) + +type Template struct { + Id string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + //CreatedAt RFC2822Time `json:"createdAt"` + CreatedAt string `json:"createdAt"` + Version TemplateVersion `json:"version,omitempty"` +} + +type templateResp struct { + Item Template `json:"item"` + Message string `json:"message"` +} + +type templateListResp struct { + Items []Template `json:"items"` + Paging Paging `json:"paging"` +} + +/*type TemplateOptions struct { + Name string + Description string + Template string + Engine TemplateEngine + Comment string + Active bool +}*/ + +func (mg *MailgunImpl) CreateTemplate(ctx context.Context, template *Template) error { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint)) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + + payload := newUrlEncodedPayload() + + if template.Name != "" { + payload.addValue("name", template.Name) + } + if template.Description != "" { + payload.addValue("description", template.Description) + } + + if template.Version.Engine != "" { + payload.addValue("engine", string(template.Version.Engine)) + } + if template.Version.Template != "" { + payload.addValue("template", template.Version.Template) + } + if template.Version.Comment != "" { + payload.addValue("comment", template.Version.Comment) + } + + var resp templateResp + if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil { + return err + } + *template = resp.Item + return nil +} + +func (mg *MailgunImpl) GetTemplate(ctx context.Context, id string) (Template, error) { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + id) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + r.addParameter("active", "yes") + + var resp templateResp + err := getResponseFromJSON(ctx, r, &resp) + if err != nil { + return Template{}, err + } + return resp.Item, nil +} + +func (mg *MailgunImpl) UpdateTemplate(ctx context.Context, template *Template) error { + if template.Id == "" { + return errors.New("UpdateTemplate() Template.Id cannot be empty") + } + + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + template.Id) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + p := newUrlEncodedPayload() + + if template.Name != "" { + p.addValue("name", template.Name) + } + if template.Description != "" { + p.addValue("description", template.Description) + } + + var resp templateResp + err := putResponseFromJSON(ctx, r, p, &resp) + if err != nil { + return err + } + *template = resp.Item + return nil +} + +func (mg *MailgunImpl) DeleteTemplate(ctx context.Context, id string) error { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + id) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + _, err := makeDeleteRequest(ctx, r) + return err +} + +type TemplatesIterator struct { + templateListResp + mg Mailgun + err error +} + +func (mg *MailgunImpl) ListTemplates(opts *ListOptions) *TemplatesIterator { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint)) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + if opts != nil { + if opts.Limit != 0 { + r.addParameter("limit", strconv.Itoa(opts.Limit)) + } + } + url, err := r.generateUrlWithParameters() + return &TemplatesIterator{ + mg: mg, + templateListResp: templateListResp{Paging: Paging{Next: url, First: url}}, + err: err, + } +} + +// If an error occurred during iteration `Err()` will return non nil +func (ti *TemplatesIterator) Err() error { + return ti.err +} + +// Retrieves the next page of items from the api. Returns false when there +// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve +// the error +func (ti *TemplatesIterator) Next(ctx context.Context, items *[]Template) bool { + if ti.err != nil { + return false + } + ti.err = ti.fetch(ctx, ti.Paging.Next) + if ti.err != nil { + return false + } + cpy := make([]Template, len(ti.Items)) + copy(cpy, ti.Items) + *items = cpy + if len(ti.Items) == 0 { + return false + } + return true +} + +// Retrieves the first page of items from the api. Returns false if there +// was an error. It also sets the iterator object to the first page. +// Use `.Err()` to retrieve the error. +func (ti *TemplatesIterator) First(ctx context.Context, items *[]Template) bool { + if ti.err != nil { + return false + } + ti.err = ti.fetch(ctx, ti.Paging.First) + if ti.err != nil { + return false + } + cpy := make([]Template, len(ti.Items)) + copy(cpy, ti.Items) + *items = cpy + return true +} + +// Retrieves the last page of items from the api. +// Calling Last() is invalid unless you first call First() or Next() +// Returns false if there was an error. It also sets the iterator object +// to the last page. Use `.Err()` to retrieve the error. +func (ti *TemplatesIterator) Last(ctx context.Context, items *[]Template) bool { + if ti.err != nil { + return false + } + ti.err = ti.fetch(ctx, ti.Paging.Last) + if ti.err != nil { + return false + } + cpy := make([]Template, len(ti.Items)) + copy(cpy, ti.Items) + *items = cpy + return true +} + +// Retrieves the previous page of items from the api. Returns false when there +// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve +// the error if any +func (ti *TemplatesIterator) Previous(ctx context.Context, items *[]Template) bool { + if ti.err != nil { + return false + } + if ti.Paging.Previous == "" { + return false + } + ti.err = ti.fetch(ctx, ti.Paging.Previous) + if ti.err != nil { + return false + } + cpy := make([]Template, len(ti.Items)) + copy(cpy, ti.Items) + *items = cpy + if len(ti.Items) == 0 { + return false + } + return true +} + +func (ti *TemplatesIterator) fetch(ctx context.Context, url string) error { + r := newHTTPRequest(url) + r.setClient(ti.mg.Client()) + r.setBasicAuth(basicAuthUser, ti.mg.APIKey()) + + return getResponseFromJSON(ctx, r, &ti.templateListResp) +} diff --git a/template_test.go b/template_test.go new file mode 100644 index 00000000..308f18a4 --- /dev/null +++ b/template_test.go @@ -0,0 +1,92 @@ +package mailgun_test + +import ( + "context" + "testing" + "time" + + "github.com/facebookgo/ensure" + "github.com/mailgun/mailgun-go/v3" + "github.com/pkg/errors" +) + +func TestTemplateCRUD(t *testing.T) { + if reason := mailgun.SkipNetworkTest(); reason != "" { + t.Skip(reason) + } + mailgun.Debug = true + + mg, err := mailgun.NewMailgunFromEnv() + ensure.Nil(t, err) + ctx := context.Background() + + findTemplate := func(id string) bool { + it := mg.ListTemplates(nil) + + var page []mailgun.Template + for it.Next(ctx, &page) { + for _, template := range page { + if template.Id == id { + return true + } + } + } + ensure.Nil(t, it.Err()) + return false + } + + const ( + Name = "Mailgun-Go TestTemplateCRUD" + Description = "Mailgun-Go Test Template Description" + UpdatedDesc = "Mailgun-Go Test Updated Description" + ) + + tmpl := mailgun.Template{ + Name: Name, + Description: Description, + } + + // Create a template + ensure.Nil(t, mg.CreateTemplate(ctx, &tmpl)) + ensure.True(t, tmpl.Id != "") + ensure.DeepEqual(t, tmpl.Description, Description) + ensure.DeepEqual(t, tmpl.Name, Name) + + // Wait the template to show up + ensure.Nil(t, waitForTemplate(mg, tmpl.Id)) + + // Ensure the template is in the list + ensure.True(t, findTemplate(tmpl.Id)) + + // Update the description + tmpl.Description = UpdatedDesc + ensure.Nil(t, mg.UpdateTemplate(ctx, &tmpl)) + + // Ensure update took + updated, err := mg.GetTemplate(ctx, tmpl.Id) + + ensure.DeepEqual(t, updated.Id, tmpl.Id) + ensure.DeepEqual(t, updated.Description, UpdatedDesc) + + // Delete the template + ensure.Nil(t, mg.DeleteTemplate(ctx, tmpl.Id)) +} + +func waitForTemplate(mg mailgun.Mailgun, id string) error { + ctx := context.Background() + var attempts int + for attempts <= 5 { + _, err := mg.GetTemplate(ctx, id) + if err != nil { + if mailgun.GetStatusFromErr(err) == 404 { + time.Sleep(time.Second * 2) + attempts += 1 + continue + } + return err + } + return nil + + } + return errors.Errorf("Waited to long for template '%s' to show up", id) +} diff --git a/template_versions.go b/template_versions.go new file mode 100644 index 00000000..02530f82 --- /dev/null +++ b/template_versions.go @@ -0,0 +1,207 @@ +package mailgun + +import ( + "context" + "strconv" +) + +type TemplateVersion struct { + Id string `json:"id"` + Template string `json:"template,omitempty"` + Engine TemplateEngine `json:"engine"` + CreatedAt string `json:"createdAt"` + //CreatedAt RFC2822Time `json:"createdAt"` + Comment string `json:"comment"` + Active bool `json:"active"` +} + +type templateVersionListResp struct { + Item struct { + Template + Versions []TemplateVersion `json:"versions,omitempty"` + } `json:"item"` + Paging Paging `json:"paging"` +} + +func (mg *MailgunImpl) AddTemplateVersion(ctx context.Context, templateId string, version *TemplateVersion) error { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateId + "/versions") + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + + payload := newUrlEncodedPayload() + payload.addValue("template", version.Template) + + if version.Engine != "" { + payload.addValue("engine", string(version.Engine)) + } + if version.Comment != "" { + payload.addValue("comment", version.Comment) + } + if version.Active { + payload.addValue("active", boolToString(version.Active)) + } + + var resp templateResp + if err := postResponseFromJSON(ctx, r, payload, &resp); err != nil { + return err + } + *version = resp.Item.Version + return nil +} + +func (mg *MailgunImpl) GetTemplateVersion(ctx context.Context, templateId, versionId string) (TemplateVersion, error) { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateId + "/versions/" + versionId) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + + var resp templateResp + err := getResponseFromJSON(ctx, r, &resp) + if err != nil { + return TemplateVersion{}, err + } + return resp.Item.Version, nil +} + +func (mg *MailgunImpl) UpdateTemplateVersion(ctx context.Context, templateId string, version *TemplateVersion) error { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateId + "/versions/" + version.Id) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + p := newUrlEncodedPayload() + + if version.Comment != "" { + p.addValue("comment", version.Comment) + } + if version.Active { + p.addValue("active", boolToString(version.Active)) + } + + var resp templateResp + err := putResponseFromJSON(ctx, r, p, &resp) + if err != nil { + return err + } + *version = resp.Item.Version + return nil +} + +func (mg *MailgunImpl) DeleteTemplateVersion(ctx context.Context, templateId, versionId string) error { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateId + "/versions/" + versionId) + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + _, err := makeDeleteRequest(ctx, r) + return err +} + +type TemplateVersionsIterator struct { + templateVersionListResp + mg Mailgun + err error +} + +func (mg *MailgunImpl) ListTemplateVersions(templateId string, opts *ListOptions) *TemplateVersionsIterator { + r := newHTTPRequest(generateApiUrl(mg, templatesEndpoint) + "/" + templateId + "/versions") + r.setClient(mg.Client()) + r.setBasicAuth(basicAuthUser, mg.APIKey()) + if opts != nil { + if opts.Limit != 0 { + r.addParameter("limit", strconv.Itoa(opts.Limit)) + } + } + url, err := r.generateUrlWithParameters() + return &TemplateVersionsIterator{ + mg: mg, + templateVersionListResp: templateVersionListResp{Paging: Paging{Next: url, First: url}}, + err: err, + } +} + +// If an error occurred during iteration `Err()` will return non nil +func (li *TemplateVersionsIterator) Err() error { + return li.err +} + +// Retrieves the next page of items from the api. Returns false when there +// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve +// the error +func (li *TemplateVersionsIterator) Next(ctx context.Context, items *[]TemplateVersion) bool { + if li.err != nil { + return false + } + li.err = li.fetch(ctx, li.Paging.Next) + if li.err != nil { + return false + } + cpy := make([]TemplateVersion, len(li.Item.Versions)) + copy(cpy, li.Item.Versions) + *items = cpy + if len(li.Item.Versions) == 0 { + return false + } + return true +} + +// Retrieves the first page of items from the api. Returns false if there +// was an error. It also sets the iterator object to the first page. +// Use `.Err()` to retrieve the error. +func (li *TemplateVersionsIterator) First(ctx context.Context, items *[]TemplateVersion) bool { + if li.err != nil { + return false + } + li.err = li.fetch(ctx, li.Paging.First) + if li.err != nil { + return false + } + cpy := make([]TemplateVersion, len(li.Item.Versions)) + copy(cpy, li.Item.Versions) + *items = cpy + return true +} + +// Retrieves the last page of items from the api. +// Calling Last() is invalid unless you first call First() or Next() +// Returns false if there was an error. It also sets the iterator object +// to the last page. Use `.Err()` to retrieve the error. +func (li *TemplateVersionsIterator) Last(ctx context.Context, items *[]TemplateVersion) bool { + if li.err != nil { + return false + } + li.err = li.fetch(ctx, li.Paging.Last) + if li.err != nil { + return false + } + cpy := make([]TemplateVersion, len(li.Item.Versions)) + copy(cpy, li.Item.Versions) + *items = cpy + return true +} + +// Retrieves the previous page of items from the api. Returns false when there +// no more pages to retrieve or if there was an error. Use `.Err()` to retrieve +// the error if any +func (li *TemplateVersionsIterator) Previous(ctx context.Context, items *[]TemplateVersion) bool { + if li.err != nil { + return false + } + if li.Paging.Previous == "" { + return false + } + li.err = li.fetch(ctx, li.Paging.Previous) + if li.err != nil { + return false + } + cpy := make([]TemplateVersion, len(li.Item.Versions)) + copy(cpy, li.Item.Versions) + *items = cpy + if len(li.Item.Versions) == 0 { + return false + } + return true +} + +func (li *TemplateVersionsIterator) fetch(ctx context.Context, url string) error { + r := newHTTPRequest(url) + r.setClient(li.mg.Client()) + r.setBasicAuth(basicAuthUser, li.mg.APIKey()) + + return getResponseFromJSON(ctx, r, &li.templateVersionListResp) +} diff --git a/template_versions_test.go b/template_versions_test.go new file mode 100644 index 00000000..07fddfd2 --- /dev/null +++ b/template_versions_test.go @@ -0,0 +1,94 @@ +package mailgun_test + +import ( + "context" + "testing" + + "github.com/facebookgo/ensure" + "github.com/mailgun/mailgun-go/v3" +) + +func TestTemplateVersionsCRUD(t *testing.T) { + if reason := mailgun.SkipNetworkTest(); reason != "" { + t.Skip(reason) + } + + mg, err := mailgun.NewMailgunFromEnv() + ensure.Nil(t, err) + ctx := context.Background() + + findVersion := func(templateId, versionId string) bool { + it := mg.ListTemplateVersions(templateId, nil) + + var page []mailgun.TemplateVersion + for it.Next(ctx, &page) { + for _, v := range page { + if v.Id == versionId { + return true + } + } + } + ensure.Nil(t, it.Err()) + return false + } + + const ( + Comment = "Mailgun-Go TestTemplateVersionsCRUD" + UpdatedComment = "Mailgun-Go Test Version Updated" + Template = "{{.Name}}" + ) + + tmpl := mailgun.Template{ + Name: "Mailgun-go TestTemplateVersionsCRUD", + } + + // Create a template + ensure.Nil(t, mg.CreateTemplate(ctx, &tmpl)) + + version := mailgun.TemplateVersion{ + Comment: Comment, + Template: Template, + Active: true, + Engine: mailgun.TemplateEngineGo, + } + + // Add a version version + ensure.Nil(t, mg.AddTemplateVersion(ctx, tmpl.Id, &version)) + ensure.True(t, version.Id != "") + ensure.DeepEqual(t, version.Comment, Comment) + ensure.DeepEqual(t, version.Engine, mailgun.TemplateEngineGo) + + // Ensure the version is in the list + ensure.True(t, findVersion(tmpl.Id, version.Id)) + + // Update the Comment + version.Comment = UpdatedComment + ensure.Nil(t, mg.UpdateTemplateVersion(ctx, tmpl.Id, &version)) + + // Ensure update took + updated, err := mg.GetTemplateVersion(ctx, tmpl.Id, version.Id) + + ensure.DeepEqual(t, version.Id, updated.Id) + ensure.DeepEqual(t, updated.Comment, UpdatedComment) + + // Add a new active Version + version2 := mailgun.TemplateVersion{ + Comment: Comment, + Template: Template, + Active: true, + Engine: mailgun.TemplateEngineGo, + } + ensure.Nil(t, mg.AddTemplateVersion(ctx, tmpl.Id, &version2)) + + // Ensure the version is in the list + ensure.True(t, findVersion(tmpl.Id, version2.Id)) + + // Delete the first version + ensure.Nil(t, mg.DeleteTemplateVersion(ctx, tmpl.Id, version.Id)) + + // Ensure version was deleted + ensure.False(t, findVersion(tmpl.Id, version.Id)) + + // Delete the template + ensure.Nil(t, mg.DeleteTemplate(ctx, tmpl.Id)) +}