package familyHub import ( "FamilyHub/src/config" "FamilyHub/src/domain" "FamilyHub/src/utils" "bytes" "context" "encoding/json" "errors" "fmt" "io" "log" "net/http" "strconv" "time" ) var errUserNotFound = errors.New("user not found") func NewApiClient(config config.Config) (*HTTPClient, error) { return &HTTPClient{ config: config, client: &http.Client{ Timeout: 60 * time.Second, }, }, nil } func (c *HTTPClient) SendReceipt(ctx context.Context, payload domain.AddReceiptRequest) error { requestBody := map[string]any{ "receipt_number": payload.Number, "receipt_date": payload.Date, } if payload.FamilyID != nil { requestBody["family_id"] = *payload.FamilyID } if payload.CreatedBy != nil { requestBody["created_by"] = *payload.CreatedBy } if payload.Type != nil { requestBody["type"] = *payload.Type } if payload.Category != nil { requestBody["category"] = *payload.Category } if payload.Description != nil { requestBody["description"] = *payload.Description } body, err := json.Marshal(requestBody) if err != nil { return err } req, err := http.NewRequestWithContext( ctx, http.MethodPost, c.config.APIHost+c.config.APIPort+"/api/v1/transactions", bytes.NewReader(body), ) if err != nil { return err } req.Header.Set("Content-Type", "application/json") responseBody, statusCode, err := c.doRequest(req, "familyhub_api.transactions.create", body) if err != nil { return err } if statusCode >= 300 { return fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512)) } return nil } func (c *HTTPClient) EnsureUser(ctx context.Context, payload domain.CreateUserRequest) error { registered, err := c.IsUserRegistered(ctx, payload.TelegramID) if err != nil { return err } if registered { return nil } return c.RegisterUser(ctx, payload) } func (c *HTTPClient) IsUserRegistered(ctx context.Context, telegramID int64) (bool, error) { _, err := c.GetUserByTelegramID(ctx, telegramID) if err == nil { return true, nil } if errors.Is(err, errUserNotFound) { return false, nil } return false, err } func (c *HTTPClient) RegisterUser(ctx context.Context, payload domain.CreateUserRequest) error { body, err := json.Marshal(payload) if err != nil { return err } req, err := http.NewRequestWithContext( ctx, http.MethodPost, c.config.APIHost+c.config.APIPort+"/api/v1/users", bytes.NewReader(body), ) if err != nil { return err } req.Header.Set("Content-Type", "application/json") responseBody, statusCode, err := c.doRequest(req, "familyhub_api.users.create", body) if err != nil { return err } if statusCode >= 300 { return fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512)) } return nil } func (c *HTTPClient) GetUserByTelegramID(ctx context.Context, telegramID int64) (*domain.UserResponse, error) { req, err := http.NewRequestWithContext( ctx, http.MethodGet, c.config.APIHost+c.config.APIPort+"/api/v1/users/by-telegram/"+strconv.FormatInt(telegramID, 10), nil, ) if err != nil { return nil, err } responseBody, statusCode, err := c.doRequest(req, "familyhub_api.users.by_telegram", nil) if err != nil { return nil, err } if statusCode == http.StatusNotFound { return nil, errUserNotFound } if statusCode >= 300 { return nil, fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512)) } var user domain.UserResponse if err := json.Unmarshal(responseBody, &user); err != nil { return nil, err } return &user, nil } func (c *HTTPClient) CreateFamily(ctx context.Context, payload domain.CreateFamilyRequest) error { body, err := json.Marshal(payload) if err != nil { return err } req, err := http.NewRequestWithContext( ctx, http.MethodPost, c.config.APIHost+c.config.APIPort+"/api/v1/families", bytes.NewReader(body), ) if err != nil { return err } req.Header.Set("Content-Type", "application/json") responseBody, statusCode, err := c.doRequest(req, "familyhub_api.families.create", body) if err != nil { return err } if statusCode >= 300 { return fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512)) } return nil } func (c *HTTPClient) doRequest(req *http.Request, service string, requestBody []byte) ([]byte, int, error) { log.Printf( "external request: service=%s method=%s url=%s body=%q", service, req.Method, req.URL.String(), utils.TruncateForLog(string(requestBody), utils.DefaultLogValueLimit), ) resp, err := c.client.Do(req) if err != nil { log.Printf("external response: service=%s method=%s url=%s err=%v", service, req.Method, req.URL.String(), err) return nil, 0, err } defer resp.Body.Close() responseBody, readErr := io.ReadAll(resp.Body) if readErr != nil { log.Printf("external response: service=%s method=%s url=%s status=%d read_err=%v", service, req.Method, req.URL.String(), resp.StatusCode, readErr) return nil, resp.StatusCode, readErr } log.Printf( "external response: service=%s method=%s url=%s status=%d body=%q", service, req.Method, req.URL.String(), resp.StatusCode, utils.TruncateForLog(string(responseBody), utils.DefaultLogValueLimit), ) return responseBody, resp.StatusCode, nil }