Added structured logging across services and repositories. Updated SQL queries to use parameterized placeholders for better readability and security. Enhanced error handling for external service communication.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"FamilyHub/src/domain"
|
"FamilyHub/src/domain"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -73,13 +74,13 @@ func (router *TransactionsRouter) Create(c *gin.Context) {
|
|||||||
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Message: err.Error()})
|
c.JSON(http.StatusBadRequest, dto.ErrorResponse{Message: err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Printf("%+v\n", input)
|
||||||
transaction, err := router.creationService.Create(c.Request.Context(), input)
|
transaction, err := router.creationService.Create(c.Request.Context(), input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handleTransactionError(c, err)
|
handleTransactionError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Printf("%+v\n", transaction)
|
||||||
c.JSON(http.StatusCreated, dto.TransactionToResponse(transaction))
|
c.JSON(http.StatusCreated, dto.TransactionToResponse(transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import (
|
|||||||
"FamilyHub/src/domain"
|
"FamilyHub/src/domain"
|
||||||
"FamilyHub/src/integrations/receiptProvider"
|
"FamilyHub/src/integrations/receiptProvider"
|
||||||
"FamilyHub/src/repositories"
|
"FamilyHub/src/repositories"
|
||||||
|
"FamilyHub/src/utils"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,18 +37,23 @@ func (s *receiptService) AddReceipt(
|
|||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
req domain.AddReceiptRequest,
|
req domain.AddReceiptRequest,
|
||||||
) (*domain.Receipt, error) {
|
) (*domain.Receipt, error) {
|
||||||
|
log.Printf("receipt add request: payload=%s", utils.ToLogJSON(req))
|
||||||
|
|
||||||
receipt, err := s.provider.GetReceipt(ctx, req.Date, req.Number)
|
receipt, err := s.provider.GetReceipt(ctx, req.Date, req.Number)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("receipt add failed: err=%v payload=%s", err, utils.ToLogJSON(req))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
receiptID, err := s.repo.Create(ctx, receipt)
|
receiptID, err := s.repo.Create(ctx, receipt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("receipt persist failed: err=%v receipt=%s", err, utils.ToLogJSON(receipt))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
receipt.ID = int(receiptID)
|
receipt.ID = int(receiptID)
|
||||||
|
|
||||||
if !s.shouldCreateTransaction(req) {
|
if !s.shouldCreateTransaction(req) {
|
||||||
|
log.Printf("receipt add response: payload=%s", utils.ToLogJSON(receipt))
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +66,7 @@ func (s *receiptService) AddReceipt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
receipt.TransactionID = &transaction.ID
|
receipt.TransactionID = &transaction.ID
|
||||||
|
log.Printf("receipt add response: payload=%s", utils.ToLogJSON(receipt))
|
||||||
return receipt, nil
|
return receipt, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,11 +102,14 @@ func (s *receiptService) createTransactionForReceipt(
|
|||||||
CreatedBy: *req.CreatedBy,
|
CreatedBy: *req.CreatedBy,
|
||||||
ReceiptID: &receiptID,
|
ReceiptID: &receiptID,
|
||||||
}
|
}
|
||||||
|
log.Printf("%+v\n", transaction)
|
||||||
|
log.Printf("receipt transaction create request: payload=%s", utils.ToLogJSON(transaction))
|
||||||
if err := s.transactionRepo.Create(ctx, transaction); err != nil {
|
if err := s.transactionRepo.Create(ctx, transaction); err != nil {
|
||||||
|
log.Printf("receipt transaction create failed: err=%v payload=%s", err, utils.ToLogJSON(transaction))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("receipt transaction create response: payload=%s", utils.ToLogJSON(transaction))
|
||||||
return transaction, nil
|
return transaction, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package services
|
|||||||
import (
|
import (
|
||||||
"FamilyHub/src/domain"
|
"FamilyHub/src/domain"
|
||||||
"FamilyHub/src/repositories"
|
"FamilyHub/src/repositories"
|
||||||
|
"FamilyHub/src/utils"
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,7 +40,10 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *transactionService) Create(ctx context.Context, req domain.CreateTransactionRequest) (*domain.Transaction, error) {
|
func (s *transactionService) Create(ctx context.Context, req domain.CreateTransactionRequest) (*domain.Transaction, error) {
|
||||||
|
log.Printf("transaction create request: payload=%s", utils.ToLogJSON(req))
|
||||||
|
|
||||||
if strings.TrimSpace(req.Type) == "" || strings.TrimSpace(req.Category) == "" {
|
if strings.TrimSpace(req.Type) == "" || strings.TrimSpace(req.Category) == "" {
|
||||||
|
log.Printf("transaction create failed: err=%v payload=%s", ErrInvalidTransaction, utils.ToLogJSON(req))
|
||||||
return nil, ErrInvalidTransaction
|
return nil, ErrInvalidTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,8 +60,10 @@ func (s *transactionService) Create(ctx context.Context, req domain.CreateTransa
|
|||||||
|
|
||||||
if err := s.repo.Create(ctx, transaction); err != nil {
|
if err := s.repo.Create(ctx, transaction); err != nil {
|
||||||
if errors.Is(err, repositories.ErrReceiptNotFound) {
|
if errors.Is(err, repositories.ErrReceiptNotFound) {
|
||||||
|
log.Printf("transaction create failed: err=%v payload=%s", ErrReceiptNotFound, utils.ToLogJSON(req))
|
||||||
return nil, ErrReceiptNotFound
|
return nil, ErrReceiptNotFound
|
||||||
}
|
}
|
||||||
|
log.Printf("transaction create failed: err=%v payload=%s", err, utils.ToLogJSON(req))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +79,7 @@ func (s *transactionService) Create(ctx context.Context, req domain.CreateTransa
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf("transaction create response: payload=%s", utils.ToLogJSON(transaction))
|
||||||
return transaction, nil
|
return transaction, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package familyHub
|
|||||||
import (
|
import (
|
||||||
"FamilyHub/src/config"
|
"FamilyHub/src/config"
|
||||||
"FamilyHub/src/domain"
|
"FamilyHub/src/domain"
|
||||||
|
"FamilyHub/src/utils"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
@@ -63,14 +66,13 @@ func (c *HTTPClient) SendReceipt(ctx context.Context, payload domain.AddReceiptR
|
|||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
responseBody, statusCode, err := c.doRequest(req, "familyhub_api.transactions.create", body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode >= 300 {
|
if statusCode >= 300 {
|
||||||
return fmt.Errorf("api error: status %d", resp.StatusCode)
|
return fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -120,14 +122,13 @@ func (c *HTTPClient) RegisterUser(ctx context.Context, payload domain.CreateUser
|
|||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
responseBody, statusCode, err := c.doRequest(req, "familyhub_api.users.create", body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode >= 300 {
|
if statusCode >= 300 {
|
||||||
return fmt.Errorf("api error: status %d", resp.StatusCode)
|
return fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -144,22 +145,21 @@ func (c *HTTPClient) GetUserByTelegramID(ctx context.Context, telegramID int64)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
responseBody, statusCode, err := c.doRequest(req, "familyhub_api.users.by_telegram", nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode == http.StatusNotFound {
|
if statusCode == http.StatusNotFound {
|
||||||
return nil, errUserNotFound
|
return nil, errUserNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
if resp.StatusCode >= 300 {
|
if statusCode >= 300 {
|
||||||
return nil, fmt.Errorf("api error: status %d", resp.StatusCode)
|
return nil, fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512))
|
||||||
}
|
}
|
||||||
|
|
||||||
var user domain.UserResponse
|
var user domain.UserResponse
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
|
if err := json.Unmarshal(responseBody, &user); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,15 +184,48 @@ func (c *HTTPClient) CreateFamily(ctx context.Context, payload domain.CreateFami
|
|||||||
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := c.client.Do(req)
|
responseBody, statusCode, err := c.doRequest(req, "familyhub_api.families.create", body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
if resp.StatusCode >= 300 {
|
if statusCode >= 300 {
|
||||||
return fmt.Errorf("api error: status %d", resp.StatusCode)
|
return fmt.Errorf("api error: status %d body %s", statusCode, utils.TruncateForLog(string(responseBody), 512))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ package familyHub
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"FamilyHub/src/config"
|
"FamilyHub/src/config"
|
||||||
|
"FamilyHub/src/utils"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -18,19 +23,42 @@ func NewBotClient(config config.Config) (*HTTPClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *HTTPClient) SendMessage(ctx context.Context, chatId int64, message string) error {
|
func (c *HTTPClient) SendMessage(ctx context.Context, chatId int64, message string) error {
|
||||||
|
url := c.config.TelegramApi + "/bot" + c.config.BotToken + "/sendMessage?chat_id=" + strconv.FormatInt(chatId, 10) + "&text=" + message
|
||||||
req, err := http.NewRequestWithContext(
|
req, err := http.NewRequestWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
http.MethodGet,
|
http.MethodGet,
|
||||||
c.config.TelegramApi+"/bot"+c.config.BotToken+"/sendMessage?chat_id="+strconv.FormatInt(chatId, 10)+"&text="+message,
|
url,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
logURL := strings.ReplaceAll(req.URL.String(), c.config.BotToken, "***")
|
||||||
|
log.Printf(
|
||||||
|
"external request: service=telegram_bot.send_message method=%s url=%s body=%q",
|
||||||
|
http.MethodGet,
|
||||||
|
logURL,
|
||||||
|
utils.TruncateForLog(fmt.Sprintf("chat_id=%d&text=%s", chatId, message), utils.DefaultLogValueLimit),
|
||||||
|
)
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("external response: service=telegram_bot.send_message method=%s url=%s err=%v", http.MethodGet, logURL, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
responseBody, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
log.Printf("external response: service=telegram_bot.send_message method=%s url=%s status=%d read_err=%v", http.MethodGet, logURL, resp.StatusCode, readErr)
|
||||||
|
return readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"external response: service=telegram_bot.send_message method=%s url=%s status=%d body=%q",
|
||||||
|
http.MethodGet,
|
||||||
|
logURL,
|
||||||
|
resp.StatusCode,
|
||||||
|
utils.TruncateForLog(string(responseBody), utils.DefaultLogValueLimit),
|
||||||
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package ocr
|
package ocr
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"FamilyHub/src/utils"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
vision "cloud.google.com/go/vision/apiv1"
|
vision "cloud.google.com/go/vision/apiv1"
|
||||||
)
|
)
|
||||||
@@ -30,19 +32,29 @@ func (g *GoogleOCR) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GoogleOCR) Recognize(ctx context.Context, image []byte) (string, error) {
|
func (g *GoogleOCR) Recognize(ctx context.Context, image []byte) (string, error) {
|
||||||
|
log.Printf("external request: service=google_ocr.detect_text image_size_bytes=%d", len(image))
|
||||||
|
|
||||||
img, err := vision.NewImageFromReader(bytes.NewReader(image))
|
img, err := vision.NewImageFromReader(bytes.NewReader(image))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("external response: service=google_ocr.detect_text err=%v", err)
|
||||||
return "", fmt.Errorf("load image: %w", err)
|
return "", fmt.Errorf("load image: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
annotations, err := g.client.DetectTexts(ctx, img, nil, 1)
|
annotations, err := g.client.DetectTexts(ctx, img, nil, 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Printf("external response: service=google_ocr.detect_text err=%v", err)
|
||||||
return "", fmt.Errorf("detect text: %w", err)
|
return "", fmt.Errorf("detect text: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(annotations) == 0 {
|
if len(annotations) == 0 {
|
||||||
|
log.Printf("external response: service=google_ocr.detect_text result=%q", "")
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Printf(
|
||||||
|
"external response: service=google_ocr.detect_text result=%q annotations=%d",
|
||||||
|
utils.TruncateForLog(annotations[0].Description, utils.DefaultLogValueLimit),
|
||||||
|
len(annotations),
|
||||||
|
)
|
||||||
return annotations[0].Description, nil
|
return annotations[0].Description, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ func (s *receiptProvider) GetReceipt(
|
|||||||
var receipt domain.Receipt
|
var receipt domain.Receipt
|
||||||
|
|
||||||
body, contentType := buildMultipartBody(date, number)
|
body, contentType := buildMultipartBody(date, number)
|
||||||
|
requestBody := body.String()
|
||||||
|
log.Printf(
|
||||||
|
"external request: service=receipt_provider method=%s url=%s content_type=%s body=%q",
|
||||||
|
http.MethodPost,
|
||||||
|
url,
|
||||||
|
contentType,
|
||||||
|
utils.TruncateForLog(requestBody, utils.DefaultLogValueLimit),
|
||||||
|
)
|
||||||
httpReq, err := http.NewRequestWithContext(
|
httpReq, err := http.NewRequestWithContext(
|
||||||
ctx,
|
ctx,
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
@@ -79,18 +87,27 @@ func (s *receiptProvider) GetReceipt(
|
|||||||
|
|
||||||
resp, err := s.client.Do(httpReq)
|
resp, err := s.client.Do(httpReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
log.Printf("external response: service=receipt_provider method=%s url=%s err=%v", http.MethodPost, url, err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
responseBody, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
log.Printf("failed to read external service response body: %v", readErr)
|
||||||
|
return nil, readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyText := strings.TrimSpace(string(responseBody))
|
||||||
|
log.Printf(
|
||||||
|
"external response: service=receipt_provider method=%s url=%s status=%d body=%q",
|
||||||
|
http.MethodPost,
|
||||||
|
url,
|
||||||
|
resp.StatusCode,
|
||||||
|
utils.TruncateForLog(bodyText, utils.DefaultLogValueLimit),
|
||||||
|
)
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
responseBody, readErr := io.ReadAll(io.LimitReader(resp.Body, 4096))
|
|
||||||
if readErr != nil {
|
|
||||||
log.Printf("failed to read external service error body: %v", readErr)
|
|
||||||
}
|
|
||||||
bodyText := strings.TrimSpace(string(responseBody))
|
|
||||||
log.Printf("external service returned %d body=%q", resp.StatusCode, bodyText)
|
|
||||||
return nil, &ExternalServiceError{
|
return nil, &ExternalServiceError{
|
||||||
StatusCode: resp.StatusCode,
|
StatusCode: resp.StatusCode,
|
||||||
Body: bodyText,
|
Body: bodyText,
|
||||||
@@ -100,8 +117,8 @@ func (s *receiptProvider) GetReceipt(
|
|||||||
var raw struct {
|
var raw struct {
|
||||||
Message map[string]interface{} `json:"message"`
|
Message map[string]interface{} `json:"message"`
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&raw); err != nil {
|
if err := json.Unmarshal(responseBody, &raw); err != nil {
|
||||||
log.Printf("external service returned %s\n", err.Error())
|
log.Printf("external service returned invalid json: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +129,7 @@ func (s *receiptProvider) GetReceipt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if receipt.IssuedAtRaw == "" {
|
if receipt.IssuedAtRaw == "" {
|
||||||
|
log.Printf("external response parse failed: service=receipt_provider err=%v date=%s number=%s", ErrReceiptNotFound, date, number)
|
||||||
return nil, ErrReceiptNotFound
|
return nil, ErrReceiptNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"errors"
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
"FamilyHub/src/domain"
|
"FamilyHub/src/domain"
|
||||||
)
|
)
|
||||||
@@ -25,29 +26,63 @@ func NewReceiptsSQLRepository(db *sql.DB) *ReceiptsSQLRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Receipt) (int64, error) {
|
func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Receipt) (int64, error) {
|
||||||
|
log.Printf("%+v\n", receipt)
|
||||||
|
|
||||||
tx, err := r.db.BeginTx(ctx, nil)
|
tx, err := r.db.BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
if receipt.ReceiptNumber != receipt.UI {
|
if receipt.ReceiptNumber != receipt.UI {
|
||||||
receipt.ReceiptNumber = receipt.UI
|
receipt.ReceiptNumber = receipt.UI
|
||||||
}
|
}
|
||||||
res, err := tx.ExecContext(ctx, `
|
|
||||||
|
log.Println("First query")
|
||||||
|
|
||||||
|
query := `
|
||||||
INSERT INTO receipts (
|
INSERT INTO receipts (
|
||||||
transaction_id, receipt_number, ui, status, issued_at,
|
transaction_id,
|
||||||
total_amount, payment_amount, cash_amount,
|
receipt_number,
|
||||||
another_amount, clearing_amount, margin,
|
ui,
|
||||||
currency, payment_type,
|
status,
|
||||||
cashbox_number, cashier,
|
issued_at,
|
||||||
name_spd, name_to, name_np, type_np,
|
total_amount,
|
||||||
street_to, house_to,
|
payment_amount,
|
||||||
kod_soato, oblast_soato, rayon_soato, selsovet_soato,
|
cash_amount,
|
||||||
doc_num, skno_number, unp,
|
another_amount,
|
||||||
|
clearing_amount,
|
||||||
|
margin,
|
||||||
|
currency,
|
||||||
|
payment_type,
|
||||||
|
cashbox_number,
|
||||||
|
cashier,
|
||||||
|
name_spd,
|
||||||
|
name_to,
|
||||||
|
name_np,
|
||||||
|
type_np,
|
||||||
|
street_to,
|
||||||
|
house_to,
|
||||||
|
kod_soato,
|
||||||
|
oblast_soato,
|
||||||
|
rayon_soato,
|
||||||
|
selsovet_soato,
|
||||||
|
doc_num,
|
||||||
|
skno_number,
|
||||||
|
unp,
|
||||||
success
|
success
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
)
|
||||||
`,
|
VALUES (
|
||||||
|
$1, $2, $3, $4, $5,
|
||||||
|
$6, $7, $8, $9, $10,
|
||||||
|
$11, $12, $13, $14, $15,
|
||||||
|
$16, $17, $18, $19, $20,
|
||||||
|
$21, $22, $23, $24, $25,
|
||||||
|
$26, $27, $28, $29
|
||||||
|
)
|
||||||
|
RETURNING id;
|
||||||
|
`
|
||||||
|
args := []any{
|
||||||
receipt.TransactionID,
|
receipt.TransactionID,
|
||||||
receipt.ReceiptNumber,
|
receipt.ReceiptNumber,
|
||||||
receipt.UI,
|
receipt.UI,
|
||||||
@@ -85,16 +120,19 @@ func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Rece
|
|||||||
receipt.UNP,
|
receipt.UNP,
|
||||||
|
|
||||||
receipt.Success,
|
receipt.Success,
|
||||||
)
|
}
|
||||||
|
|
||||||
|
log.Printf("SQL: %s", query)
|
||||||
|
log.Printf("ARGS: %+v", args)
|
||||||
|
|
||||||
|
var receiptID int64
|
||||||
|
|
||||||
|
err = tx.QueryRowContext(ctx, query, args...).Scan(&receiptID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
receiptID, err := res.LastInsertId()
|
log.Println("Second query")
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
stmt, err := tx.PrepareContext(ctx, `
|
stmt, err := tx.PrepareContext(ctx, `
|
||||||
INSERT INTO positions (
|
INSERT INTO positions (
|
||||||
@@ -109,7 +147,11 @@ func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Rece
|
|||||||
tag,
|
tag,
|
||||||
marking_code,
|
marking_code,
|
||||||
ukz_code
|
ukz_code
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
)
|
||||||
|
VALUES (
|
||||||
|
$1, $2, $3, $4, $5,
|
||||||
|
$6, $7, $8, $9, $10, $11
|
||||||
|
)
|
||||||
`)
|
`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -117,7 +159,8 @@ func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Rece
|
|||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
|
|
||||||
for _, p := range receipt.Positions {
|
for _, p := range receipt.Positions {
|
||||||
_, err = stmt.ExecContext(ctx,
|
_, err = stmt.ExecContext(
|
||||||
|
ctx,
|
||||||
receiptID,
|
receiptID,
|
||||||
p.SectionNumber,
|
p.SectionNumber,
|
||||||
p.GTINCode,
|
p.GTINCode,
|
||||||
@@ -135,7 +178,11 @@ func (r *ReceiptsSQLRepository) Create(ctx context.Context, receipt *domain.Rece
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return receiptID, tx.Commit()
|
if err = tx.Commit(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return receiptID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.Receipt, error) {
|
func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.Receipt, error) {
|
||||||
@@ -157,7 +204,7 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
|
|||||||
doc_num, skno_number, unp,
|
doc_num, skno_number, unp,
|
||||||
success
|
success
|
||||||
FROM receipts
|
FROM receipts
|
||||||
WHERE id = ?
|
WHERE id = $1
|
||||||
`, id).Scan(
|
`, id).Scan(
|
||||||
&receipt.ID,
|
&receipt.ID,
|
||||||
&receipt.TransactionID,
|
&receipt.TransactionID,
|
||||||
@@ -213,7 +260,7 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
|
|||||||
product_count, amount,
|
product_count, amount,
|
||||||
discount, surcharge,
|
discount, surcharge,
|
||||||
tag, marking_code, ukz_code
|
tag, marking_code, ukz_code
|
||||||
FROM positions WHERE receipt_id = ?
|
FROM positions WHERE receipt_id = $1
|
||||||
`, id)
|
`, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -247,10 +294,16 @@ func (r *ReceiptsSQLRepository) GetByID(ctx context.Context, id int64) (*domain.
|
|||||||
func (r *ReceiptsSQLRepository) GetAll(ctx context.Context, limit, offset int) ([]*domain.Receipt, error) {
|
func (r *ReceiptsSQLRepository) GetAll(ctx context.Context, limit, offset int) ([]*domain.Receipt, error) {
|
||||||
|
|
||||||
rows, err := r.db.QueryContext(ctx, `
|
rows, err := r.db.QueryContext(ctx, `
|
||||||
SELECT id, transaction_id, receipt_number, issued_at, total_amount, currency
|
SELECT
|
||||||
|
id,
|
||||||
|
transaction_id,
|
||||||
|
receipt_number,
|
||||||
|
issued_at,
|
||||||
|
total_amount,
|
||||||
|
currency
|
||||||
FROM receipts
|
FROM receipts
|
||||||
ORDER BY issued_at DESC
|
ORDER BY issued_at DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT $1 OFFSET $2
|
||||||
`, limit, offset)
|
`, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -261,6 +314,7 @@ func (r *ReceiptsSQLRepository) GetAll(ctx context.Context, limit, offset int) (
|
|||||||
|
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var rct domain.Receipt
|
var rct domain.Receipt
|
||||||
|
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&rct.ID,
|
&rct.ID,
|
||||||
&rct.TransactionID,
|
&rct.TransactionID,
|
||||||
@@ -271,9 +325,14 @@ func (r *ReceiptsSQLRepository) GetAll(ctx context.Context, limit, offset int) (
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
receipts = append(receipts, &rct)
|
receipts = append(receipts, &rct)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return receipts, nil
|
return receipts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,11 +346,11 @@ func (r *ReceiptsSQLRepository) Update(ctx context.Context, receipt *domain.Rece
|
|||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `
|
_, err = tx.ExecContext(ctx, `
|
||||||
UPDATE receipts SET
|
UPDATE receipts SET
|
||||||
transaction_id = ?,
|
transaction_id = $1,
|
||||||
issued_at = ?,
|
issued_at = $2,
|
||||||
total_amount = ?,
|
total_amount = $3,
|
||||||
currency = ?
|
currency = $4
|
||||||
WHERE id = ?
|
WHERE id = $5
|
||||||
`,
|
`,
|
||||||
receipt.TransactionID,
|
receipt.TransactionID,
|
||||||
receipt.IssuedAt,
|
receipt.IssuedAt,
|
||||||
@@ -303,7 +362,7 @@ func (r *ReceiptsSQLRepository) Update(ctx context.Context, receipt *domain.Rece
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = tx.ExecContext(ctx, `DELETE FROM positions WHERE receipt_id = ?`, receipt.ID)
|
_, err = tx.ExecContext(ctx, `DELETE FROM positions WHERE receipt_id = $1`, receipt.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -312,7 +371,7 @@ func (r *ReceiptsSQLRepository) Update(ctx context.Context, receipt *domain.Rece
|
|||||||
_, err = tx.ExecContext(ctx, `
|
_, err = tx.ExecContext(ctx, `
|
||||||
INSERT INTO positions (
|
INSERT INTO positions (
|
||||||
receipt_id, product_name, product_count, amount
|
receipt_id, product_name, product_count, amount
|
||||||
) VALUES (?, ?, ?, ?)
|
) VALUES ($1, $2, $3, $4)
|
||||||
`, receipt.ID, p.ProductName, p.ProductCount, p.Amount)
|
`, receipt.ID, p.ProductName, p.ProductCount, p.Amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -324,7 +383,7 @@ func (r *ReceiptsSQLRepository) Update(ctx context.Context, receipt *domain.Rece
|
|||||||
|
|
||||||
func (r *ReceiptsSQLRepository) Delete(ctx context.Context, id int64) error {
|
func (r *ReceiptsSQLRepository) Delete(ctx context.Context, id int64) error {
|
||||||
_, err := r.db.ExecContext(ctx,
|
_, err := r.db.ExecContext(ctx,
|
||||||
`DELETE FROM receipts WHERE id = ?`,
|
`DELETE FROM receipts WHERE id = $1`,
|
||||||
id,
|
id,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DefaultLogValueLimit = 4096
|
||||||
|
|
||||||
|
func ToLogJSON(value any) string {
|
||||||
|
if value == nil {
|
||||||
|
return "null"
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
return TruncateForLog(fmt.Sprintf("%+v", value), DefaultLogValueLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
return TruncateForLog(string(data), DefaultLogValueLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TruncateForLog(value string, limit int) string {
|
||||||
|
if limit <= 0 || len(value) <= limit {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
return value[:limit] + "...(truncated)"
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"transaction_id": null,
|
||||||
|
"STATUS": 1,
|
||||||
|
"another_amount": 0,
|
||||||
|
"cash_amount": 0,
|
||||||
|
"cashbox_number": 119091676,
|
||||||
|
"cashier": "Старший кассир КСО",
|
||||||
|
"clearing_amount": 190.06,
|
||||||
|
"currency": "BYN",
|
||||||
|
"doc_num": "39972",
|
||||||
|
"house_to": "11",
|
||||||
|
"kod_soato": "5000000000",
|
||||||
|
"margin": 0,
|
||||||
|
"name_np": "Минск",
|
||||||
|
"name_spd": "Общество с ограниченной ответственностью \"ГРИНрозница\"",
|
||||||
|
"name_to": "Магазин \"ГРИН-5\"",
|
||||||
|
"oblast_soato": null,
|
||||||
|
"rayon_soato": null,
|
||||||
|
"selsovet_soato": null,
|
||||||
|
"payment_amount": 190.06,
|
||||||
|
"payment_type": 1,
|
||||||
|
"receipt_number": "CBB580D6268681CB071931DC",
|
||||||
|
"skno_number": "AVQ24170126963",
|
||||||
|
"street_to": "УЛ. ПЕТРА МСТИСЛАВЦА",
|
||||||
|
"success": "A check is correct",
|
||||||
|
"total_amount": 190.06,
|
||||||
|
"type_np": "г.",
|
||||||
|
"ui": "CBB580D6268681CB071931DC",
|
||||||
|
"unp": "191634233",
|
||||||
|
"issued_at": "09/11/2025, 18:08:31"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user