Skip to content

Commit

Permalink
Implement forem_listing resource (#7)
Browse files Browse the repository at this point in the history
* add forem_listing resource schema

* add golint in pre-commit

* add read and create functionality to forem_listing resource

* Add test for create draft listing

Also, make changes based on golint suggestions

* add test for listing resource to publish

Tests now test the successful publish of a listing

* Improve SchemaDescriptionBuilder

Logs AtLeastOneOf, ConflictsWith, RequiredWith, MinItems and MaxItems

* add dev-client-go 1.1.3 dependency

* keep myself dry

TestAccListing_draft now reuses the getListingBodySchemaToPublish method
to create a ListingBodySchema

* upgrade to dev-client-go 1.2.0

* Add golangci-lint to pre-commit

* simpify tags section

* update listing now works like a charm

* add update listing tests
  • Loading branch information
karvounis authored Apr 13, 2022
1 parent 8d90e41 commit 892c5ff
Show file tree
Hide file tree
Showing 16 changed files with 579 additions and 44 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ vendor/
.vscode/

.env
ex/
20 changes: 19 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
linters:
disable-all: true
enable:
- asciicheck
- bodyclose
- contextcheck
- deadcode
- gofmt
- durationcheck
- errname
- gomnd
- gosimple
- govet
- ineffassign
- makezero
- misspell
- nakedret
- nilerr
- noctx
- nolintlint
- revive
- staticcheck
- structcheck
- typecheck
- unconvert
- unparam
- unused
- varcheck
- vet
- wastedassign
linters-settings:
revive:
ignore-generated-header: true
severity: warning
rules:
- name: atomic
- name: var-naming
disabled: true
3 changes: 1 addition & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,8 @@ repos:
rev: v0.5.0
hooks:
- id: go-fmt
- id: go-vet
- id: golangci-lint
- id: go-mod-tidy
- id: golangci-lint
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.64.0
hooks:
Expand Down
6 changes: 3 additions & 3 deletions docs/data-sources/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ data "forem_article" "example_username_slug" {

### Optional

- `id` (String) ID of the article.
- `slug` (String) Slug of the article.
- `username` (String) User or organization username.
- `id` (String) ID of the article. At least one of the following has to be added: `id, username, slug`.
- `slug` (String) Slug of the article. At least one of the following has to be added: `id, username, slug`. Required to be set with the following: `username`.
- `username` (String) User or organization username. At least one of the following has to be added: `id, username, slug`. Required to be set with the following: `slug`.

### Read-Only

Expand Down
4 changes: 2 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ provider "forem" {

### Optional

- `api_key` (String) API key to be able to communicate with the FOREM API. Can be specified with the `FOREM_API_KEY` environment variable.
- `host` (String) Host of the FOREM API. You can specify the `dev.to` or any other Forem installation. Can be specified with the `FOREM_HOST` environment variable. Default: `https://dev.to/api`.
- `api_key` (String) API key to be able to communicate with the FOREM API. Environment variable: `FOREM_API_KEY`.
- `host` (String) Host of the FOREM API. Environment variable: `FOREM_HOST`. Defaults to: `https://dev.to/api`.
4 changes: 2 additions & 2 deletions docs/resources/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ resource "forem_article" "example_full" {
- `cover_image` (String) URL of the cover image of the article.
- `description` (String) Article description.
- `organization_id` (Number) Only users belonging to an organization can assign the article to it.
- `published` (Boolean) Set to `true` to create a published article. Defaults to `false`.
- `published` (Boolean) Set to `true` to create a published article. Defaults to: `false`.
- `series` (String) Article series name. All articles belonging to the same series need to have the same name in this parameter.
- `tags` (List of String) List of tags related to the article.
- `tags` (List of String) List of tags related to the article. Maximum items: `4`.
### Read-Only
Expand Down
50 changes: 50 additions & 0 deletions docs/resources/listing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "forem_listing Resource - terraform-provider-forem"
subcategory: ""
description: |-
forem_listing resource creates and updates a particular listing. A listing is a classified ad that users create on Forem. They can be related to conference announcements, job offers, mentorships, upcoming events and more.
API Docs
https://developers.forem.com/api#operation/createListinghttps://developers.forem.com/api#operation/updateListing
---

# forem_listing (Resource)

`forem_listing` resource creates and updates a particular listing. A listing is a classified ad that users create on Forem. They can be related to conference announcements, job offers, mentorships, upcoming events and more.

## API Docs

- https://developers.forem.com/api#operation/createListing
- https://developers.forem.com/api#operation/updateListing



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `body_markdown` (String) The body of the listing in Markdown format.
- `category` (String) The category that the listing belongs to.
- `title` (String) Title of the listing.

### Optional

- `action` (String) Set it to `draft` to create an unpublished listing.
- `contact_via_connect` (Boolean) True if users are allowed to contact the listing's owner via DEV connect, false otherwise. Defaults to: `false`.
- `expires_at` (String) Date and time of expiration.
- `location` (String) Geographical area or city for the listing.
- `organization_id` (Number) The id of the organization the user is creating the listing for. Only users belonging to an organization can assign the listing to it.
- `tags` (List of String) List of tags related to the listing. Maximum items: `8`.

### Read-Only

- `created_at` (String) When the listing was created.
- `id` (String) ID of the listing.
- `organization` (Map of String) Organization object of the listing.
- `published` (Boolean) Whether the listing is published or not.
- `slug` (String) Slug of the listing.
- `updated_at` (String) When the listing was updated.
- `user` (Map of String) User object of the listing.


4 changes: 2 additions & 2 deletions forem/data_source_followed_tags.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
)

const (
FormatIntBase = 10
formatIntBase = 10
)

func dataSourceFollowedTags() *schema.Resource {
Expand Down Expand Up @@ -71,7 +71,7 @@ func dataSourceFollowedTagsRead(ctx context.Context, d *schema.ResourceData, met
}

d.Set("tags", ftags)
d.SetId(strconv.FormatInt(time.Now().Unix(), FormatIntBase))
d.SetId(strconv.FormatInt(time.Now().Unix(), formatIntBase))

return nil
}
31 changes: 25 additions & 6 deletions forem/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,37 @@ import (
)

const (
DEV_TO_BASE_URL = "https://dev.to/api"
devToBaseURL = "https://dev.to/api"

envForemAPIKey = "FOREM_API_KEY"
envForemHost = "FOREM_HOST"
)

func init() {
schema.DescriptionKind = schema.StringMarkdown
schema.SchemaDescriptionBuilder = func(s *schema.Schema) string {
desc := s.Description
if s.Default != nil {
desc += fmt.Sprintf(" Defaults to `%v`.", s.Default)
desc += fmt.Sprintf(" Defaults to: `%v`.", s.Default)
}
if s.Deprecated != "" {
desc += " " + s.Deprecated
}
if len(s.AtLeastOneOf) > 0 {
desc += fmt.Sprintf(" At least one of the following has to be added: `%s`.", strings.Join(s.AtLeastOneOf, ", "))
}
if len(s.ConflictsWith) > 0 {
desc += fmt.Sprintf(" Conflicts with the following: `%s`.", strings.Join(s.ConflictsWith, ", "))
}
if len(s.RequiredWith) > 0 {
desc += fmt.Sprintf(" Required to be set with the following: `%s`.", strings.Join(s.RequiredWith, ", "))
}
if s.MinItems > 0 {
desc += fmt.Sprintf(" Minimum items: `%d`.", s.MinItems)
}
if s.MaxItems > 0 {
desc += fmt.Sprintf(" Maximum items: `%d`.", s.MaxItems)
}
return strings.TrimSpace(desc)
}
}
Expand All @@ -34,20 +52,21 @@ func Provider() *schema.Provider {
ConfigureContextFunc: providerConfigure,
Schema: map[string]*schema.Schema{
"api_key": {
Description: "API key to be able to communicate with the FOREM API. Can be specified with the `FOREM_API_KEY` environment variable.",
Description: fmt.Sprintf("API key to be able to communicate with the FOREM API. Environment variable: `%s`.", envForemAPIKey),
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("FOREM_API_KEY", nil),
DefaultFunc: schema.EnvDefaultFunc(envForemAPIKey, nil),
},
"host": {
Description: "Host of the FOREM API. You can specify the `dev.to` or any other Forem installation. Can be specified with the `FOREM_HOST` environment variable. Default: `https://dev.to/api`.",
Description: fmt.Sprintf("Host of the FOREM API. Environment variable: `%s`. Defaults to: `%s`.", envForemHost, devToBaseURL),
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc("FOREM_HOST", DEV_TO_BASE_URL),
DefaultFunc: schema.EnvDefaultFunc(envForemHost, devToBaseURL),
},
},
ResourcesMap: map[string]*schema.Resource{
"forem_article": resourceArticle(),
"forem_listing": resourceListing(),
},
DataSourcesMap: map[string]*schema.Resource{
"forem_user": dataSourceUser(),
Expand Down
22 changes: 11 additions & 11 deletions forem/resource_article.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import (
)

const (
MaxArticleTags = 4
ReadArticlesPerPage = 25
maxArticleTags = 4
readArticlesPerPage = 25
)

func resourceArticle() *schema.Resource {
Expand Down Expand Up @@ -55,7 +55,7 @@ func resourceArticle() *schema.Resource {
Description: "List of tags related to the article.",
Type: schema.TypeList,
Optional: true,
MaxItems: MaxArticleTags,
MaxItems: maxArticleTags,
Elem: &schema.Schema{Type: schema.TypeString},
},
"published": {
Expand Down Expand Up @@ -166,6 +166,7 @@ func resourceArticle() *schema.Resource {
}
}

// TODO: Waiting for API to allow deletion of an article
func resourceArticleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
return nil
}
Expand Down Expand Up @@ -243,12 +244,11 @@ func resourceArticleUpdate(ctx context.Context, d *schema.ResourceData, meta int
ab.Article.CanonicalURL = v.(string)
}
if v, ok := d.GetOk("tags"); ok {
tags := v.([]interface{})
tagsList := []string{}
for _, t := range tags {
tagsList = append(tagsList, t.(string))
tags := []string{}
for _, t := range v.([]interface{}) {
tags = append(tags, t.(string))
}
ab.Article.Tags = tagsList
ab.Article.Tags = tags
}
if v, ok := d.GetOk("organization_id"); ok {
ab.Article.OrganizationID = v.(int32)
Expand All @@ -266,10 +266,10 @@ func resourceArticleRead(ctx context.Context, d *schema.ResourceData, meta inter
client := meta.(*dev.Client)

id := d.Get("id").(string)
tflog.Debug(ctx, fmt.Sprintf("Getting article: %s", id))
tflog.Debug(ctx, fmt.Sprintf("Getting article with ID: %s", id))

page := int32(1)
perPage := int32(ReadArticlesPerPage)
perPage := int32(readArticlesPerPage)
missing := true
var article dev.Article

Expand All @@ -285,7 +285,7 @@ func resourceArticleRead(ctx context.Context, d *schema.ResourceData, meta inter

for _, v := range articleResp {
if strconv.Itoa(int(v.ID)) == id {
tflog.Debug(ctx, fmt.Sprintf("Found article: %s", id))
tflog.Debug(ctx, fmt.Sprintf("Found article with ID: %s", id))
missing = false
article = v
break
Expand Down
24 changes: 12 additions & 12 deletions forem/resource_article_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ func TestAccArticle_basic(t *testing.T) {

resourceName := "forem_article.test"
title := gofakeit.SentenceSimple()
body_markdown := gofakeit.HipsterParagraph(2, 5, 10, "\n")
bodyMarkdown := gofakeit.HipsterParagraph(2, 5, 10, "\n")

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccArticleBasicConfig(title, body_markdown),
Config: testAccArticleBasicConfig(title, bodyMarkdown),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttrSet(resourceName, "id"),
resource.TestCheckResourceAttr(resourceName, "title", title),
resource.TestCheckResourceAttr(resourceName, "body_markdown", body_markdown),
resource.TestCheckResourceAttr(resourceName, "body_markdown", bodyMarkdown),
resource.TestCheckResourceAttr(resourceName, "published", "false"),
resource.TestCheckResourceAttr(resourceName, "tags.#", "0"),
resource.TestCheckResourceAttrSet(resourceName, "slug"),
Expand All @@ -46,15 +46,15 @@ func TestAccArticle_full(t *testing.T) {

title := gofakeit.HipsterSentence(5)
published := gofakeit.Bool()
canonical_url := gofakeit.URL()
canonicalURL := gofakeit.URL()
tags := []string{gofakeit.Word(), gofakeit.Word(), gofakeit.Word()}
series := gofakeit.LoremIpsumSentence(3)
article := &dev.Article{
Title: title,
BodyMarkdown: gofakeit.HipsterParagraph(2, 5, 10, "\n"),
Published: published,
Description: gofakeit.LoremIpsumSentence(5),
CanonicalURL: canonical_url,
CanonicalURL: canonicalURL,
CoverImage: gofakeit.URL(),
TagList: tags,
}
Expand All @@ -64,7 +64,7 @@ func TestAccArticle_full(t *testing.T) {
BodyMarkdown: gofakeit.HipsterParagraph(2, 5, 10, "\n"),
Published: published,
Description: gofakeit.LoremIpsumSentence(5),
CanonicalURL: canonical_url,
CanonicalURL: canonicalURL,
CoverImage: gofakeit.URL(),
TagList: append(tags, gofakeit.Word()),
}
Expand Down Expand Up @@ -135,19 +135,19 @@ func TestAccArticle_tooManyTags(t *testing.T) {
})
}

func testAccArticleBasicConfig(title, body_markdown string) string {
func testAccArticleBasicConfig(title, bodyMarkdown string) string {
return fmt.Sprintf(`
resource "forem_article" "test" {
title = %[1]q
body_markdown = %[2]q
}`, title, body_markdown)
title = %q
body_markdown = %q
}`, title, bodyMarkdown)
}

func testAccArticleFullConfig(article *dev.Article, series string) string {
return fmt.Sprintf(`
resource "forem_article" "test" {
title = %[1]q
body_markdown = %[2]q
title = %q
body_markdown = %q
published = %v
description = %q
canonical_url = %q
Expand Down
Loading

0 comments on commit 892c5ff

Please sign in to comment.