-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathposts_delete.go
More file actions
134 lines (112 loc) · 4.75 KB
/
posts_delete.go
File metadata and controls
134 lines (112 loc) · 4.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package threads
import (
"context"
"encoding/json"
"fmt"
)
// DeletePost deletes a specific post by ID with proper validation and confirmation.
// Returns the deleted post ID as reported by the API. If the API response cannot
// be parsed, the returned ID will be an empty string; a non-nil error is only
// returned when the HTTP request itself fails or the server returns a non-200 status.
// Note: The API enforces a limit of 100 deletes per 24-hour window. Check
// PublishingLimits.DeleteQuotaUsage via GetPublishingLimits to monitor usage.
// The threads_delete permission scope is required for this endpoint.
func (c *Client) DeletePost(ctx context.Context, postID PostID) (string, error) {
if !postID.Valid() {
return "", NewValidationError(400, ErrEmptyPostID, "Cannot delete post without ID", "post_id")
}
// Ensure we have a valid token
if err := c.EnsureValidToken(ctx); err != nil {
return "", err
}
// First, validate that the post exists and is owned by the authenticated user
if err := c.validatePostOwnership(ctx, postID); err != nil {
return "", err
}
// Make API call to delete post
path := fmt.Sprintf("/%s", postID.String())
resp, err := c.httpClient.DELETE(path, c.getAccessTokenSafe())
if err != nil {
return "", err
}
// Handle specific error cases
if resp.StatusCode == 404 {
return "", NewValidationError(404, "Post not found", fmt.Sprintf("Post with ID %s does not exist or is not accessible", postID.String()), "post_id")
}
if resp.StatusCode == 403 {
return "", NewAuthenticationError(403, "Access denied", fmt.Sprintf("Cannot delete post %s - insufficient permissions or not the post owner", postID.String()))
}
if resp.StatusCode != 200 {
return "", c.handleAPIError(resp)
}
// Parse response to confirm deletion
var deleteResp struct {
Success bool `json:"success"`
DeletedID string `json:"deleted_id"`
}
if len(resp.Body) > 0 {
if err := json.Unmarshal(resp.Body, &deleteResp); err != nil {
// If we can't parse the response but got 200, assume success
if c.config.Logger != nil {
c.config.Logger.Warn("Could not parse delete response, but got 200 status", "post_id", postID.String())
}
}
}
// Log successful deletion if logger is available
if c.config.Logger != nil {
c.config.Logger.Info("Successfully deleted post", "post_id", postID.String())
}
return deleteResp.DeletedID, nil
}
// DeletePostWithConfirmation deletes a post with an additional confirmation step
func (c *Client) DeletePostWithConfirmation(ctx context.Context, postID PostID, confirmationCallback func(post *Post) bool) (string, error) {
if !postID.Valid() {
return "", NewValidationError(400, ErrEmptyPostID, "Cannot delete post without ID", "post_id")
}
if confirmationCallback == nil {
return "", NewValidationError(400, "Confirmation callback is required", "Must provide confirmation callback", "confirmation_callback")
}
// Get the post first to show details for confirmation
post, err := c.GetPost(ctx, postID)
if err != nil {
return "", err
}
// Call confirmation callback
if !confirmationCallback(post) {
return "", NewValidationError(400, "Deletion cancelled", "User cancelled the deletion", "confirmation")
}
// Proceed with deletion
return c.DeletePost(ctx, postID)
}
// validatePostOwnership checks that the post exists and is owned by the
// authenticated user. Empty identifiers on either side fail closed —
// otherwise two empty strings would compare equal and silently authorise
// deletion.
func (c *Client) validatePostOwnership(ctx context.Context, postID PostID) error {
// Get the post to check ownership
post, err := c.GetPost(ctx, postID)
if err != nil {
return err
}
// Get authenticated user info
me, err := c.GetMe(ctx)
if err != nil {
return NewAuthenticationError(401, "Cannot verify post ownership", "Failed to get authenticated user information")
}
// Prefer owner ID when available — it's the canonical identifier and
// doesn't collide with username handle changes.
if post.Owner != nil && post.Owner.ID != "" && me.ID != "" {
if post.Owner.ID != me.ID {
return NewAuthenticationError(403, "Cannot delete post", fmt.Sprintf("Post %s is owned by user ID %s, not %s", postID.String(), post.Owner.ID, me.ID))
}
return nil
}
// Fall back to username when owner ID is unavailable.
if post.Username == "" || me.Username == "" {
return NewAuthenticationError(403, "Cannot verify post ownership", fmt.Sprintf("Post %s has no comparable owner identifier (post.Owner=%v, post.Username=%q, me.ID=%q, me.Username=%q)", postID.String(), post.Owner, post.Username, me.ID, me.Username))
}
if post.Username != me.Username {
return NewAuthenticationError(403, "Cannot delete post", fmt.Sprintf("Post %s belongs to user %s, not %s", postID.String(), post.Username, me.Username))
}
return nil
}