get prices methods

This commit is contained in:
nquidox 2026-03-10 23:44:24 +03:00
parent c772f0a4d2
commit 9e97f082fa
4 changed files with 324 additions and 14 deletions

View file

@ -38,6 +38,10 @@ func (h *Handler) RegisterRoutes(r *gin.RouterGroup) {
originsGroup.GET("", h.controller.getOrigins) originsGroup.GET("", h.controller.getOrigins)
originsGroup.DELETE("", h.controller.deleteOrigin) originsGroup.DELETE("", h.controller.deleteOrigin)
chartsGroup := r.Group("/prices")
chartsGroup.GET("", h.controller.getChartsPrices)
chartsGroup.GET("/:uuid", h.controller.getDistinctPrices)
} }
// create godoc // create godoc
@ -303,3 +307,66 @@ func (co *controller) deleteOrigin(c *gin.Context) {
logDebug(controllerLogHeader, "delete origin success") logDebug(controllerLogHeader, "delete origin success")
c.Status(http.StatusNoContent) c.Status(http.StatusNoContent)
} }
// getChartsPrices godoc
//
// @Summary Получить цены мерча за период
// @Description Получить цены мерча за период
// @Tags Merch
// @Security BearerAuth
// @Produce json
// @Param days query string false "period in days"
// @Success 200 {array} PricesResponse
// @Failure 400 {object} responses.BadRequest
// @Failure 401 {object} responses.Unauthorized
// @Failure 500 {object} responses.InternalServerError
// @Router /prices [get]
func (co *controller) getChartsPrices(c *gin.Context) {
response, err := co.service.getPrices(c, getUserId(c), getDays(c))
if err != nil {
c.JSON(http.StatusBadRequest, responses.InternalServerError{Error: err.Error()})
logErr(controllerLogHeader, err)
return
}
c.JSON(http.StatusOK, response)
}
// getDistinctPrices godoc
//
// @Summary Получить перепады цен мерча за период по его merch_uuid
// @Description Получить перепады цен мерча за период по его merch_uuid
// @Tags Merch
// @Security BearerAuth
// @Produce json
// @Param uuid path string true "merch_uuid"
// @Param days query string false "period in days"
// @Success 200 {object} PricesResponse
// @Success 204
// @Failure 400 {object} responses.BadRequest
// @Failure 401 {object} responses.Unauthorized
// @Failure 500 {object} responses.InternalServerError
// @Router /prices/{uuid} [get]
func (co *controller) getDistinctPrices(c *gin.Context) {
merchUuid := c.Param("uuid")
if merchUuid == "" {
err := errors.New("MerchUuid is empty")
c.JSON(http.StatusBadRequest, responses.BadRequest{Error: err.Error()})
logErr(controllerLogHeader, err)
return
}
response, err := co.service.getDistinctPrices(c, getUserId(c), merchUuid, getDays(c))
if err != nil {
c.JSON(http.StatusBadRequest, responses.InternalServerError{Error: err.Error()})
logErr(controllerLogHeader, err)
return
}
if response == nil {
c.Status(http.StatusNoContent)
return
}
c.JSON(http.StatusOK, response)
}

View file

@ -45,3 +45,20 @@ type extraDataDTO struct {
MerchUuid string `json:"merch_uuid"` MerchUuid string `json:"merch_uuid"`
Links []originLink `json:"links"` Links []originLink `json:"links"`
} }
// prices dtos
type PriceEntry struct {
CreatedAt int64 `json:"created_at"`
Value int `json:"value"`
}
type OriginWithPrices struct {
Origin string `json:"origin"`
Prices []PriceEntry
}
type PricesResponse struct {
Name string `json:"name"`
MerchUuid string `json:"merch_uuid"`
Origins []OriginWithPrices `json:"origins"`
}

View file

@ -19,6 +19,7 @@ type Repository interface {
getMerchIdByUuid(ctx context.Context, userId int64, uuid string) (int64, error) getMerchIdByUuid(ctx context.Context, userId int64, uuid string) (int64, error)
getMerchUuidMap(ctx context.Context, merchUuids []string) (map[string]int64, error) getMerchUuidMap(ctx context.Context, merchUuids []string) (map[string]int64, error)
getAllUserMerch(ctx context.Context, userId int64) ([]Merch, error)
updateMerch(ctx context.Context, userId int64, merch *updateMerchDTO) (*merchDTO, error) updateMerch(ctx context.Context, userId int64, merch *updateMerchDTO) (*merchDTO, error)
updateExtraData(ctx context.Context, merchId int64, insertData []ExtraData) ([]ExtraData, error) updateExtraData(ctx context.Context, merchId int64, insertData []ExtraData) ([]ExtraData, error)
@ -39,6 +40,8 @@ type Origins interface {
type Prices interface { type Prices interface {
insertPrices(ctx context.Context, prices []Price) error insertPrices(ctx context.Context, prices []Price) error
getPricesWithDays(ctx context.Context, userId int64, days time.Time) ([]Price, error)
getDistinctPrices(ctx context.Context, userId int64, merchUuid string, days time.Time) ([]Price, error)
} }
type Tasks interface { type Tasks interface {
@ -219,6 +222,32 @@ func (r *repo) getMerchUuidMap(ctx context.Context, merchUuids []string) (map[st
return merchUuidMap, nil return merchUuidMap, nil
} }
func (r *repo) getAllUserMerch(ctx context.Context, userId int64) ([]Merch, error) {
var userMerch []Merch
q := `SELECT id, merch_uuid, name FROM merch WHERE user_id = $1 AND deleted_at IS NULL`
rows, err := r.db.Query(ctx, q, userId)
if err != nil {
return nil, err
}
for rows.Next() {
var m Merch
if err = rows.Scan(&m.Id, &m.MerchUuid, &m.Name); err != nil {
rows.Close()
return nil, err
}
userMerch = append(userMerch, m)
}
rows.Close()
if err = rows.Err(); err != nil {
return nil, err
}
return userMerch, nil
}
func (r *repo) deleteOneMerchRecord(ctx context.Context, userId int64, merchUuid string, delTime time.Time) error { func (r *repo) deleteOneMerchRecord(ctx context.Context, userId int64, merchUuid string, delTime time.Time) error {
tx, err := r.db.Begin(ctx) tx, err := r.db.Begin(ctx)
if err != nil { if err != nil {
@ -365,3 +394,78 @@ func (r *repo) getTaskData(ctx context.Context) ([]taskData, error) {
return result, nil return result, nil
} }
func (r *repo) getPricesWithDays(ctx context.Context, userId int64, days time.Time) ([]Price, error) {
q := `
SELECT mp.created_at, mp.merch_id, mp.price, mp.origin_id
FROM merch_prices AS mp
JOIN merch AS m ON m.id = mp.merch_id
WHERE m.user_id = $1
AND mp.created_at > $2
AND mp.deleted_at IS NULL
AND m.deleted_at IS NULL
`
rows, err := r.db.Query(ctx, q, userId, days)
if err != nil {
return nil, err
}
var result []Price
for rows.Next() {
var p Price
if err = rows.Scan(&p.CreatedAt, &p.MerchId, &p.Price, &p.OriginId); err != nil {
rows.Close()
return nil, err
}
result = append(result, p)
}
rows.Close()
if err = rows.Err(); err != nil {
return nil, err
}
return result, nil
}
func (r *repo) getDistinctPrices(ctx context.Context, userId int64, merchUuid string, days time.Time) ([]Price, error) {
q := `
SELECT price, created_at, origin_id
FROM (
SELECT DISTINCT ON (price) price, created_at, origin_id
FROM merch_prices
WHERE merch_id = (
SELECT id
FROM merch
WHERE merch_uuid = $1
AND user_id = $2
AND deleted_at IS NULL
)
AND deleted_at IS NULL
AND created_at > $3
)
ORDER BY created_at;
`
fmt.Println(merchUuid, userId, days)
rows, err := r.db.Query(ctx, q, merchUuid, userId, days)
if err != nil {
return nil, err
}
var result []Price
for rows.Next() {
var p Price
if err = rows.Scan(&p.Price, &p.CreatedAt, &p.OriginId); err != nil {
rows.Close()
return nil, err
}
result = append(result, p)
}
rows.Close()
if err = rows.Err(); err != nil {
return nil, err
}
return result, nil
}

View file

@ -2,10 +2,11 @@ package merch
import ( import (
"context" "context"
"errors"
"fmt"
"github.com/google/uuid"
"merch-api/internal/user" "merch-api/internal/user"
"merch-api/pkg/utils" "merch-api/pkg/utils"
"github.com/google/uuid"
) )
const serviceLogHeader string = "[Service]" const serviceLogHeader string = "[Service]"
@ -111,6 +112,9 @@ func (s *service) createMerch(ctx context.Context, userId int64, payload *newMer
return s.repo.createMerch(ctx, newMerch, merchExtra) return s.repo.createMerch(ctx, newMerch, merchExtra)
} }
// getOriginsMaps
// first return name:id
// second id:name
func (s *service) getOriginsMaps(ctx context.Context) (map[string]int64, map[int64]string, error) { func (s *service) getOriginsMaps(ctx context.Context) (map[string]int64, map[int64]string, error) {
origins, err := s.repo.getOrigins(ctx) origins, err := s.repo.getOrigins(ctx)
if err != nil { if err != nil {
@ -197,3 +201,121 @@ func (s *service) updateExtraData(ctx context.Context, userId int64, payload *ex
func (s *service) deleteOneMerchRecord(ctx context.Context, userId int64, merchUuid string) error { func (s *service) deleteOneMerchRecord(ctx context.Context, userId int64, merchUuid string) error {
return s.repo.deleteOneMerchRecord(ctx, userId, merchUuid, s.utils.TimeNowUTC()) return s.repo.deleteOneMerchRecord(ctx, userId, merchUuid, s.utils.TimeNowUTC())
} }
func (s *service) getPrices(ctx context.Context, userId int64, days int) ([]PricesResponse, error) {
fmt.Println("Enter service")
merchList, err := s.repo.getAllUserMerch(ctx, userId)
if err != nil {
logErr(serviceLogHeader, err)
return nil, err
}
if len(merchList) == 0 {
errMsg := errors.New("no merch found")
logErr(serviceLogHeader, errMsg)
return nil, errMsg
}
merchMap := make(map[int64]Merch, len(merchList))
for _, merch := range merchList {
merchMap[merch.Id] = merch
}
var response []PricesResponse
for _, item := range merchList {
response = append(response, PricesResponse{
MerchUuid: item.MerchUuid,
Name: item.Name,
Origins: []OriginWithPrices{},
})
}
pricesList, err := s.repo.getPricesWithDays(ctx, userId, getPeriod(days))
if err != nil {
logErr(serviceLogHeader, err)
return nil, err
}
_, originNamesMap, err := s.getOriginsMaps(ctx)
if err != nil {
logErr(serviceLogHeader, err)
return nil, err
}
pricesMap := make(map[string]map[string][]PriceEntry)
for _, item := range pricesList {
merchUuid := merchMap[item.MerchId].MerchUuid
if _, ok := pricesMap[merchUuid]; !ok {
pricesMap[merchUuid] = make(map[string][]PriceEntry)
}
originName := originNamesMap[item.OriginId]
pricesMap[merchUuid][originName] = append(pricesMap[merchUuid][originName], PriceEntry{
CreatedAt: item.CreatedAt.Unix(),
Value: item.Price,
})
}
for i := range response {
for _, name := range originNamesMap {
prices := pricesMap[response[i].MerchUuid][name]
if prices == nil {
continue
}
response[i].Origins = append(response[i].Origins, OriginWithPrices{
Origin: name,
Prices: prices,
})
}
}
return response, nil
}
func (s *service) getDistinctPrices(ctx context.Context, userId int64, merchUuid string, days int) (*PricesResponse, error) {
result, err := s.repo.getDistinctPrices(ctx, userId, merchUuid, getPeriod(days))
if err != nil {
logErr(serviceLogHeader, err)
return nil, err
}
if result == nil {
return nil, nil
}
_, originNamesMap, err := s.getOriginsMaps(ctx)
if err != nil {
logErr(serviceLogHeader, err)
return nil, err
}
response := PricesResponse{
MerchUuid: merchUuid,
Origins: []OriginWithPrices{},
}
pricesMap := make(map[string][]PriceEntry)
for _, item := range result {
originName := originNamesMap[item.OriginId]
if _, ok := pricesMap[originName]; !ok {
pricesMap[originName] = make([]PriceEntry, 0)
}
pricesMap[originName] = append(pricesMap[originName], PriceEntry{
CreatedAt: item.CreatedAt.Unix(),
Value: item.Price,
})
}
for key, item := range pricesMap {
response.Origins = append(response.Origins, OriginWithPrices{
Origin: key,
Prices: item,
})
}
return &response, nil
}