Go client for the Write.as API https://developers.write.as

post.go 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. package writeas
  2. import (
  3. "fmt"
  4. "net/http"
  5. "time"
  6. )
  7. type (
  8. // Post represents a published Write.as post, whether anonymous, owned by a
  9. // user, or part of a collection.
  10. Post struct {
  11. ID string `json:"id"`
  12. Slug string `json:"slug"`
  13. Token string `json:"token"`
  14. Font string `json:"appearance"`
  15. Language *string `json:"language"`
  16. RTL *bool `json:"rtl"`
  17. Listed bool `json:"listed"`
  18. Created time.Time `json:"created"`
  19. Updated time.Time `json:"updated"`
  20. Title string `json:"title"`
  21. Content string `json:"body"`
  22. Views int64 `json:"views"`
  23. Tags []string `json:"tags"`
  24. Images []string `json:"images"`
  25. OwnerName string `json:"owner,omitempty"`
  26. Collection *Collection `json:"collection,omitempty"`
  27. }
  28. // OwnedPostParams are, together, fields only the original post author knows.
  29. OwnedPostParams struct {
  30. ID string `json:"id"`
  31. Token string `json:"token,omitempty"`
  32. }
  33. // PostParams holds values for creating or updating a post.
  34. PostParams struct {
  35. // Parameters only for updating
  36. ID string `json:"-"`
  37. Token string `json:"token,omitempty"`
  38. // Parameters for creating or updating
  39. Slug string `json:"slug"`
  40. Created *time.Time `json:"created,omitempty"`
  41. Updated *time.Time `json:"updated,omitempty"`
  42. Title string `json:"title,omitempty"`
  43. Content string `json:"body,omitempty"`
  44. Font string `json:"font,omitempty"`
  45. IsRTL *bool `json:"rtl,omitempty"`
  46. Language *string `json:"lang,omitempty"`
  47. // Parameters only for creating
  48. Crosspost []map[string]string `json:"crosspost,omitempty"`
  49. // Parameters for collection posts
  50. Collection string `json:"-"`
  51. }
  52. // PinnedPostParams holds values for pinning a post
  53. PinnedPostParams struct {
  54. ID string `json:"id"`
  55. Position int `json:"position"`
  56. }
  57. // BatchPostResult contains the post-specific result as part of a larger
  58. // batch operation.
  59. BatchPostResult struct {
  60. ID string `json:"id,omitempty"`
  61. Code int `json:"code,omitempty"`
  62. ErrorMessage string `json:"error_msg,omitempty"`
  63. }
  64. // ClaimPostResult contains the post-specific result for a request to
  65. // associate a post to an account.
  66. ClaimPostResult struct {
  67. ID string `json:"id,omitempty"`
  68. Code int `json:"code,omitempty"`
  69. ErrorMessage string `json:"error_msg,omitempty"`
  70. Post *Post `json:"post,omitempty"`
  71. }
  72. )
  73. // GetPost retrieves a published post, returning the Post and any error (in
  74. // user-friendly form) that occurs. See
  75. // https://developers.write.as/docs/api/#retrieve-a-post.
  76. func (c *Client) GetPost(id string) (*Post, error) {
  77. p := &Post{}
  78. env, err := c.get(fmt.Sprintf("/posts/%s", id), p)
  79. if err != nil {
  80. return nil, err
  81. }
  82. var ok bool
  83. if p, ok = env.Data.(*Post); !ok {
  84. return nil, fmt.Errorf("Wrong data returned from API.")
  85. }
  86. status := env.Code
  87. if status == http.StatusOK {
  88. return p, nil
  89. } else if status == http.StatusNotFound {
  90. return nil, fmt.Errorf("Post not found.")
  91. } else if status == http.StatusGone {
  92. return nil, fmt.Errorf("Post unpublished.")
  93. }
  94. return nil, fmt.Errorf("Problem getting post: %d. %s\n", status, env.ErrorMessage)
  95. }
  96. // CreatePost publishes a new post, returning a user-friendly error if one comes
  97. // up. See https://developers.write.as/docs/api/#publish-a-post.
  98. func (c *Client) CreatePost(sp *PostParams) (*Post, error) {
  99. p := &Post{}
  100. endPre := ""
  101. if sp.Collection != "" {
  102. endPre = "/collections/" + sp.Collection
  103. }
  104. env, err := c.post(endPre+"/posts", sp, p)
  105. if err != nil {
  106. return nil, err
  107. }
  108. var ok bool
  109. if p, ok = env.Data.(*Post); !ok {
  110. return nil, fmt.Errorf("Wrong data returned from API.")
  111. }
  112. status := env.Code
  113. if status != http.StatusCreated {
  114. if status == http.StatusBadRequest {
  115. return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
  116. }
  117. return nil, fmt.Errorf("Problem creating post: %d. %s\n", status, env.ErrorMessage)
  118. }
  119. return p, nil
  120. }
  121. // UpdatePost updates a published post with the given PostParams. See
  122. // https://developers.write.as/docs/api/#update-a-post.
  123. func (c *Client) UpdatePost(id, token string, sp *PostParams) (*Post, error) {
  124. return c.updatePost("", id, token, sp)
  125. }
  126. func (c *Client) updatePost(collection, identifier, token string, sp *PostParams) (*Post, error) {
  127. p := &Post{}
  128. endpoint := "/posts/" + identifier
  129. /*
  130. if collection != "" {
  131. endpoint = "/collections/" + collection + endpoint
  132. } else {
  133. sp.Token = token
  134. }
  135. */
  136. sp.Token = token
  137. env, err := c.put(endpoint, sp, p)
  138. if err != nil {
  139. return nil, err
  140. }
  141. var ok bool
  142. if p, ok = env.Data.(*Post); !ok {
  143. return nil, fmt.Errorf("Wrong data returned from API.")
  144. }
  145. status := env.Code
  146. if status != http.StatusOK {
  147. if c.isNotLoggedIn(status) {
  148. return nil, fmt.Errorf("Not authenticated.")
  149. } else if status == http.StatusBadRequest {
  150. return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
  151. }
  152. return nil, fmt.Errorf("Problem updating post: %d. %s\n", status, env.ErrorMessage)
  153. }
  154. return p, nil
  155. }
  156. // DeletePost permanently deletes a published post. See
  157. // https://developers.write.as/docs/api/#delete-a-post.
  158. func (c *Client) DeletePost(id, token string) error {
  159. return c.deletePost("", id, token)
  160. }
  161. func (c *Client) deletePost(collection, identifier, token string) error {
  162. p := map[string]string{}
  163. endpoint := "/posts/" + identifier
  164. /*
  165. if collection != "" {
  166. endpoint = "/collections/" + collection + endpoint
  167. } else {
  168. p["token"] = token
  169. }
  170. */
  171. p["token"] = token
  172. env, err := c.delete(endpoint, p)
  173. if err != nil {
  174. return err
  175. }
  176. status := env.Code
  177. if status == http.StatusNoContent {
  178. return nil
  179. } else if c.isNotLoggedIn(status) {
  180. return fmt.Errorf("Not authenticated.")
  181. } else if status == http.StatusBadRequest {
  182. return fmt.Errorf("Bad request: %s", env.ErrorMessage)
  183. }
  184. return fmt.Errorf("Problem deleting post: %d. %s\n", status, env.ErrorMessage)
  185. }
  186. // ClaimPosts associates anonymous posts with a user / account.
  187. // https://developers.write.as/docs/api/#claim-posts.
  188. func (c *Client) ClaimPosts(sp *[]OwnedPostParams) (*[]ClaimPostResult, error) {
  189. p := &[]ClaimPostResult{}
  190. env, err := c.post("/posts/claim", sp, p)
  191. if err != nil {
  192. return nil, err
  193. }
  194. var ok bool
  195. if p, ok = env.Data.(*[]ClaimPostResult); !ok {
  196. return nil, fmt.Errorf("Wrong data returned from API.")
  197. }
  198. status := env.Code
  199. if status == http.StatusOK {
  200. return p, nil
  201. } else if c.isNotLoggedIn(status) {
  202. return nil, fmt.Errorf("Not authenticated.")
  203. } else if status == http.StatusBadRequest {
  204. return nil, fmt.Errorf("Bad request: %s", env.ErrorMessage)
  205. } else {
  206. return nil, fmt.Errorf("Problem claiming post: %d. %s\n", status, env.ErrorMessage)
  207. }
  208. // TODO: does this also happen with moving posts?
  209. }
  210. // GetUserPosts retrieves the authenticated user's posts.
  211. // See https://developers.write.as/docs/api/#retrieve-user-39-s-posts
  212. func (c *Client) GetUserPosts() (*[]Post, error) {
  213. p := &[]Post{}
  214. env, err := c.get("/me/posts", p)
  215. if err != nil {
  216. return nil, err
  217. }
  218. var ok bool
  219. if p, ok = env.Data.(*[]Post); !ok {
  220. return nil, fmt.Errorf("Wrong data returned from API.")
  221. }
  222. status := env.Code
  223. if status != http.StatusOK {
  224. if c.isNotLoggedIn(status) {
  225. return nil, fmt.Errorf("Not authenticated.")
  226. }
  227. return nil, fmt.Errorf("Problem getting user posts: %d. %s\n", status, env.ErrorMessage)
  228. }
  229. return p, nil
  230. }
  231. // PinPost pins a post in the given collection.
  232. // See https://developers.write.as/docs/api/#pin-a-post-to-a-collection
  233. func (c *Client) PinPost(alias string, pp *PinnedPostParams) error {
  234. res := &[]BatchPostResult{}
  235. env, err := c.post(fmt.Sprintf("/collections/%s/pin", alias), []*PinnedPostParams{pp}, res)
  236. if err != nil {
  237. return err
  238. }
  239. var ok bool
  240. if res, ok = env.Data.(*[]BatchPostResult); !ok {
  241. return fmt.Errorf("Wrong data returned from API.")
  242. }
  243. // Check for basic request errors on top level response
  244. status := env.Code
  245. if status != http.StatusOK {
  246. if c.isNotLoggedIn(status) {
  247. return fmt.Errorf("Not authenticated.")
  248. }
  249. return fmt.Errorf("Problem pinning post: %d. %s\n", status, env.ErrorMessage)
  250. }
  251. // Check the individual post result
  252. if len(*res) == 0 || len(*res) > 1 {
  253. return fmt.Errorf("Wrong data returned from API.")
  254. }
  255. if (*res)[0].Code != http.StatusOK {
  256. return fmt.Errorf("Problem pinning post: %d", (*res)[0].Code)
  257. // TODO: return ErrorMessage (right now it'll be empty)
  258. // return fmt.Errorf("Problem pinning post: %s", res[0].ErrorMessage)
  259. }
  260. return nil
  261. }
  262. // UnpinPost unpins a post from the given collection.
  263. // See https://developers.write.as/docs/api/#unpin-a-post-from-a-collection
  264. func (c *Client) UnpinPost(alias string, pp *PinnedPostParams) error {
  265. res := &[]BatchPostResult{}
  266. env, err := c.post(fmt.Sprintf("/collections/%s/unpin", alias), []*PinnedPostParams{pp}, res)
  267. if err != nil {
  268. return err
  269. }
  270. var ok bool
  271. if res, ok = env.Data.(*[]BatchPostResult); !ok {
  272. return fmt.Errorf("Wrong data returned from API.")
  273. }
  274. // Check for basic request errors on top level response
  275. status := env.Code
  276. if status != http.StatusOK {
  277. if c.isNotLoggedIn(status) {
  278. return fmt.Errorf("Not authenticated.")
  279. }
  280. return fmt.Errorf("Problem unpinning post: %d. %s\n", status, env.ErrorMessage)
  281. }
  282. // Check the individual post result
  283. if len(*res) == 0 || len(*res) > 1 {
  284. return fmt.Errorf("Wrong data returned from API.")
  285. }
  286. if (*res)[0].Code != http.StatusOK {
  287. return fmt.Errorf("Problem unpinning post: %d", (*res)[0].Code)
  288. // TODO: return ErrorMessage (right now it'll be empty)
  289. // return fmt.Errorf("Problem unpinning post: %s", res[0].ErrorMessage)
  290. }
  291. return nil
  292. }