From 9f3965785f1367aa9e3773409cc4623db2786d02 Mon Sep 17 00:00:00 2001 From: CeoFred Date: Wed, 7 Feb 2024 14:27:59 +0100 Subject: [PATCH] Revert "feat: v0.1.0" This reverts commit e7509a3ce3cd688fd88b2685957d9dba30bb076f. --- README.md | 145 +++++++-------- fastotp.go | 173 ++++++++++++++++++ fastotp_test.go | 473 ++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 2 +- interface.go | 3 +- lib/http.go | 25 +-- mock.go | 2 +- type_test.go | 61 +++++++ types.go | 30 ++- weavy.go | 310 ------------------------------- 10 files changed, 801 insertions(+), 423 deletions(-) create mode 100644 fastotp.go create mode 100644 fastotp_test.go create mode 100644 type_test.go delete mode 100644 weavy.go diff --git a/README.md b/README.md index d116615..dedfc37 100644 --- a/README.md +++ b/README.md @@ -1,104 +1,89 @@ -# Weavy Chat GO Lang SDK +# Go FastOTP -This library provides a Go client for interacting with the Weavy Chat API. It allows you to create applications, manage users, issue access tokens, and perform various operations within the Weavy Chat ecosystem. +FastOTP is a Go library for interacting with the FastOTP API to generate one-time passwords (OTPs) easily. ## Installation -To install the library, use `go get`: - ```bash -go get github.com/CeoFred/weavychat +go get -u github.com/CeoFred/fast-otp@v1.0.2 ``` ## Usage -```go -import "github.com/CeoFred/weavychat" -``` - -## Documentation -This library currently supports the following Weavy Chat API methods: - -`NewWeavyServer`: Creates a new WeavyServer instance. -`NewApp`: Creates a new app. -`GetApp`: Retrieves an existing app. -`NewUser`: Creates a new user. -`AddUserToApp`: Adds users to an app. -`RemoveUserFromApp`: Removes users from an app. -`AppInit`: Initializes an app. -`GetAccessToken`: Issues an access token for a user. - -## Authentication - -You need to provide the server URL and API key to authenticate with the Weavy Chat API. +### Basic Example ```go -weavyServer := weavychat.NewWeavyServer("your-weavy-server-url", "your-api-key") -``` - -## Creating Applications - -You can create applications using the `NewApp` method: - -```go -appRequest := &weavychat.AppRequest{ - ID: 1, - Type: weavychat.AppType("your-app-type"), - UID: "your-uid", - DisplayName: "Your App", - Metadata: weavychat.Metadata{}, - Tags: []string{"tag1", "tag2"}, -} -app, err := weavyServer.NewApp(context.Background(), appRequest) -if err != nil { - // Handle error +package main + +import ( + "fmt" + "log" + + "github.com/CeoFred/fast-otp" +) + +func main() { + // Replace "your_api_key" with your actual FastOTP API key + apiKey := "your_api_key" + + // Create an instance of FastOtp + client := fastotp.NewFastOTP(apiKey) + + // Create context for library functions + ctx := context.Background() + + // Define OTP generation payload + payload := fastotp.GenerateOTPPayload{ + Delivery: OtpDelivery{ + "email": "example@example.com", + }, + Identifier: "user123", + TokenLength: 6, + Type: "numeric", + Validity: 120, + } + + // Generate OTP + otp, err := client.GenerateOTP(ctx, payload) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Generated OTP: %s\n", otp) + + // Validate OTP + otp, err = client.ValidateOTP( + ctx, + ValidateOTPPayload{ + Identifier: "user123", + Token: "123456", + } + ) + if err != nil { + log.Fatal(err) + } + + fmt.Printf("Otp validation status: %s\n", otp.Status) } ``` -## Managing Users +## API Documentation -You can create new users using the `NewUser` method: +For detailed information about the FastOTP API and available endpoints, refer to the [official API documentation](https://api.fastotp.co/docs). -```go -user := &weavychat.User{ - UID: "user-uid", - Email: "user@example.com", - GivenName: "John", - MiddleName: "Doe", - Name: "John Doe", - FamilyName: "Doe", - Nickname: "JD", - PhoneNumber: "+1234567890", - Comment: "A new user", - Picture: "user-avatar-url", - Directory: "directory-id", - Metadata: weavychat.Metadata{}, - Tags: []string{"tag1", "tag2"}, - IsSuspended: false, -} - -newUser, err := weavyServer.NewUser(context.Background(), user) -if err != nil { - // Handle error -} -``` - -## Access Tokens +## Configuration -You can issue access tokens for users: - -```go -accessToken, err := weavyServer.GetAccessToken(context.Background(), "user-uid", 3600) -if err != nil { - // Handle error -} -``` +- `APIKey`: Your FastOTP API key. ## Contributing -Contributions are welcome! If you find any issues or have suggestions for improvement, please create an issue or a pull request on GitHub. +If you'd like to contribute to this project, please follow the guidelines in [CONTRIBUTING.md](CONTRIBUTING.md). ## License -This library is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. \ No newline at end of file +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. + +## Acknowledgments + +- Thanks to the FastOTP team for providing the awesome OTP generation service. diff --git a/fastotp.go b/fastotp.go new file mode 100644 index 0000000..6266012 --- /dev/null +++ b/fastotp.go @@ -0,0 +1,173 @@ +package fastotp + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + httpclient "github.com/CeoFred/fast-otp/lib" +) + +const ( + baseURL = "https://api.fastotp.co" +) + +// FastOTP is the main struct for the FastOtp package. +type FastOTP struct { + apiKey string + baseURL string + client HttpClient +} + +// ErrorResponse is the error struct for the FastOtp package. +type ErrorResponse struct { + Errors map[string][]string `json:"errors"` + Message string `json:"message"` +} + +// OTP is the struct for the OTP object. +type OTP struct { + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + UpdatedAt time.Time `json:"updated_at"` + DeliveryDetails DeliveryDetails `json:"delivery_details"` + ID string `json:"id"` + Identifier string `json:"identifier"` + Status OTPStatus `json:"status"` + Type OTPType `json:"type"` + DeliveryMethods []string `json:"delivery_methods"` +} + +// OTPResponse is the struct for the OTP response object. +type OTPResponse struct { + OTP OTP `json:"otp"` +} + +// DeliveryDetails is the struct for the DeliveryDetails object. +type DeliveryDetails struct { + Email string `json:"email"` +} + +// OTPDelivery is the struct for the OtpDelivery object. +type OTPDelivery map[string]string + +// GenerateOTPPayload is the struct for the GenerateOTPPayload object. +type GenerateOTPPayload struct { + Delivery OTPDelivery `json:"delivery"` + Identifier string `json:"identifier"` + Type OTPType `json:"type"` + TokenLength int `json:"token_length"` + Validity int `json:"validity"` +} + +// ValidateOTPPayload is the struct for the ValidateOTPPayload object. +type ValidateOTPPayload struct { + Identifier string `json:"identifier"` + Token string `json:"token"` +} + +// NewFastOTP creates a new FastOtp instance. +func NewFastOTP(apiKey string) *FastOTP { + return &FastOTP{ + apiKey: apiKey, + baseURL: baseURL, + client: httpclient.NewAPIClient(baseURL, apiKey), + } +} + +func (f *FastOTP) GenerateOTP(ctx context.Context, payload GenerateOTPPayload) (*OTP, error) { + resp, err := f.client.Post(ctx, "/generate", payload) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var errorResponse ErrorResponse + if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { + return nil, err + } + + if len(errorResponse.Errors) > 0 { + return nil, formatValidationError(errorResponse.Errors) + } + + return nil, fmt.Errorf("API error: %s", errorResponse.Message) + } + + var otpResponse OTPResponse + if err := json.NewDecoder(resp.Body).Decode(&otpResponse); err != nil { + return nil, err + } + + return &otpResponse.OTP, nil +} + +func (f *FastOTP) ValidateOTP(ctx context.Context, payload ValidateOTPPayload) (*OTP, error) { + resp, err := f.client.Post(ctx, "/validate", payload) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var errorResponse ErrorResponse + if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { + return nil, err + } + + if len(errorResponse.Errors) > 0 { + return nil, formatValidationError(errorResponse.Errors) + } + + return nil, fmt.Errorf("API error: %s", errorResponse.Message) + } + + var otpResponse OTPResponse + if err := json.NewDecoder(resp.Body).Decode(&otpResponse); err != nil { + return nil, err + } + + return &otpResponse.OTP, nil +} + +// GetOtp gets a new otp +func (f *FastOTP) GetOtp(ctx context.Context, id string) (*OTP, error) { + resp, err := f.client.Get(ctx, id) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + var errorResponse ErrorResponse + if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { + return nil, err + } + + if len(errorResponse.Errors) > 0 { + return nil, formatValidationError(errorResponse.Errors) + } + + return nil, fmt.Errorf("API error: %s", errorResponse.Message) + } + + var otpResponse OTPResponse + if err := json.NewDecoder(resp.Body).Decode(&otpResponse); err != nil { + return nil, err + } + + return &otpResponse.OTP, nil +} + +func formatValidationError(errors map[string][]string) error { + var errorMessage string + for field, fieldErrors := range errors { + for _, err := range fieldErrors { + errorMessage += fmt.Sprintf("%s: %s\n", field, err) + } + } + return fmt.Errorf("validation errors:\n%s", errorMessage) +} diff --git a/fastotp_test.go b/fastotp_test.go new file mode 100644 index 0000000..9d1861d --- /dev/null +++ b/fastotp_test.go @@ -0,0 +1,473 @@ +package fastotp + +import ( + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" + "testing" + + httpclient "github.com/CeoFred/fast-otp/lib" + + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" + "gopkg.in/stretchr/testify.v1/require" +) + +var ( + mockedResponse = `{ + "otp": { + "created_at": "2024-01-19T00:24:06.000000Z", + "delivery_details": { + "email": "test@example.com" + }, + "delivery_methods": [ + "email" + ], + "expires_at": "2024-01-19T17:04:06.000000Z", + "id": "9b202659-fee7-46ab-836b-cdd310c4f327", + "identifier": "test_identifier", + "status": "pending", + "type": "alpha_numeric", + "updated_at": "2024-01-19T00:24:06.000000Z" + } + }` + + mockedValidationResponse = `{ + "otp": { + "created_at": "2024-01-19T00:24:06.000000Z", + "delivery_details": { + "email": "test@example.com" + }, + "delivery_methods": [ + "email" + ], + "expires_at": "2024-01-19T17:04:06.000000Z", + "id": "9b202659-fee7-46ab-836b-cdd310c4f327", + "identifier": "test_identifier", + "status": "validated", + "type": "alpha_numeric", + "updated_at": "2024-01-19T00:24:06.000000Z" + } + }` + + mockedError = `{ + "message": "something isn't right" + }` + + mockedErrorWithSomeError = `{ + "message": "something isn't right" + "errors":[ + "some error" + ] + }` + + mockAPIKey = "test_api_key" +) + +func TestMain(m *testing.M) { + code := 1 + defer func() { + os.Exit(code) + }() + + code = m.Run() +} + +func TestMarshalling(t *testing.T) { + var resp *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedResponse))).Decode(&resp) + require.NoError(t, err) + + fmt.Printf("\n\nID is: %s\n\n", resp.OTP.ID) + assert.Equal(t, "test@example.com", resp.OTP.DeliveryDetails.Email) + assert.Equal(t, OTPStatusPending, resp.OTP.Status) + assert.Equal(t, OTPTypeAlphaNumeric, resp.OTP.Type) + assert.Equal(t, "9b202659-fee7-46ab-836b-cdd310c4f327", resp.OTP.ID) + assert.Equal(t, "test_identifier", resp.OTP.Identifier) +} + +func TestGenerateOTP(t *testing.T) { + reset := mockHttpRequest(http.StatusOK, http.MethodPost, "/generate", mockedResponse) + defer reset() + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(context.TODO(), payload) + require.NoError(t, err) + require.NotNil(t, otp) + + assert.Equal(t, "test@example.com", otp.DeliveryDetails.Email) + assert.Equal(t, "test_identifier", otp.Identifier) + assert.Equal(t, OTPStatusPending, otp.Status) + assert.Equal(t, OTPTypeAlphaNumeric, otp.Type) + assert.Equal(t, "9b202659-fee7-46ab-836b-cdd310c4f327", otp.ID) +} + +func TestGenerateOTP_500Response(t *testing.T) { + reset := mockErrorHttpRequest(500, http.MethodPost, "/generate", "") + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(context.TODO(), payload) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestGenerateOTP_400Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusBadRequest, http.MethodPost, "/generate", mockedError) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(context.TODO(), payload) + require.Error(t, err) + assert.Nil(t, otp) + assert.Contains(t, err.Error(), "something isn't right") +} + +func TestGenerateOTP_401ErrorResponse(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + GetFunc: func(ctx context.Context, id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + PostFunc: func(ctx context.Context, endpoint string, payload interface{}) (*http.Response, error) { + return nil, errors.New("something isn't right") + }, + }, + } + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(context.TODO(), payload) + require.Error(t, err) + require.Nil(t, otp) +} + +func TestGenerateOTP_401MoreErrorResponse(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + PostFunc: func(ctx context.Context, endpoint string, payload interface{}) (*http.Response, error) { + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + }, + } + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(context.TODO(), payload) + require.Error(t, err) + require.Nil(t, otp) +} + +func TestGenerateOTP_401Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusBadRequest, http.MethodPost, "/generate", mockedErrorWithSomeError) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + delivery := OTPDelivery{ + "email": "test@example.com", + } + + payload := GenerateOTPPayload{ + Delivery: delivery, + Identifier: "test_identifier", + TokenLength: 6, + Type: OTPTypeAlphaNumeric, + Validity: 120, + } + + otp, err := fastOtp.GenerateOTP(context.TODO(), payload) + require.Error(t, err) + assert.Nil(t, otp) + assert.Contains(t, err.Error(), "something isn't right") + +} + +func TestValidateOTP(t *testing.T) { + reset := mockHttpRequest(http.StatusOK, http.MethodPost, "/validate", mockedValidationResponse) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + ctx := context.Background() + otp, err := fastOtp.ValidateOTP(ctx, payload) + require.NoError(t, err) + require.NotNil(t, otp) + assert.Equal(t, "test_identifier", otp.Identifier) + assert.Equal(t, OTPStatusValidated, otp.Status) +} + +func TestValidateOTP_500Response(t *testing.T) { + reset := mockErrorHttpRequest(500, http.MethodPost, "/validate", "") + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + ctx := context.Background() + otp, err := fastOtp.ValidateOTP(ctx, payload) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestValidateOTP_400Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusBadRequest, http.MethodPost, "/validate", mockedError) + defer reset() + fastOtp := NewFastOTP(mockAPIKey) + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(context.TODO(), payload) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestValidateOTP_401Response(t *testing.T) { + reset := mockErrorHttpRequest(http.StatusUnauthorized, http.MethodPost, "/validate", mockedErrorWithSomeError) + defer reset() + + fastOtp := NewFastOTP(mockAPIKey) + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(context.TODO(), payload) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestValidateOTP_401MoreErrorResponse(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + PostFunc: func(ctx context.Context, endpoint string, payload interface{}) (*http.Response, error) { + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + }, + } + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(context.TODO(), payload) + require.Error(t, err) + assert.Nil(t, otp) +} + +func TestValidateOTP_POSTError(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + PostFunc: func(ctx context.Context, endpoint string, payload interface{}) (*http.Response, error) { + return nil, errors.New("some error") + }, + }, + } + + payload := ValidateOTPPayload{ + Identifier: "test_identifier", + Token: "123456", + } + + otp, err := fastOtp.ValidateOTP(context.TODO(), payload) + require.EqualError(t, err, "some error") + assert.Nil(t, otp) +} + +func TestFastOtp_GetOtp(t *testing.T) { + fastOtp := &FastOTP{ + apiKey: mockAPIKey, + client: mockedHTTPClient{ + GetFunc: func(ctx context.Context, id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(200, otpResponse) + }, + }, + } + + otp, err := fastOtp.GetOtp(context.TODO(), "test") + require.NoError(t, err) + require.NotNil(t, otp) + + //assert.Equal(t, "123456", otp.T) + assert.Equal(t, OTPStatusValidated, otp.Status) +} + +func TestFastOtp_GetOtpWithError(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + GetFunc: func(ctx context.Context, id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(401, &ErrorResponse{Message: "something isn't right"}) + }, + }, + } + + otp, err := fastOtp.GetOtp(context.TODO(), "test") + require.Error(t, err) + require.Nil(t, otp) +} + +func TestFastOtp_GetOtpWithMoreError(t *testing.T) { + fastOtp := &FastOTP{ + client: mockedHTTPClient{ + GetFunc: func(ctx context.Context, id string) (*http.Response, error) { + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(mockedValidationResponse))).Decode(&otpResponse) + if err != nil { + log.Fatal(err) + } + return httpmock.NewJsonResponse(401, + &ErrorResponse{ + Errors: map[string][]string{"error": {"something isn't right"}}, + Message: "something isn't right"}) + }, + }, + } + + otp, err := fastOtp.GetOtp(context.TODO(), "test") + require.Error(t, err) + require.Nil(t, otp) +} + +func mockHttpRequest(code int, method, path, response string) func() { + httpmock.ActivateNonDefault(httpclient.FastOTPClient) + httpmock.RegisterResponder(method, path, func(req *http.Request) (*http.Response, error) { + // this can be used for method/path specific assertions + // if req.Method == http.MethodPost { + // body, err := io.ReadAll(req.Body) + // if err != nil { + // log.Fatal(err) + // } + // } + + var otpResponse *OTPResponse + err := json.NewDecoder(bytes.NewBuffer([]byte(response))).Decode(&otpResponse) + if err != nil { + log.Fatalln(err) + } + + return httpmock.NewJsonResponse(code, otpResponse) + }) + return httpmock.DeactivateAndReset +} + +func mockErrorHttpRequest(code int, method, path, response string) func() { + httpmock.ActivateNonDefault(httpclient.FastOTPClient) + httpmock.RegisterResponder(method, path, func(req *http.Request) (*http.Response, error) { + // this can be used for method/path specific assertions + // if req.Method == http.MethodPost { + // body, err := io.ReadAll(req.Body) + // if err != nil { + // log.Fatal(err) + // } + // } + + var ( + errorResponse ErrorResponse + ) + err := json.NewDecoder(bytes.NewBuffer([]byte(response))).Decode(&errorResponse) + if err != nil { + return httpmock.NewStringResponse(http.StatusInternalServerError, mockedError), nil + } + + return httpmock.NewJsonResponse(code, errorResponse) + }) + return httpmock.DeactivateAndReset +} diff --git a/go.mod b/go.mod index cb5c296..8fec9de 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/CeoFred/weavychat +module github.com/CeoFred/fast-otp go 1.21.2 diff --git a/interface.go b/interface.go index cae249b..414452b 100644 --- a/interface.go +++ b/interface.go @@ -1,4 +1,4 @@ -package weavychat +package fastotp import ( "context" @@ -9,5 +9,4 @@ import ( type HttpClient interface { Get(ctx context.Context, id string) (*http.Response, error) Post(ctx context.Context, endpoint string, payload interface{}) (*http.Response, error) - Delete(ctx context.Context, endpoint string, payload interface{}) (*http.Response, error) } diff --git a/lib/http.go b/lib/http.go index d16e926..f3dff99 100644 --- a/lib/http.go +++ b/lib/http.go @@ -47,34 +47,13 @@ func (c *APIClient) Post(ctx context.Context, endpoint string, payload interface return nil, err } - fmt.Println(url) req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(payloadBytes)) if err != nil { return nil, err } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) - - return c.client.Do(req) -} - -func (c *APIClient) Delete(ctx context.Context, endpoint string, payload interface{}) (*http.Response, error) { - url := c.baseURL + endpoint - - // Convert payload to JSON - payloadBytes, err := json.Marshal(payload) - if err != nil { - return nil, err - } - - req, err := http.NewRequestWithContext(ctx, http.MethodDelete, url, bytes.NewBuffer(payloadBytes)) - if err != nil { - return nil, err - } - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) + req.Header.Set("x-api-key", c.apiKey) return c.client.Do(req) } @@ -88,7 +67,7 @@ func (c *APIClient) Get(ctx context.Context, id string) (*http.Response, error) } req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", c.apiKey)) + req.Header.Set("x-api-key", c.apiKey) return c.client.Do(req) } diff --git a/mock.go b/mock.go index 692eb4d..812b423 100644 --- a/mock.go +++ b/mock.go @@ -1,4 +1,4 @@ -package weavychat +package fastotp import ( "context" diff --git a/type_test.go b/type_test.go new file mode 100644 index 0000000..d6c08e1 --- /dev/null +++ b/type_test.go @@ -0,0 +1,61 @@ +package fastotp + +import "testing" + +func TestOTPType_String(t *testing.T) { + tests := []struct { + name string + o OTPType + want string + }{ + { + name: "TestOTPType_String", + o: OTPTypeNumeric, + want: "numeric", + }, + { + name: "TestOTPType_String", + o: OTPTypeAlpha, + want: "alpha", + }, + { + name: "TestOTPType_String", + o: OTPTypeAlphaNumeric, + want: "alpha_numeric", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.o.String(); got != tt.want { + t.Errorf("OTPType.String() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOTPStatus_String(t *testing.T) { + tests := []struct { + name string + o OTPStatus + want string + }{ + { + name: "TestOTPStatus_String", + o: OTPStatusPending, + want: "pending", + }, + { + name: "TestOTPStatus_String", + o: OTPStatusValidated, + want: "validated", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.o.String(); got != tt.want { + t.Errorf("OTPStatus.String() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/types.go b/types.go index b700f41..7fb506d 100644 --- a/types.go +++ b/types.go @@ -1,16 +1,34 @@ -package weavychat +package fastotp type ( - AppType string + + // OTPType otp types + OTPType string + + // OTPStatus status of otp + OTPStatus string ) const ( - AppTypeChat AppType = "chat" - AppTypeFiles AppType = "files" - AppTypePosts AppType = "posts" + OTPTypeUnknown OTPType = "unknown" + // OTPTypeNumeric numbers only OTP + OTPTypeNumeric OTPType = "numeric" + // OTPTypeAlpha alphabet only OTP + OTPTypeAlpha OTPType = "alpha" + // OTPTypeAlphaNumeric combination of numbers and alphabet OTP + OTPTypeAlphaNumeric OTPType = "alpha_numeric" + + // OTPStatusPending pending otp status + OTPStatusPending OTPStatus = "pending" + // OTPStatusValidated validated otp status + OTPStatusValidated OTPStatus = "validated" ) // String returns the string value of OTPType -func (o AppType) String() string { +func (o OTPType) String() string { + return string(o) +} + +func (o OTPStatus) String() string { return string(o) } diff --git a/weavy.go b/weavy.go deleted file mode 100644 index b001c78..0000000 --- a/weavy.go +++ /dev/null @@ -1,310 +0,0 @@ -package weavychat - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "net/http" - - httpclient "github.com/CeoFred/weavychat/lib" -) - -type AccessTokenResponse struct { - ExpiresIn int `json:"expires_in"` - AccessToken string `json:"access_token"` -} -type WeavyServer struct { - ServerURL string - APIKey string - client HttpClient -} - -type Metadata map[string]string - -type ErrorResponse struct { - Type string `json:"type"` - Title string `json:"title"` - Status uint `json:"status"` -} - -type AppRequest struct { - ID int `json:"id"` - Type AppType `json:"type"` - UID string `json:"uid"` - DisplayName string `json:"display_name"` - Metadata Metadata `json:"metadata"` - Tags []string `json:"tags"` -} - -type WeavyApp struct { - AppRequest - Name string `json:"name"` - Description string `json:"description"` - ArchiveURL string `json:"archive_url"` - AvatarURL string `json:"avatar_url"` - CreatedAt string `json:"created_at"` - CreatedByID int `json:"created_by_id"` - ModifiedAt string `json:"modified_at"` - ModifiedByID int `json:"modified_by_id"` - IsStarred bool `json:"is_starred"` - IsSubscribed bool `json:"is_subscribed"` - IsTrashed bool `json:"is_trashed"` -} - -type User struct { - UID string `json:"uid"` - Email string `json:"email"` - GivenName string `json:"given_name"` - MiddleName string `json:"middle_name"` - Name string `json:"name"` - FamilyName string `json:"family_name"` - Nickname string `json:"nickname"` - PhoneNumber string `json:"phone_number"` - Comment string `json:"comment"` - Picture string `json:"picture"` - Directory string `json:"directory"` - Metadata Metadata `json:"metadata"` - Tags []string `json:"tags"` - IsSuspended bool `json:"is_suspended"` -} - -type UserProfile struct { - ID int `json:"id"` - UID string `json:"uid"` - DisplayName string `json:"display_name"` - Email string `json:"email"` - GivenName string `json:"given_name"` - MiddleName string `json:"middle_name"` - Name string `json:"name"` - FamilyName string `json:"family_name"` - Nickname string `json:"nickname"` - PhoneNumber string `json:"phone_number"` - Comment string `json:"comment"` - Directory struct { - ID int `json:"id"` - Name string `json:"name"` - MemberCount int `json:"member_count"` - } `json:"directory"` - DirectoryID int `json:"directory_id"` - Picture struct { - ID int `json:"id"` - Name string `json:"name"` - MediaType string `json:"media_type"` - Width int `json:"width"` - Height int `json:"height"` - Size int `json:"size"` - ThumbnailURL string `json:"thumbnail_url"` - } `json:"picture"` - PictureID int `json:"picture_id"` - AvatarURL string `json:"avatar_url"` - Metadata map[string]string `json:"metadata"` - Tags []string `json:"tags"` - Presence string `json:"presence"` - CreatedAt string `json:"created_at"` - ModifiedAt string `json:"modified_at"` - IsSuspended bool `json:"is_suspended"` - IsTrashed bool `json:"is_trashed"` -} - -func NewWeavyServer(server, apiKey string) *WeavyServer { - return &WeavyServer{ - ServerURL: server, - APIKey: apiKey, - client: httpclient.NewAPIClient(server+"/api", apiKey), - } -} - -func (s *WeavyServer) NewApp(ctx context.Context, a *AppRequest) (*WeavyApp, error) { - if a.UID == "" { - return nil, errors.New("app uid is required") - } - - if a.Type == "" { - return nil, errors.New("app Type is required") - } - - resp, err := s.client.Post(ctx, "/apps", a) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - var errorResponse ErrorResponse - if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { - return nil, err - } - return nil, fmt.Errorf("API error: Type: %s, Title: %s", errorResponse.Type, errorResponse.Title) - } - - var newAppResponse WeavyApp - if err := json.NewDecoder(resp.Body).Decode(&newAppResponse); err != nil { - return nil, err - } - - return &newAppResponse, nil -} - -func (s *WeavyServer) GetApp(ctx context.Context, id string) (*WeavyApp, error) { - if id == "" { - return nil, errors.New("app id is required") - } - - resp, err := s.client.Get(ctx, "/apps/"+id) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var errorResponse ErrorResponse - if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { - return nil, err - } - - return nil, fmt.Errorf("API error: Type: %s, Title: %s", errorResponse.Type, errorResponse.Title) - } - - var newAppResponse WeavyApp - if err := json.NewDecoder(resp.Body).Decode(&newAppResponse); err != nil { - return nil, err - } - - return &newAppResponse, nil -} - -func (s *WeavyServer) NewUser(ctx context.Context, a *User) (*UserProfile, error) { - if a.UID == "" { - return nil, errors.New("user uid is required") - } - - resp, err := s.client.Post(ctx, "/users", a) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - var errorResponse ErrorResponse - if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { - return nil, err - } - - return nil, fmt.Errorf("API error: Type: %s, Title: %s", errorResponse.Type, errorResponse.Title) - } - - var newUserResponse UserProfile - if err := json.NewDecoder(resp.Body).Decode(&newUserResponse); err != nil { - return nil, err - } - - return &newUserResponse, nil -} - -func (s *WeavyServer) AddUserToApp(ctx context.Context, app_id int, users []uint) error { - if app_id == 0 { - return errors.New("app_id is required") - } - - resp, err := s.client.Post(ctx, fmt.Sprintf("/apps/%d/members", app_id), users) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusNoContent { - var errorResponse ErrorResponse - if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { - return err - } - - return fmt.Errorf("API error: Type: %s, Title: %s", errorResponse.Type, errorResponse.Title) - } - - return nil -} - -func (s *WeavyServer) RemoveUserFromApp(ctx context.Context, app_id int, users []uint) error { - if app_id == 0 { - return errors.New("app_id is required") - } - - resp, err := s.client.Delete(ctx, fmt.Sprintf("/apps/%d/members", app_id), users) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var errorResponse ErrorResponse - if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { - return err - } - - return fmt.Errorf("API error: Type: %s, Title: %s", errorResponse.Type, errorResponse.Title) - } - - return nil -} - -func (s *WeavyServer) AppInit(ctx context.Context, app *WeavyApp) error { - - if app.UID == "" { - return errors.New("app uid is required") - } - if app.Type == "" { - return errors.New("app type is required") - } - requestBody := struct{ App *WeavyApp }{ - App: app, - } - - resp, err := s.client.Post(ctx, "/apps/init", requestBody) - if err != nil { - return err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var errorResponse ErrorResponse - if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { - return err - } - - return fmt.Errorf("API error: Type: %s, Title: %s", errorResponse.Type, errorResponse.Title) - } - return nil - -} - -// Issues an access token for a user. If a user with the with the specified uid does not exists, this endpoint first creates the user and then issues an access_token -func (s *WeavyServer) GetAccessToken(ctx context.Context, uid string, ttl int) (*AccessTokenResponse, error) { - - requestBody := struct { - ExpiresIn int `json:"expires_in"` - }{ - ExpiresIn: ttl, - } - resp, err := s.client.Post(ctx, fmt.Sprintf("/users/%s/tokens", uid), requestBody) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - var errorResponse ErrorResponse - if err := json.NewDecoder(resp.Body).Decode(&errorResponse); err != nil { - return nil, err - } - - return nil, fmt.Errorf("API error: Type: %s, Title: %s", errorResponse.Type, errorResponse.Title) - } - var accessToken AccessTokenResponse - if err := json.NewDecoder(resp.Body).Decode(&accessToken); err != nil { - return nil, err - } - - return &accessToken, nil - -}