commit 24a142c6a64faef664546328e9463341ebce8524 Author: Ron Rise Date: Wed Nov 1 10:56:22 2023 -0400 We Had To Use Dark Magic To Make This Work diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..68729d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea/ +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..6b76562 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Email api golang skd + +```shell +go get git.siteworxpro.com/golang/packages/email-api +``` + +````go +import "git.siteworxpro.com/golang/packages/email-api/client" + +config := client.ConfigFromEnv() +apiClient := client.NewClient(config) + +emails, err := apiClient.GetEmails(1, 100) +if err != nil { + log.Fatal(err) +} + +for _, email := range emails.Emails { + println(email.Uuid) +} +```` \ No newline at end of file diff --git a/client/client.go b/client/client.go new file mode 100755 index 0000000..930998f --- /dev/null +++ b/client/client.go @@ -0,0 +1,39 @@ +package client + +type Client struct { + config *Configuration + token *Token +} + +var c = struct { + client *Client + initialized bool +}{} + +type accessTokenRequest struct { + GrantType string `json:"grant_type"` + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` +} + +func NewClient(config *Configuration) *Client { + + if c.initialized { + return c.client + } + + client := &Client{ + config: config, + } + + token, err := getToken(client.config) + if err != nil { + return nil + } + + client.token = token + + c.client = client + + return c.client +} diff --git a/client/configuration.go b/client/configuration.go new file mode 100755 index 0000000..3b91853 --- /dev/null +++ b/client/configuration.go @@ -0,0 +1,36 @@ +package client + +import ( + "git.siteworxpro.com/golang/packages/utilities/Env" + "log" +) + +const ( + apiEndpoint Env.EnvironmentVariable = "API_ENDPOINT" + clientId Env.EnvironmentVariable = "CLIENT_ID" + clientSecret Env.EnvironmentVariable = "CLIENT_SECRET" +) + +type Configuration struct { + Endpoint string + ClientId string + ClientSecret string +} + +func ConfigFromEnv() *Configuration { + config := Configuration{ + Endpoint: apiEndpoint.GetEnvString("https://email.siteworxpro.com"), + ClientId: clientId.GetEnvString(""), + ClientSecret: clientSecret.GetEnvString(""), + } + + if config.ClientId == "" { + log.Fatal("Client ID not provided in ENV. use CLIENT_ID") + } + + if config.ClientSecret == "" { + log.Fatal("Client Secret not provided in ENV. use ClientSecret") + } + + return &config +} diff --git a/client/create.go b/client/create.go new file mode 100755 index 0000000..c41b2dc --- /dev/null +++ b/client/create.go @@ -0,0 +1,64 @@ +package client + +import ( + "bytes" + "encoding/json" + "net/http" + "time" +) + +type EmailRequest struct { + Source string `json:"Source"` + Destination struct { + ToAddresses []string `json:"ToAddresses"` + CcAddresses []string `json:"CcAddresses"` + BccAddresses []string `json:"BccAddresses"` + } `json:"Destination"` + Message struct { + Body struct { + Html struct { + Data string `json:"Data"` + } `json:"Html,omitempty"` + Text struct { + Data string `json:"Data"` + } `json:"Text,omitempty"` + } `json:"Body"` + Subject struct { + Data string `json:"Data"` + } `json:"Subject"` + } `json:"Message"` + ScheduledTime time.Time `json:"ScheduledTime,omitempty"` + Catch bool `json:"Catch,omitempty"` +} + +type EmailCreateResponse struct { + Id string `json:"id"` + CreatedAt time.Time `json:"created_at"` +} + +func (client *Client) SendEmail(request *EmailRequest) (*EmailCreateResponse, error) { + jsonData, _ := json.Marshal(request) + + httpClient := &http.Client{} + req, _ := http.NewRequest( + "POST", + client.config.Endpoint+"/api/email", + bytes.NewReader(jsonData), + ) + req.Header.Set("Authorization", client.token.TokenType+" "+client.token.AccessToken) + req.Header.Set("Content-Type", "application/json") + + resp, err := httpClient.Do(req) + + if err != nil { + return nil, err + } + + response := Response[EmailCreateResponse]{} + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return nil, err + } + + return &response.Payload, nil +} diff --git a/client/get.go b/client/get.go new file mode 100755 index 0000000..fccb05b --- /dev/null +++ b/client/get.go @@ -0,0 +1,70 @@ +package client + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" +) + +func (client *Client) GetEmails(page uint16, limit uint16) (*EmailResponse, error) { + response := Response[EmailResponse]{} + + httpClient := &http.Client{} + req, _ := http.NewRequest( + "GET", + client.config.Endpoint+"/api/email?page="+strconv.Itoa(int(page))+"&limit="+strconv.Itoa(int(limit)), + nil, + ) + req.Header.Set("Authorization", client.token.TokenType+" "+client.token.AccessToken) + get, err := httpClient.Do(req) + + if err != nil { + return nil, err + } + + if get.StatusCode >= 400 { + return nil, fmt.Errorf("") + } + + body := make([]byte, get.ContentLength) + _, err = get.Body.Read(body) + if err != nil { + return nil, err + } + + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + + return &response.Payload, nil +} + +func (client *Client) GetEmail(uuid string) (*Email, error) { + httpClient := &http.Client{} + req, _ := http.NewRequest( + "GET", + client.config.Endpoint+"/api/email/"+uuid, + nil, + ) + req.Header.Set("Authorization", client.token.TokenType+" "+client.token.AccessToken) + get, err := httpClient.Do(req) + defer get.Body.Close() + + if err != nil { + return nil, err + } + + if get.StatusCode >= 400 { + return nil, fmt.Errorf("") + } + + response := Response[Email]{} + err = json.NewDecoder(get.Body).Decode(&response) + if err != nil { + return nil, err + } + + return &response.Payload, nil +} diff --git a/client/token.go b/client/token.go new file mode 100755 index 0000000..9f5514e --- /dev/null +++ b/client/token.go @@ -0,0 +1,99 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "git.siteworxpro.com/golang/packages/email-api/redis" + "net/http" + "time" +) + +type Token struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn uint16 `json:"expires_in"` +} + +type AccessTokenRequest struct { + GrantType string `json:"grant_type"` + ClientId string `json:"client_id"` + ClientSecret string `json:"client_secret"` +} + +func NewToken(config *Configuration) *Token { + + return &Token{} +} + +func getToken(configuration *Configuration) (*Token, error) { + + token := FromCache() + + if token.AccessToken != "" { + return token, nil + } + + request := accessTokenRequest{ + GrantType: "client_credentials", + ClientId: configuration.ClientId, + ClientSecret: configuration.ClientSecret, + } + + body, _ := json.Marshal(request) + + resp, err := http.Post(configuration.Endpoint+"/access_token", "application/json", bytes.NewBuffer(body)) + + if err != nil { + return nil, err + } + + responseBody := make([]byte, resp.ContentLength) + _, err = resp.Body.Read(responseBody) + if err != nil { + return nil, err + } + + err = json.Unmarshal(responseBody, token) + if err != nil { + return nil, err + } + + saveCache(token) + + return token, nil +} + +func FromCache() *Token { + rdb := redis.NewRedis() + + token := Token{} + + result, err := rdb.Get(context.Background(), "api.access_token").Result() + if err != nil { + return &token + } + + err = json.Unmarshal([]byte(result), &token) + if err != nil { + return &token + } + + return &token +} + +func saveCache(token *Token) { + rdb := redis.NewRedis() + + tokenJson, _ := json.Marshal(token) + + expiresIn := time.Duration(int64(token.ExpiresIn) * 100000000) + + cmd := rdb.Set(context.Background(), "api.access_token", tokenJson, expiresIn) + result, err := cmd.Result() + if err != nil { + return + } + + println(result) +} diff --git a/client/types.go b/client/types.go new file mode 100755 index 0000000..70e52c8 --- /dev/null +++ b/client/types.go @@ -0,0 +1,29 @@ +package client + +import "time" + +type Email struct { + Uuid string `json:"uuid"` + IsSent uint8 `json:"is_sent"` + CreatedAt time.Time `json:"created_at"` + SentTime time.Time `json:"sent_time"` + To string `json:"to"` + From string `json:"from"` + Subject string `json:"subject"` + Body string `json:"body,omitempty"` +} + +type EmailResponse struct { + Emails []Email `json:"emails"` + Page uint16 `json:"page"` + AvailablePages uint16 `json:"availablePages"` + Total uint16 `json:"total"` +} + +type Response[T any] struct { + Payload T `json:"payload"` + Time uint32 `json:"time"` + Version string `json:"version"` + Status string `json:"status"` + Md5 string `json:"md5"` +} diff --git a/go.mod b/go.mod new file mode 100755 index 0000000..9a1321e --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module git.siteworxpro.com/golang/packages/email-api + +go 1.19 + +require ( + git.siteworxpro.com/golang/packages/utilities v1.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/redis/go-redis/v9 v9.0.2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100755 index 0000000..a5295f1 --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +git.siteworxpro.com/golang/packages/utilities v1.0.1/go.mod h1:5ZZDqKb5Y7B552oJjhaMVAj6r9xvqw/wkp1EADmT5wg= +git.siteworxpro.com/golang/packages/utilities v1.2.0 h1:UDsaEl0NOqPcHudMKcOGwtlShBoqCW3NXYZH9zh+Ju8= +git.siteworxpro.com/golang/packages/utilities v1.2.0/go.mod h1:5ZZDqKb5Y7B552oJjhaMVAj6r9xvqw/wkp1EADmT5wg= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= diff --git a/redis/redis.go b/redis/redis.go new file mode 100755 index 0000000..1bcc786 --- /dev/null +++ b/redis/redis.go @@ -0,0 +1,40 @@ +package redis + +import ( + "git.siteworxpro.com/golang/packages/utilities/Env" + "github.com/redis/go-redis/v9" + "strconv" +) + +const ( + redisHost Env.EnvironmentVariable = "REDIS_HOST" + redisHPassword Env.EnvironmentVariable = "REDIS_PASSWORD" + redisDb Env.EnvironmentVariable = "REDIS_DB" +) + +var r = struct { + client *redis.Client + initialized bool +}{} + +func NewRedis() *redis.Client { + if r.initialized { + return r.client + } + + db, err := strconv.ParseInt(redisDb.GetEnvString("0"), 10, 64) + + if err != nil { + panic(err) + } + + rdb := redis.NewClient(&redis.Options{ + Addr: redisHost.GetEnvString("localhost:6379"), + Password: redisHPassword.GetEnvString(""), + DB: int(db), + }) + + r.client = rdb + + return r.client +}