diff --git a/system/discourse/client.go b/system/discourse/client.go new file mode 100644 index 0000000..f6ffc0b --- /dev/null +++ b/system/discourse/client.go @@ -0,0 +1,200 @@ +package discourse + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "net/url" + + "github.com/hashicorp/go-retryablehttp" +) + +type Response struct { + Type string `json:"type"` + Timestamp int64 `json:"timestamp"` + Message string `json:"message"` + Validation map[string]interface{} `json:"validation,omitempty"` + + Post PostModel `json:"post,omitempty"` + Posts []PostModel `json:"latest_posts,omitempty"` +} + +type RequestError struct { + Response Response + Err error +} + +func (re *RequestError) Error() string { + return re.Err.Error() +} + +func (re *RequestError) Type() string { + return re.Response.Type +} + +func (re *RequestError) Message() string { + return re.Response.Message +} + +type Logger interface { + Debug(args ...interface{}) + Debugf(format string, args ...interface{}) + + Info(args ...interface{}) + Infof(format string, args ...interface{}) + + Warn(args ...interface{}) + Warnf(format string, args ...interface{}) + + Error(args ...interface{}) + Errorf(format string, args ...interface{}) + + Fatal(args ...interface{}) + Fatalf(format string, args ...interface{}) +} + +type StdLogger struct { + L Logger +} + +func NewStdLogger(l Logger) retryablehttp.Logger { + return &StdLogger{ + L: l, + } +} + +func (l *StdLogger) Printf(message string, v ...interface{}) { + l.L.Debug(message, v) +} + +type ClientConfig struct { + Endpoint string + Credentials map[string]string + HTTPClient *http.Client + Logger Logger +} + +type Client struct { + httpClient *retryablehttp.Client + endpoint *url.URL + credentials map[string]string + logger Logger + Posts PostsService +} + +func NewDefaultClientConfig( + endpoint string, + credentials map[string]string, + logger Logger, +) ClientConfig { + return ClientConfig{ + Endpoint: endpoint, + Credentials: credentials, + HTTPClient: http.DefaultClient, + Logger: logger, + } +} + +func NewClient(cc *ClientConfig) *Client { + c := new(Client) + c.logger = cc.Logger + c.httpClient = retryablehttp.NewClient() + c.httpClient.RetryMax = 3 + if c.logger != nil { + c.httpClient.Logger = NewStdLogger(c.logger) + } + c.httpClient.HTTPClient = cc.HTTPClient + c.endpoint, _ = url.Parse(cc.Endpoint) + c.credentials = cc.Credentials + fmt.Printf("%v\n", c.credentials) + + c.Posts = &PostServiceHandler{client: c} + + return c +} + +func (c *Client) NewRequest( + ctx context.Context, + method string, + location string, + body interface{}, +) (*http.Request, error) { + var parsedURL *url.URL + var req *http.Request + var err error + + if parsedURL, err = c.endpoint.Parse(location); err != nil { + return nil, err + } + + buffer := new(bytes.Buffer) + if body != nil { + if err = json.NewEncoder(buffer).Encode(body); err != nil { + return nil, err + } + } + + if req, err = http.NewRequest( + method, + parsedURL.String(), + buffer, + ); err != nil { + return nil, err + } + + req.Header.Add("User-Agent", "gobbs") + req.Header.Add("Accept", "application/json") + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Api-Username", c.credentials["username"]) + req.Header.Add("Api-Key", c.credentials["key"]) + + fmt.Printf("%v\n", req) + return req, nil +} + +func (c *Client) Do( + ctx context.Context, + req *http.Request, + content *Response, +) error { + var rreq *retryablehttp.Request + var res *http.Response + var body []byte + var err error + + if rreq, err = retryablehttp.FromRequest(req); err != nil { + return err + } + + rreq = rreq.WithContext(ctx) + if res, err = c.httpClient.Do(rreq); err != nil { + return err + } + defer res.Body.Close() + + if body, err = ioutil.ReadAll(res.Body); err != nil { + return err + } + + if content != nil { + if err = json.Unmarshal(body, content); err != nil { + return err + } + } + + fmt.Printf("%v\n", res) + + if res.StatusCode < http.StatusOK || + res.StatusCode > http.StatusNoContent { + return &RequestError{ + Err: errors.New("Non-2xx status code"), + Response: *content, + } + } + + return nil +} diff --git a/system/discourse/discourse.go b/system/discourse/discourse.go index 4dcd8ed..eb34215 100644 --- a/system/discourse/discourse.go +++ b/system/discourse/discourse.go @@ -2,6 +2,7 @@ package discourse import ( "bufio" + "context" "crypto/rand" "crypto/rsa" "crypto/x509" @@ -9,6 +10,7 @@ import ( "encoding/json" "encoding/pem" "fmt" + "net/http" "net/url" "os" "strings" @@ -17,10 +19,12 @@ import ( "github.com/mrusme/gobbs/models/post" "github.com/mrusme/gobbs/system/adapter" "github.com/pkg/browser" + "go.uber.org/zap" ) type System struct { config map[string]interface{} + logger *zap.SugaredLogger } type UserAPIKey struct { @@ -38,6 +42,10 @@ func (sys *System) SetConfig(cfg *map[string]interface{}) { sys.config = *cfg } +func (sys *System) SetLogger(logger *zap.SugaredLogger) { + sys.logger = logger +} + func (sys *System) Load() error { return nil @@ -149,12 +157,31 @@ func (sys *System) Connect(sysURL string) error { if sys.config == nil { sys.config = make(map[string]interface{}) } + sys.config["url"] = sysURL sys.config["credentials"] = credentials return nil } func (sys *System) ListPosts() ([]post.Post, error) { + credentials := make(map[string]string) + for k, v := range (sys.config["credentials"]).(map[string]interface{}) { + credentials[k] = v.(string) + } + c := NewClient(&ClientConfig{ + Endpoint: sys.config["url"].(string), + Credentials: credentials, + HTTPClient: http.DefaultClient, + Logger: sys.logger, + }) + + posts, err := c.Posts.List(context.Background()) + if err != nil { + return []post.Post{}, err + } + + fmt.Printf("%v\n", posts) + return []post.Post{}, nil } diff --git a/system/discourse/posts.go b/system/discourse/posts.go new file mode 100644 index 0000000..2caa206 --- /dev/null +++ b/system/discourse/posts.go @@ -0,0 +1,115 @@ +package discourse + +import ( + "context" + "net/http" +) + +const PostsBaseURL = "/posts" + +type PostModel struct { + ID string `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + AvatarTemplate string `json:"avater_template"` + CreatedAt string `json:"created_at"` + Cooked string `json:"cooked"` + PostNumber int `json:"post_number"` + PostType int `json:"post_type"` + UpdatedAt string `json:"updated_at"` + ReplyCount int `json:"reply_count"` + IncomingLinkCount int `json:"incoming_link_count"` + Reads int `json:"reads"` + ReadersCount int `json:"readers_count"` + Score int `json:"score"` + Yours bool `json:"yours"` + TopicID int `json:"topic_id"` + TopicSlug string `json:"topic_slug"` + TopicTitle string `json:"topic_title"` + TopicHTMLTitle string `json:"topic_html_title"` + CategoryID int `json:"category_id"` + DisplayUsername string `json:"display_username"` + PrimaryGroupName string `json:"primary_group_name"` + FlairName string `json:"flair_name"` + FlairURL string `json:"flair_url"` + FlairBGColor string `json:"flair_bg_color"` + FlairColor string `json:"flair_color"` + Version int `json:"version"` + CanEdit bool `json:"can_edit"` + CanDelete bool `json:"can_delete"` + CanRecover bool `json:"can_recover"` + CanWiki bool `json:"can_wiki"` + UserTitle string `json:"user_title"` + Raw string `json:"raw"` + ActionsSummary struct { + ID int `json:"id"` + CanAct bool `json:"can_act"` + } `json:"actions_summary"` + Moderator bool `json:"moderator"` + Admin bool `json:"admin"` + Staff bool `json:"staff"` + UserID int `json:"user_id"` + Hidden bool `json:"hidden"` + TrustLevel int `json:"trust_level"` + DeletedAt string `json:"deleted_at"` + UserDeleted bool `json:"user_deleted"` + EditReason string `json:"edit_reason"` + CanViewEditHistory bool `json:"can_view_edit_history"` + Wiki bool `json:"wiki"` + ReviewableID string `json:"reviewable_id"` + ReviewableScoreCount int `json:"reviewable_score_count"` + ReviewableScorePendingCount int `json:"reviewable_score_pending_count"` +} + +type PostsService interface { + Show( + ctx context.Context, + id string, + ) (PostModel, error) + List( + ctx context.Context, + ) ([]PostModel, error) +} + +type PostServiceHandler struct { + client *Client +} + +// Show +func (a *PostServiceHandler) Show( + ctx context.Context, + id string, +) (PostModel, error) { + uri := PostsBaseURL + "/" + id + ".json" + + req, err := a.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return PostModel{}, err + } + + response := new(Response) + if err = a.client.Do(ctx, req, response); err != nil { + return PostModel{}, err + } + + return response.Post, nil +} + +// List +func (a *PostServiceHandler) List( + ctx context.Context, +) ([]PostModel, error) { + uri := PostsBaseURL + ".json" + + req, err := a.client.NewRequest(ctx, http.MethodGet, uri, nil) + if err != nil { + return []PostModel{}, err + } + + response := new(Response) + if err = a.client.Do(ctx, req, response); err != nil { + return []PostModel{}, err + } + + return response.Posts, nil +} diff --git a/system/lemmy/lemmy.go b/system/lemmy/lemmy.go index d32a9d6..574c425 100644 --- a/system/lemmy/lemmy.go +++ b/system/lemmy/lemmy.go @@ -3,10 +3,12 @@ package lemmy import ( "github.com/mrusme/gobbs/models/post" "github.com/mrusme/gobbs/system/adapter" + "go.uber.org/zap" ) type System struct { config map[string]interface{} + logger *zap.SugaredLogger } func (sys *System) GetConfig() map[string]interface{} { @@ -17,6 +19,10 @@ func (sys *System) SetConfig(cfg *map[string]interface{}) { sys.config = *cfg } +func (sys *System) SetLogger(logger *zap.SugaredLogger) { + sys.logger = logger +} + func (sys *System) Connect(sysURL string) error { return nil } diff --git a/system/system.go b/system/system.go index ef5ccb8..cde6b64 100644 --- a/system/system.go +++ b/system/system.go @@ -7,11 +7,13 @@ import ( "github.com/mrusme/gobbs/system/adapter" "github.com/mrusme/gobbs/system/discourse" "github.com/mrusme/gobbs/system/lemmy" + "go.uber.org/zap" ) type System interface { GetConfig() map[string]interface{} SetConfig(cfg *map[string]interface{}) + SetLogger(logger *zap.SugaredLogger) GetCapabilities() []adapter.Capability Connect(sysURL string) error @@ -20,7 +22,11 @@ type System interface { ListPosts() ([]post.Post, error) } -func New(sysType string, sysConfig *map[string]interface{}) (System, error) { +func New( + sysType string, + sysConfig *map[string]interface{}, + logger *zap.SugaredLogger, +) (System, error) { var sys System switch sysType { @@ -33,6 +39,7 @@ func New(sysType string, sysConfig *map[string]interface{}) (System, error) { } sys.SetConfig(sysConfig) + sys.SetLogger(logger) err := sys.Load() if err != nil { return nil, err