From 718101ee672f83637e1f73a6ecc7da8fcbf05ba0 Mon Sep 17 00:00:00 2001 From: nquidox Date: Sun, 8 Mar 2026 16:42:37 +0300 Subject: [PATCH] get tasks + insert result --- internal/common/interfaces.go | 6 +- internal/merch/handler.go | 2 +- internal/merch/model.go | 6 ++ internal/merch/provider.go | 85 ++++++++++++++++++++++++-- internal/merch/provider_test.go | 75 +++++++++++++++++++++++ internal/merch/repository.go | 102 ++++++++++++++++++++++++++++++++ 6 files changed, 268 insertions(+), 8 deletions(-) create mode 100644 internal/merch/provider_test.go diff --git a/internal/common/interfaces.go b/internal/common/interfaces.go index 300b1eb..ab37322 100644 --- a/internal/common/interfaces.go +++ b/internal/common/interfaces.go @@ -1,8 +1,10 @@ package common -import "context" +import ( + "context" +) type MerchProvider interface { GetTasks(ctx context.Context) ([]Task, error) - InsertRecords(ctx context.Context) error + InsertPrices(ctx context.Context, rawPrices []Result) error } diff --git a/internal/merch/handler.go b/internal/merch/handler.go index 0f236b5..2ed77d1 100644 --- a/internal/merch/handler.go +++ b/internal/merch/handler.go @@ -21,7 +21,7 @@ func New(deps Deps) *Handler { r := newRepo(deps.DB) s := newService(r, deps.Utils, deps.UserProvider) c := newController(s, deps.Utils) - p := newProvider() + p := newProvider(s, deps.Utils) return &Handler{ controller: c, diff --git a/internal/merch/model.go b/internal/merch/model.go index e1a03ec..85321fa 100644 --- a/internal/merch/model.go +++ b/internal/merch/model.go @@ -44,3 +44,9 @@ type ExtraData struct { OriginId int64 URL string } + +type taskData struct { + MerchUuid string + OriginName string + Url string +} diff --git a/internal/merch/provider.go b/internal/merch/provider.go index 3e055c0..8d7c8b5 100644 --- a/internal/merch/provider.go +++ b/internal/merch/provider.go @@ -3,18 +3,93 @@ package merch import ( "context" "merch-api/internal/common" + "merch-api/pkg/utils" ) -type provider struct{} +const providerLogHeader string = "[provider]" -func newProvider() *provider { - return &provider{} +type provider struct { + service *service + utils utils.Utils +} + +func newProvider(s *service, u utils.Utils) *provider { + return &provider{ + service: s, + utils: u, + } } func (p *provider) GetTasks(ctx context.Context) ([]common.Task, error) { - return nil, nil + rawTasks, err := p.service.repo.getTaskData(ctx) + if err != nil { + logErr(providerLogHeader, err) + return nil, err + } + return p.convertTasks(rawTasks), nil } -func (p *provider) InsertRecords(ctx context.Context) error { +func (p *provider) InsertPrices(ctx context.Context, rawPrices []common.Result) error { + if rawPrices == nil || len(rawPrices) == 0 { + logDebug(providerLogHeader, "no prices given") + return nil + } + + var uuids []string + for _, rawPrice := range rawPrices { + uuids = append(uuids, rawPrice.MerchUuid) + } + + uuidMap, err := p.service.getMerchUuidMap(ctx, uuids) + if err != nil { + logErr(serviceLogHeader, err) + return err + } + + originIds, _, err := p.service.getOriginsMaps(ctx) + if err != nil { + logErr(serviceLogHeader, err) + return err + } + + now := p.utils.TimeNowUTC() + var insertPrices []Price + for _, rawPrice := range rawPrices { + insertPrices = append(insertPrices, Price{ + CreatedAt: now, + UpdatedAt: p.utils.NullTimeFromNow(now), + MerchId: uuidMap[rawPrice.MerchUuid], + OriginId: originIds[rawPrice.OriginName], + Price: int(rawPrice.Price), + }) + } + + if err = p.service.repo.insertPrices(ctx, insertPrices); err != nil { + logErr(serviceLogHeader, err) + return err + } + return nil } + +func (p *provider) convertTasks(rawTasks []taskData) []common.Task { + convert := make(map[string]*common.Task, len(rawTasks)) + for _, item := range rawTasks { + task, ok := convert[item.MerchUuid] + if !ok { + task = &common.Task{ + MerchUuid: item.MerchUuid, + Origins: make(map[string]string), + } + convert[item.MerchUuid] = task + } + task.Origins[item.OriginName] = item.Url + } + + result := make([]common.Task, 0, len(convert)) + for _, task := range convert { + result = append(result, *task) + } + + return result +} diff --git a/internal/merch/provider_test.go b/internal/merch/provider_test.go new file mode 100644 index 0000000..99ebe6a --- /dev/null +++ b/internal/merch/provider_test.go @@ -0,0 +1,75 @@ +package merch + +import ( + "merch-api/internal/common" + "merch-api/pkg/utils" + "reflect" + "testing" +) + +func Test_provider_convertTasks(t *testing.T) { + type fields struct { + service *service + utils utils.Utils + } + type args struct { + rawTasks []taskData + } + tests := []struct { + name string + fields fields + args args + want []common.Task + }{ + { + name: "Success convert tasks", + fields: fields{ + service: nil, + utils: nil, + }, + args: args{[]taskData{ + {"1", "surugaya", "url"}, + {"1", "mandarake", "url"}, + {"1", "amiami", "url"}, + {"2", "surugaya", "url"}, + {"2", "mandarake", "url"}, + {"2", "amiami", "url"}, + {"3", "surugaya", "url"}, + {"3", "amiami", "url"}, + {"4", "amiami", "url"}, + }}, + want: []common.Task{ + {"1", map[string]string{"surugaya": "url", "mandarake": "url", "amiami": "url"}}, + {"2", map[string]string{"surugaya": "url", "mandarake": "url", "amiami": "url"}}, + {"3", map[string]string{"surugaya": "url", "amiami": "url"}}, + {"4", map[string]string{"amiami": "url"}}, + }, + }, + { + name: "Success convert tasks", + fields: fields{ + service: nil, + utils: nil, + }, + args: args{[]taskData{ + {"1", "surugaya", "url"}, + {"1", "mandarake", "url"}, + {"1", "amiami", "url"}, + }}, + want: []common.Task{ + {"1", map[string]string{"surugaya": "url", "mandarake": "url", "amiami": "url"}}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &provider{ + service: tt.fields.service, + utils: tt.fields.utils, + } + if got := p.convertTasks(tt.args.rawTasks); !reflect.DeepEqual(got, tt.want) { + t.Errorf("convertTasks() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/merch/repository.go b/internal/merch/repository.go index 5883361..644d22d 100644 --- a/internal/merch/repository.go +++ b/internal/merch/repository.go @@ -18,6 +18,7 @@ type Repository interface { getMany(ctx context.Context, userId int64) ([]merchDTO, error) getMerchIdByUuid(ctx context.Context, userId int64, uuid string) (int64, error) + getMerchUuidMap(ctx context.Context, merchUuids []string) (map[string]int64, error) updateMerch(ctx context.Context, userId int64, merch *updateMerchDTO) (*merchDTO, error) updateExtraData(ctx context.Context, merchId int64, insertData []ExtraData) ([]ExtraData, error) @@ -26,6 +27,8 @@ type Repository interface { deleteOneMerchRecord(ctx context.Context, userId int64, merchUuid string, delTime time.Time) error Origins + Prices + Tasks } type Origins interface { @@ -34,6 +37,14 @@ type Origins interface { deleteOriginByName(ctx context.Context, name string, deletedAt sql.NullTime) error } +type Prices interface { + insertPrices(ctx context.Context, prices []Price) error +} + +type Tasks interface { + getTaskData(ctx context.Context) ([]taskData, error) +} + type repo struct { db *pgxpool.Pool } @@ -179,6 +190,35 @@ func (r *repo) getMerchIdByUuid(ctx context.Context, userId int64, uuid string) return merchId, nil } +func (r *repo) getMerchUuidMap(ctx context.Context, merchUuids []string) (map[string]int64, error) { + q := `SELECT merch_uuid, id FROM merch WHERE deleted_at IS NULL AND merch_uuid = ANY($1)` + + rows, err := r.db.Query(ctx, q, merchUuids) + if err != nil { + return nil, err + } + + var merchUuidMap map[string]int64 + for rows.Next() { + var ( + uuid string + id int64 + ) + if err = rows.Scan(&uuid, &id); err != nil { + rows.Close() + return nil, err + } + merchUuidMap[uuid] = id + } + + rows.Close() + if err = rows.Err(); err != nil { + return nil, err + } + + return merchUuidMap, nil +} + func (r *repo) deleteOneMerchRecord(ctx context.Context, userId int64, merchUuid string, delTime time.Time) error { tx, err := r.db.Begin(ctx) if err != nil { @@ -263,3 +303,65 @@ func (r *repo) updateExtraData(ctx context.Context, merchId int64, insertData [] return result, nil } + +func (r *repo) insertPrices(ctx context.Context, prices []Price) error { + q := ` + INSERT INTO merch_prices (created_at, updated_at, merch_id, origin_id, price) + SELECT $1, $2, src.merch_id, src.origin_id, src.price + FROM UNNEST( + $3::bigint[], + $4::bigint[], + $5::int[] + ) AS src (merch_id, origin_id, price) + ` + + var ( + merchIds []int64 + originIds []int64 + priceValues []int + ) + + for _, price := range prices { + merchIds = append(merchIds, price.MerchId) + originIds = append(originIds, price.OriginId) + priceValues = append(priceValues, price.Price) + } + + _, err := r.db.Exec(ctx, q, merchIds, originIds, priceValues) + if err != nil { + return err + } + + return nil +} + +func (r *repo) getTaskData(ctx context.Context) ([]taskData, error) { + q := `SELECT m.merch_uuid, med.url, mo.name + FROM merch AS m + JOIN merch_extra_data AS med ON m.id = med.merch_id + JOIN merch_origins AS mo ON mo.id = med.origin_id + WHERE m.deleted_at IS NULL + AND med.deleted_at IS NULL + AND mo.deleted_at IS NULL + ` + rows, err := r.db.Query(ctx, q) + if err != nil { + return nil, err + } + + var result []taskData + for rows.Next() { + var t taskData + if err = rows.Scan(&t.MerchUuid, &t.Url, &t.OriginName); err != nil { + return nil, err + } + result = append(result, t) + } + + rows.Close() + if err = rows.Err(); err != nil { + return nil, err + } + + return result, nil +}