diff --git a/config.yaml b/config.yaml index 29c1606..6c5e5a1 100644 --- a/config.yaml +++ b/config.yaml @@ -91,6 +91,14 @@ event_types: short_description: Short description auto_claim: true qr_code_value: "qr_code_base64_string" + - name: early_test + title: one-time event + reward: 5 + frequency: unlimited + description: one time event + short_description: Short description + auto_claim: true + no_auto_open: true levels: downgradeable: false @@ -108,12 +116,10 @@ auth: verifier: allowed_age: 18 allowed_identity_timestamp: 1715698750 - poseidonsmt_root_verifier: rpc: evm_rpc_url contract: poseidon_smt_contract_address request_timeout: 10s - poll_verifier: rpc: evm_rpc_url proposal_state_address: 0x123...123 diff --git a/internal/cli/event.go b/internal/cli/event.go new file mode 100644 index 0000000..f0fb1dc --- /dev/null +++ b/internal/cli/event.go @@ -0,0 +1,16 @@ +package cli + +import ( + "github.com/rarimo/geo-points-svc/internal/config" + "github.com/rarimo/geo-points-svc/internal/data/evtypes" + "github.com/rarimo/geo-points-svc/internal/service/event" +) + +func eventStart(cfg config.Config, data int) { + evtypes.InitForOneTimeEvent(cfg) + + err := event.Run(cfg, data) + if err != nil { + cfg.Log().Errorf("Error starting event: %s", err) + } +} diff --git a/internal/cli/main.go b/internal/cli/main.go index 4d7a177..a03e46f 100644 --- a/internal/cli/main.go +++ b/internal/cli/main.go @@ -29,6 +29,10 @@ func Run(args []string) bool { migrateCmd = app.Command("migrate", "migrate command") migrateUpCmd = migrateCmd.Command("up", "migrate db up") migrateDownCmd = migrateCmd.Command("down", "migrate db down") + + event = app.Command("event", "claim event command") + eventCmd = event.Command("create-early-test", "claim event command") + date = eventCmd.Arg("before", "date after ...").Required().Int() ) cmd, err := app.Parse(args[1:]) @@ -48,6 +52,8 @@ func Run(args []string) bool { err = MigrateUp(cfg) case migrateDownCmd.FullCommand(): err = MigrateDown(cfg) + case eventCmd.FullCommand(): + eventStart(cfg, *date) default: log.Errorf("unknown command %s", cmd) return false diff --git a/internal/data/balances.go b/internal/data/balances.go index 76bf4e4..fc47673 100644 --- a/internal/data/balances.go +++ b/internal/data/balances.go @@ -54,6 +54,8 @@ type BalancesQ interface { FilterByInternalAID(aid string) BalancesQ FilterByExternalAID(aid string) BalancesQ FilterBySharedHash(hash string) BalancesQ + FilterByCreatedBefore(date int) BalancesQ + FilterVerified() BalancesQ } type WithoutPassportEventBalance struct { diff --git a/internal/data/events.go b/internal/data/events.go index 669de0a..ccfa9b9 100644 --- a/internal/data/events.go +++ b/internal/data/events.go @@ -58,7 +58,7 @@ type EventsQ interface { SelectAbsentTypes(allTypes ...string) ([]ReopenableEvent, error) FilterByID(...string) EventsQ - FilterByNullifier(string) EventsQ + FilterByNullifier(...string) EventsQ FilterByStatus(...EventStatus) EventsQ FilterByType(...string) EventsQ FilterByNotType(types ...string) EventsQ diff --git a/internal/data/evtypes/init.go b/internal/data/evtypes/init.go index 1dec196..53d6e46 100644 --- a/internal/data/evtypes/init.go +++ b/internal/data/evtypes/init.go @@ -40,3 +40,23 @@ func Init(_ context.Context, cfg extConfig, sig chan struct{}) { log.Debugf("Adding/overwriting event types from DB: %+v", dbTypes) types.Push(dbTypes...) } + +func InitForOneTimeEvent(cfg extConfig) { + var ( + log = cfg.Log().WithField("who", "evtypes") + q = pg.NewEventTypes(cfg.DB().Clone()) + types = cfg.EventTypes() + ) + + dbTypes, err := q.New().Select() + if err != nil { + panic(fmt.Errorf("select all event types: %w", err)) + } + + defer func() { + types.dbSynced = true + }() + + log.Debugf("Adding/overwriting event types from DB: %+v", dbTypes) + types.Push(dbTypes...) +} diff --git a/internal/data/evtypes/models/extra.go b/internal/data/evtypes/models/extra.go index 3b794e8..3b7991f 100644 --- a/internal/data/evtypes/models/extra.go +++ b/internal/data/evtypes/models/extra.go @@ -21,6 +21,7 @@ const ( TypeInternalPassportScan = "internal_passport_scan" TypeExternalPassportScan = "external_passport_scan" TypePollParticipation = "poll_participation" + TypeEarlyTest = "early_test" ) const ( diff --git a/internal/data/pg/balances.go b/internal/data/pg/balances.go index 44a0172..bc0dd4a 100644 --- a/internal/data/pg/balances.go +++ b/internal/data/pg/balances.go @@ -222,6 +222,10 @@ func (q *balances) FilterBySharedHash(hash string) data.BalancesQ { return q.applyCondition(squirrel.Eq{"shared_hash": hash}) } +func (q *balances) FilterByCreatedBefore(data int) data.BalancesQ { + return q.applyCondition(squirrel.Lt{"created_at": data}) +} + func (q *balances) applyCondition(cond squirrel.Sqlizer) data.BalancesQ { q.selector = q.selector.Where(cond) q.updater = q.updater.Where(cond) @@ -229,3 +233,10 @@ func (q *balances) applyCondition(cond squirrel.Sqlizer) data.BalancesQ { q.counter = q.counter.Where(cond) return q } + +func (q *balances) FilterVerified() data.BalancesQ { + return q.applyCondition(squirrel.Or{ + squirrel.NotEq{"internal_aid": nil}, + squirrel.NotEq{"external_aid": nil}, + }) +} diff --git a/internal/data/pg/events.go b/internal/data/pg/events.go index 4e6ada9..4750faa 100644 --- a/internal/data/pg/events.go +++ b/internal/data/pg/events.go @@ -193,7 +193,7 @@ func (q *events) FilterByID(ids ...string) data.EventsQ { return q.applyCondition(squirrel.Eq{"id": ids}) } -func (q *events) FilterByNullifier(nullifier string) data.EventsQ { +func (q *events) FilterByNullifier(nullifier ...string) data.EventsQ { return q.applyCondition(squirrel.Eq{"nullifier": nullifier}) } diff --git a/internal/service/event/main.go b/internal/service/event/main.go new file mode 100644 index 0000000..7c99739 --- /dev/null +++ b/internal/service/event/main.go @@ -0,0 +1,108 @@ +package event + +import ( + "fmt" + + "github.com/rarimo/geo-points-svc/internal/config" + "github.com/rarimo/geo-points-svc/internal/data" + "github.com/rarimo/geo-points-svc/internal/data/evtypes" + "github.com/rarimo/geo-points-svc/internal/data/evtypes/models" + "github.com/rarimo/geo-points-svc/internal/data/pg" + "github.com/rarimo/geo-points-svc/internal/service/handlers" +) + +func Run(cfg config.Config, date int) error { + log := cfg.Log() + db := cfg.DB() + lvls := cfg.Levels() + evTypes := cfg.EventTypes() + + balancesQ := pg.NewBalances(db) + eventsQ := pg.NewEvents(db) + referralsQ := pg.NewReferrals(db) + + evType := evTypes.Get(models.TypeEarlyTest, evtypes.FilterInactive) + + if evType == nil { + log.Infof("Event type %s is inactive", models.TypeEarlyTest) + return nil + } + + balances, err := balancesQ.FilterByCreatedBefore(date).FilterVerified().Select() + + if err != nil { + log.WithError(err).Error("failed to filter by updated before") + return err + } + if len(balances) == 0 { + log.Infof("no balances found") + return nil + } + + nullifiers := make([]string, 0, len(balances)) + + for _, balance := range balances { + nullifiers = append(nullifiers, balance.Nullifier) + } + + filteredEvents, err := eventsQ. + FilterByType(models.TypeEarlyTest). + FilterByNullifier(nullifiers...). + Select() + + if err != nil { + log.WithError(err).Errorf("Failed to select %s events", err) + return err + } + + eventsMap := make(map[string]struct{}, len(filteredEvents)) + + for _, event := range filteredEvents { + eventsMap[event.Nullifier] = struct{}{} + } + + for _, balance := range balances { + err = eventsQ.New().Transaction(func() error { + if _, exists := eventsMap[balance.Nullifier]; exists { + log.Infof("Event %s is already done for user with nullifier %s ", models.TypeEarlyTest, balance.Nullifier) + return nil + } + + err = eventsQ.Insert(data.Event{ + Nullifier: balance.Nullifier, + Type: models.TypeEarlyTest, + Status: data.EventFulfilled, + }) + + if err != nil { + return fmt.Errorf("failed to insert `early_test` event: %w", err) + } + + if evtypes.FilterByAutoClaim(true)(*evType) { + return nil + } + + _, err = eventsQ.FilterByNullifier(balance.Nullifier).Update(data.EventFulfilled, nil, &evType.Reward) + if err != nil { + return fmt.Errorf("failed to update %s events for user=%s: %w", models.TypeEarlyTest, balance.Nullifier, err) + } + + level, err := handlers.DoLevelRefUpgrade(lvls, referralsQ, &balance, evType.Reward) + if err != nil { + return fmt.Errorf("failed to do lvlup and referrals updates: %w", err) + } + + err = balancesQ.New().FilterByNullifier(balance.Nullifier).Update(map[string]any{ + data.ColAmount: pg.AddToValue(data.ColAmount, evType.Reward), + data.ColLevel: level, + }) + + if err != nil { + return fmt.Errorf("error update balance amount and level: %w", err) + } + + return nil + }) + } + return nil +} diff --git a/internal/service/workers/leveljustice/main.go b/internal/service/workers/leveljustice/main.go index e4812fc..ce60041 100644 --- a/internal/service/workers/leveljustice/main.go +++ b/internal/service/workers/leveljustice/main.go @@ -37,5 +37,4 @@ func Run(cfg config.Config, sig chan struct{}) { } sig <- struct{}{} - }