Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/autoclaim #24

Merged
merged 9 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/spec/components/schemas/PassportEventState.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
allOf:
- $ref: '#/components/schemas/PassportEventStateKey'
- type: object
required:
- attributes
properties:
attributes:
required:
- claimed
type: object
properties:
claimed:
type: bool
example: true
description: If passport scan event was automatically claimed
13 changes: 13 additions & 0 deletions docs/spec/components/schemas/PassportEventStateKey.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
type: object
required:
- id
- type
properties:
id:
type: string
description: Nullifier of the points owner
example: "0x123...abc"
pattern: '^0x[0-9a-fA-F]{64}$'
type:
type: string
enum: [ passport_event_state ]
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,17 @@ post:
data:
$ref: '#/components/schemas/VerifyPassport'
responses:
204:
200:
description: Success
content:
application/vnd.api+json:
schema:
type: object
required:
- data
properties:
data:
$ref: '#/components/schemas/PassportEventState'
400:
$ref: '#/components/responses/invalidParameter'
401:
Expand Down
9 changes: 6 additions & 3 deletions internal/data/balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,20 @@ type BalancesQ interface {
GetWithRank(nullifier string) (*Balance, error)
SelectWithRank() ([]Balance, error)

// Filters not applied. Return balances which already have scanned passport, but there no fulfilled or claimed events for this
// WithoutPassportEvent returns balances which already
// have scanned passport, but there no claimed events
// for this. Filters are not applied.
WithoutPassportEvent() ([]WithoutPassportEventBalance, error)
WithoutReferralEvent() ([]ReferredReferrer, error)

FilterByNullifier(string) BalancesQ
FilterByNullifier(...string) BalancesQ
FilterDisabled() BalancesQ
}

type WithoutPassportEventBalance struct {
Balance
EventID *string `db:"event_id"`
EventID string `db:"event_id"`
EventStatus EventStatus `db:"event_status"`
}

type ReferredReferrer struct {
Expand Down
1 change: 1 addition & 0 deletions internal/data/evtypes/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type EventConfig struct {
StartsAt *time.Time `fig:"starts_at"`
ExpiresAt *time.Time `fig:"expires_at"`
NoAutoOpen bool `fig:"no_auto_open"`
AutoClaim bool `fig:"auto_claim"`
Disabled bool `fig:"disabled"`
ActionURL *url.URL `fig:"action_url"`
Logo *url.URL `fig:"logo"`
Expand Down
8 changes: 4 additions & 4 deletions internal/data/pg/balances.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ func (q *balances) GetWithRank(nullifier string) (*data.Balance, error) {
func (q *balances) WithoutPassportEvent() ([]data.WithoutPassportEventBalance, error) {
var res []data.WithoutPassportEventBalance
stmt := fmt.Sprintf(`
SELECT b.*, e.id AS event_id
FROM %s AS b LEFT JOIN %s AS e
SELECT b.*, e.id AS event_id, e.status AS event_status
FROM %s AS b INNER JOIN %s AS e
ON b.nullifier = e.nullifier AND e.type='passport_scan'
WHERE (e.status NOT IN ('fulfilled', 'claimed') OR e.nullifier IS NULL)
WHERE e.status NOT IN ('claimed')
AND b.referred_by IS NOT NULL
AND b.country IS NOT NULL
`, balancesTable, eventsTable)
Expand Down Expand Up @@ -188,7 +188,7 @@ func (q *balances) WithoutReferralEvent() ([]data.ReferredReferrer, error) {

}

func (q *balances) FilterByNullifier(nullifier string) data.BalancesQ {
func (q *balances) FilterByNullifier(nullifier ...string) data.BalancesQ {
return q.applyCondition(squirrel.Eq{"nullifier": nullifier})
}

Expand Down
113 changes: 75 additions & 38 deletions internal/service/handlers/claim_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (
"net/http"

"github.com/rarimo/decentralized-auth-svc/pkg/auth"
"github.com/rarimo/rarime-points-svc/internal/config"
"github.com/rarimo/rarime-points-svc/internal/data"
"github.com/rarimo/rarime-points-svc/internal/data/evtypes"
"github.com/rarimo/rarime-points-svc/internal/data/pg"
"github.com/rarimo/rarime-points-svc/internal/service/requests"
"github.com/rarimo/rarime-points-svc/resources"
Expand Down Expand Up @@ -82,7 +84,13 @@ func ClaimEvent(w http.ResponseWriter, r *http.Request) {
return
}

event, err = claimEventWithPoints(r, *event, evType.Reward, balance)
err = EventsQ(r).Transaction(func() error {
event, err = claimEvent(r, event, balance)
if err != nil {
return err
}
return nil
})
if err != nil {
Log(r).WithError(err).Errorf("Failed to claim event %s and accrue %d points to the balance %s",
event.ID, evType.Reward, event.Nullifier)
Expand All @@ -101,53 +109,82 @@ func ClaimEvent(w http.ResponseWriter, r *http.Request) {
ape.Render(w, newClaimEventResponse(*event, evType.Resource(), *balance))
}

// claimEventWithPoints requires event to exist
func claimEventWithPoints(r *http.Request, event data.Event, reward int64, balance *data.Balance) (claimed *data.Event, err error) {
err = EventsQ(r).Transaction(func() error {
// Upgrade level logic when threshold is reached
refsCount, level := Levels(r).LvlUp(balance.Level, reward+balance.Amount)
if level != balance.Level {
count, err := ReferralsQ(r).FilterByNullifier(balance.Nullifier).Count()
if err != nil {
return fmt.Errorf("failed to get referral count: %w", err)
}

refToAdd := prepareReferralsToAdd(balance.Nullifier, uint64(refsCount), count)
if err = ReferralsQ(r).Insert(refToAdd...); err != nil {
return fmt.Errorf("failed to insert referrals: %w", err)
}

err = BalancesQ(r).FilterByNullifier(balance.Nullifier).Update(map[string]any{
data.ColLevel: level,
})
if err != nil {
return fmt.Errorf("failed to update level: %w", err)
}
}
// claimEvent requires event to exist
// call in transaction to prevent unexpected changes
func claimEvent(r *http.Request, event *data.Event, balance *data.Balance) (claimed *data.Event, err error) {
if event == nil {
return nil, nil
}

claimed, err = EventsQ(r).FilterByID(event.ID).Update(data.EventClaimed, nil, &reward)
if err != nil {
return fmt.Errorf("update event status: %w", err)
}
evType := EventTypes(r).Get(event.Type, evtypes.FilterInactive)
if evType == nil {
return event, nil
}

claimed, err = EventsQ(r).FilterByID(event.ID).Update(data.EventClaimed, nil, &evType.Reward)
if err != nil {
return nil, fmt.Errorf("update event status: %w", err)
}

err = DoClaimEventUpdates(
Levels(r),
ReferralsQ(r),
BalancesQ(r),
CountriesQ(r),
*balance,
evType.Reward)
if err != nil {
return nil, fmt.Errorf("failed to do claim event updates: %w", err)
}

return claimed, nil
}

err = BalancesQ(r).FilterByNullifier(balance.Nullifier).Update(map[string]any{
data.ColAmount: pg.AddToValue(data.ColAmount, reward),
})
// DoClaimEventUpdates do updates which link to claim event:
// update reserved amount in country;
// lvlup and update referrals count;
// accruing points;
//
// Balance must be active and with verified passport
func DoClaimEventUpdates(
levels config.Levels,
referralsQ data.ReferralsQ,
balancesQ data.BalancesQ,
countriesQ data.CountriesQ,
balance data.Balance,
reward int64) (err error) {

refsCount, level := levels.LvlUp(balance.Level, reward+balance.Amount)
if refsCount > 0 {
count, err := referralsQ.New().FilterByNullifier(balance.Nullifier).Count()
if err != nil {
return fmt.Errorf("update balance amount: %w", err)
return fmt.Errorf("failed to get referral count: %w", err)
}

err = CountriesQ(r).FilterByCodes(*balance.Country).Update(map[string]any{
data.ColReserved: pg.AddToValue(data.ColReserved, reward),
})
if err != nil {
return fmt.Errorf("increase country reserve: %w", err)
refToAdd := prepareReferralsToAdd(balance.Nullifier, uint64(refsCount), count)
if err = referralsQ.New().Insert(refToAdd...); err != nil {
return fmt.Errorf("failed to insert referrals: %w", err)
}

return nil
}

err = balancesQ.FilterByNullifier(balance.Nullifier).Update(map[string]any{
data.ColAmount: pg.AddToValue(data.ColAmount, reward),
data.ColLevel: level,
})
if err != nil {
return fmt.Errorf("update balance amount and level: %w", err)
}

return claimed, nil
err = countriesQ.FilterByCodes(*balance.Country).Update(map[string]any{
data.ColReserved: pg.AddToValue(data.ColReserved, reward),
})
if err != nil {
return fmt.Errorf("increase country reserve: %w", err)
}

return nil
}

func newClaimEventResponse(
Expand Down
Loading
Loading