Skip to content

Commit

Permalink
Migrate InpostService to be an interface
Browse files Browse the repository at this point in the history
  • Loading branch information
alufers committed Sep 21, 2023
1 parent 2f49ec5 commit 83141a5
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 244 deletions.
253 changes: 10 additions & 243 deletions inpostextra/inpost_service.go
Original file line number Diff line number Diff line change
@@ -1,246 +1,13 @@
package inpostextra

import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"strings"
"time"

"github.com/alufers/paczkobot/httphelpers"
"github.com/dgrijalva/jwt-go"
"gorm.io/gorm"
)

var (
ErrReauthenticationRequired = fmt.Errorf("reauthentication required")
ErrRefreshTokenExpired = fmt.Errorf("refresh token expired")
)

type InpostService struct {
BaseURL string
httpClient httphelpers.Client
}

func NewInpostService(
httpClient httphelpers.Client,
) *InpostService {
return &InpostService{
BaseURL: "https://api-inmobile-pl.easypack24.net",
httpClient: httpClient,
}
}

func (s *InpostService) makeJSONRequest(creds *InpostCredentials, method string, path string, data interface{}, respRef interface{}) error {
var bodyBuf io.Reader
if method != "GET" && method != "HEAD" {
body, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("error marshalling JSON data: %s", err)
}
bodyBuf = bytes.NewBuffer(body)
}
// log.Printf("Sending %s request to %s", method, s.BaseURL+path)
if bodyBuf != nil {
log.Printf("Body: %s", bodyBuf)
}
httpReq, err := http.NewRequest(method, s.BaseURL+path, bodyBuf)
if err != nil {
return fmt.Errorf("error creating HTTP request: %s", err)
}
if bodyBuf != nil {
httpReq.Header.Set("Content-Type", "application/json")
}
httpReq.Header.Set("Accept", "application/json")
if creds != nil {
httpReq.Header.Set("Authorization", creds.AuthToken)
}
resp, err := s.httpClient.Do(httpReq)
if err != nil {
return fmt.Errorf("error sending HTTP request: %s", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
data, _ := ioutil.ReadAll(resp.Body)
dataLen := 1024
if len(data) < dataLen {
dataLen = len(data)
}
return fmt.Errorf("HTTP request failed with status code %s: %v", resp.Status, string(data[0:dataLen]))
}
if respRef != nil {
bodyBuf := bytes.NewBuffer(nil)
_, err = io.Copy(bodyBuf, resp.Body)
if err != nil {
return fmt.Errorf("error reading response body: %s", err)
}
// log.Printf("Response: %s", bodyBuf.String())
err = json.Unmarshal(bodyBuf.Bytes(), respRef)

if err != nil {
return fmt.Errorf("error decoding JSON response: %s", err)
}
}
return nil
}

func (s *InpostService) SendSMSCode(phoneNumber string) error {
phoneNumber = NormalizePhoneNumber(phoneNumber)

// make a post request to /v1/sendSMSCode with the phone number sent as JSON
data := map[string]string{
"phoneNumber": phoneNumber,
}
return s.makeJSONRequest(nil, "POST", "/v1/sendSMSCode", data, nil)
}

func (s *InpostService) ConfirmSMSCode(phoneNumber string, code string) (*InpostCredentials, error) {
phoneNumber = NormalizePhoneNumber(phoneNumber)
// make a post request to /v1/confirmSMSCode with the phone number and code sent as JSON
data := map[string]string{
"phoneNumber": phoneNumber,
"smsCode": code,
"phoneOS": "Android",
}

out := &struct {
AuthToken string `json:"authToken"`
RefreshToken string `json:"refreshToken"`
}{}
err := s.makeJSONRequest(nil, "POST", "/v1/confirmSMSCode", data, out)
if err != nil {
return nil, err
}

creds := &InpostCredentials{
AuthToken: out.AuthToken,
RefreshToken: out.RefreshToken,
PhoneNumber: phoneNumber,
}
return creds, nil
}

// Authenticate uses the refresh token to get a new access token.
func (s *InpostService) Authenticate(creds *InpostCredentials) error {
// make a post request to /v1/authenticate with the access token and refresh token sent as JSON
data := map[string]string{
"phoneOS": "Android",
"refreshToken": creds.RefreshToken,
}
out := &struct {
AuthToken string `json:"authToken"`
ReauthenticationRequired bool `json:"reauthenticationRequired"`
RefreshTokenExpiryDate *time.Time `json:"refreshTokenExpiryDate"`
}{}
err := s.makeJSONRequest(nil, "POST", "/v1/authenticate", data, out)
if err != nil {
return err
}
if out.ReauthenticationRequired {
return ErrReauthenticationRequired
}
log.Printf("RefreshTokenExpiryDate: %v", out.RefreshTokenExpiryDate)
if out.RefreshTokenExpiryDate != nil && out.RefreshTokenExpiryDate.Before(time.Now()) {
return ErrRefreshTokenExpired
}
creds.AuthToken = out.AuthToken
return nil
}

func (s *InpostService) ReauthenticateIfNeeded(db *gorm.DB, creds *InpostCredentials) error {
if creds.RefreshToken == "" {
return fmt.Errorf("refresh token is empty")
}

log.Printf("[ReauthenticateIfNeeded] Creds: %+v", creds)

tok, _ := jwt.ParseWithClaims(strings.TrimPrefix(creds.AuthToken, "Bearer "), &jwt.StandardClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte("unused"), nil
})
claims := tok.Claims.(*jwt.StandardClaims)
expirationDate := time.Unix(claims.ExpiresAt, 0)
if expirationDate.Before(time.Now()) {
log.Printf("Inpost token for phone number %v expired %v ago, refreshing...", creds.PhoneNumber, time.Since(expirationDate))
err := s.Authenticate(creds)
if err != nil {
return err
}
if err := db.Save(creds).Error; err != nil {
return fmt.Errorf("error saving credentials for phone number %v: %v", creds.PhoneNumber, err)
}
return nil
}

return nil
}

func (s *InpostService) GetParcel(db *gorm.DB, creds *InpostCredentials, shipmentNumber string) (*InpostParcel, error) {
err := s.ReauthenticateIfNeeded(db, creds)
if err != nil {
return nil, err
}

// make a get request to /v1/parcel/{shipmentNumber}
out := &InpostParcel{}
err = s.makeJSONRequest(creds, "GET", fmt.Sprintf("/v3/parcels/tracked/%s", shipmentNumber), nil, out)
if err != nil {
return nil, err
}
return out, nil
}

func (s *InpostService) GetUserParcels(db *gorm.DB, creds *InpostCredentials) (*GetTrackedParcelsResponse, error) {
err := s.ReauthenticateIfNeeded(db, creds)
if err != nil {
return nil, err
}

// make a get request to parcels
out := &GetTrackedParcelsResponse{}
err = s.makeJSONRequest(creds, "GET", "/v3/parcels/tracked", nil, &out)
if err != nil {
return nil, err
}
return out, nil
}

func (s *InpostService) OpenParcelLocker(db *gorm.DB, creds *InpostCredentials, shipmentNumber string) error {
parcel, err := s.GetParcel(db, creds, shipmentNumber)
if err != nil {
return fmt.Errorf("error getting parcel details before opening: %v", err)
}
if parcel.PickUpPoint == nil || parcel.PickUpPoint.Location == nil {
return fmt.Errorf("parcel %v has no pickup point or location", shipmentNumber)
}
if parcel.Receiver == nil || parcel.Receiver.PhoneNumber == "" {
return fmt.Errorf("parcel %v has no receiver or receiver phone number", shipmentNumber)
}
startOpenSessionRequest := map[string]any{
"geoPoint": map[string]any{
"accuracy": 10.0,
"latitude": parcel.PickUpPoint.Location.Latitude,
"longitude": parcel.PickUpPoint.Location.Longitude,
},
"parcel": map[string]any{
"shipmentNumber": shipmentNumber,
"openCode": parcel.OpenCode,
"receiverPhoneNumber": parcel.Receiver.PhoneNumber,
},
}
validateResp := &ValidateCompartmentResponse{}
err = s.makeJSONRequest(creds, "POST", "/v1/collect/validate", startOpenSessionRequest, validateResp)
if err != nil {
return fmt.Errorf("error validating compartment: %v", err)
}

openResp := make(map[string]any)
err = s.makeJSONRequest(creds, "POST", "/v1/collect/compartment/open/"+validateResp.SessionUUID, map[string]any{}, &openResp)
if err != nil {
return fmt.Errorf("error opening compartment: %v", err)
}
return nil
import "gorm.io/gorm"

type InpostService interface {
SendSMSCode(phoneNumber string) error
ConfirmSMSCode(phoneNumber string, code string) (*InpostCredentials, error)
Authenticate(creds *InpostCredentials) error
ReauthenticateIfNeeded(db *gorm.DB, creds *InpostCredentials) error
GetParcel(db *gorm.DB, creds *InpostCredentials, shipmentNumber string) (*InpostParcel, error)
GetUserParcels(db *gorm.DB, creds *InpostCredentials) (*GetTrackedParcelsResponse, error)
OpenParcelLocker(db *gorm.DB, creds *InpostCredentials, shipmentNumber string) error
}
Loading

0 comments on commit 83141a5

Please sign in to comment.