Skip to content

Commit

Permalink
Showing 6 changed files with 88 additions and 31 deletions.
4 changes: 3 additions & 1 deletion internal/data/events.go
Original file line number Diff line number Diff line change
@@ -62,7 +62,9 @@ type EventsQ interface {
FilterByStatus(...EventStatus) EventsQ
FilterByType(...string) EventsQ
FilterByNotType(types ...string) EventsQ
FilterByUpdatedAtBefore(int64) EventsQ
FilterByExternalID(string) EventsQ
FilterInactiveNotClaimed(types ...string) EventsQ
// FilterByUpdatedAtBefore must be only used with SelectReopenable, because it
// depends on table alias.
FilterByUpdatedAtBefore(int64) EventsQ
}
12 changes: 8 additions & 4 deletions internal/data/pg/events.go
Original file line number Diff line number Diff line change
@@ -30,7 +30,7 @@ func NewEvents(db *pgdb.DB) data.EventsQ {
updater: squirrel.Update(eventsTable),
deleter: squirrel.Delete(eventsTable),
counter: squirrel.Select("COUNT(*) AS count").From(eventsTable),
reopenable: squirrel.Select("nullifier", "type").Distinct().From(eventsTable + " e1"),
reopenable: squirrel.Select("e1.nullifier", "e1.type").Distinct().From(eventsTable + " e1"),
}
}

@@ -146,7 +146,10 @@ func (q *events) SelectReopenable() ([]data.ReopenableEvent, error) {
WHERE e2.nullifier = e1.nullifier
AND e2.type = e1.type
AND e2.status IN (?, ?))`, eventsTable)
stmt := q.reopenable.Where(subq, data.EventOpen, data.EventFulfilled)
stmt := q.reopenable.
Where(subq, data.EventOpen, data.EventFulfilled).
Join(balancesTable + " b ON b.nullifier = e1.nullifier").
Where("b.referred_by IS NOT NULL")

var res []data.ReopenableEvent
if err := q.db.Select(&res, stmt); err != nil {
@@ -168,7 +171,7 @@ func (q *events) SelectAbsentTypes(allTypes ...string) ([]data.ReopenableEvent,
)
SELECT u.nullifier, t.type
FROM (
SELECT nullifier FROM %s
SELECT nullifier FROM %s WHERE referred_by IS NOT NULL
) u
CROSS JOIN types t
LEFT JOIN %s e ON e.nullifier = u.nullifier AND e.type = t.type
@@ -220,7 +223,8 @@ func (q *events) FilterByExternalID(id string) data.EventsQ {
}

func (q *events) FilterByUpdatedAtBefore(unix int64) data.EventsQ {
return q.applyCondition(squirrel.Lt{"updated_at": unix})
q.reopenable = q.reopenable.Where(squirrel.Lt{"e1.updated_at": unix})
return q
}

func (q *events) FilterInactiveNotClaimed(types ...string) data.EventsQ {
40 changes: 25 additions & 15 deletions internal/service/handlers/create_event_type.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package handlers

import (
"fmt"
"net/http"

"github.com/rarimo/geo-auth-svc/pkg/auth"
@@ -39,37 +40,46 @@ func CreateEventType(w http.ResponseWriter, r *http.Request) {
}

typeModel := models.ResourceToModel(req.Data.Attributes)
if err = EventTypesQ(r).Insert(typeModel); err != nil {
Log(r).WithError(err).Error("Failed to insert event type")
err = EventsQ(r).Transaction(func() error {
if err = EventTypesQ(r).Insert(typeModel); err != nil {
return fmt.Errorf("insert event type: %w", err)
}
EventTypes(r).Push(typeModel)

// TODO: add cron jobs for limited events and other special logic when updating other fields is supported
if evtypes.FilterNotOpenable(typeModel) {
return nil
}
return openQREvents(r, typeModel)
})

if err != nil {
Log(r).WithError(err).Error("Failed to add event type and open events")
ape.RenderErr(w, problems.InternalError())
return
}
EventTypes(r).Push(typeModel)

if evtypes.FilterNotOpenable(typeModel) {
w.WriteHeader(http.StatusNoContent)
return
}
w.WriteHeader(http.StatusNoContent)
}

func openQREvents(r *http.Request, evType models.EventType) error {
balances, err := BalancesQ(r).FilterDisabled().Select()
if err != nil {
Log(r).WithError(err).Error("Failed to select balances")
ape.RenderErr(w, problems.InternalError())
return
return fmt.Errorf("select balances: %w", err)
}

eventsToInsert := make([]data.Event, 0, len(balances))
for _, b := range balances {
eventsToInsert = append(eventsToInsert, data.Event{
Nullifier: b.Nullifier,
Status: data.EventOpen,
Type: typeModel.Name,
Type: evType.Name,
})
}

if err = EventsQ(r).Insert(eventsToInsert...); err != nil {
Log(r).WithError(err).Error("Failed to insert qr-code events")
ape.RenderErr(w, problems.InternalError())
return
return fmt.Errorf("insert events: %w", err)
}
w.WriteHeader(http.StatusNoContent)

return nil
}
45 changes: 36 additions & 9 deletions internal/service/handlers/update_event_type.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package handlers

import (
"fmt"
"net/http"

"github.com/rarimo/geo-auth-svc/pkg/auth"
"github.com/rarimo/geo-points-svc/internal/data"
"github.com/rarimo/geo-points-svc/internal/data/evtypes/models"
"github.com/rarimo/geo-points-svc/internal/service/requests"
"github.com/rarimo/geo-points-svc/resources"
@@ -33,27 +35,52 @@ func UpdateEventType(w http.ResponseWriter, r *http.Request) {
}
if evType == nil {
Log(r).Debugf("Event type %s not found", req.Data.Attributes.Name)
ape.RenderErr(w, problems.Conflict())
ape.RenderErr(w, problems.NotFound())
return
}
}

typeModel := models.ResourceToModel(req.Data.Attributes)
res, err := EventTypesQ(r).FilterByNames(typeModel.Name).Update(typeModel.ForUpdate())

var updated []models.EventType
err = EventsQ(r).Transaction(func() error {
updated, err = EventTypesQ(r).FilterByNames(typeModel.Name).Update(typeModel.ForUpdate())
if err != nil {
return fmt.Errorf("update event type: %w", err)
}
if len(updated) != 1 {
return fmt.Errorf("critical: count of updated event types is %d, expected 1", len(updated))
}
// Currently, event cannot be 'not openable' in other ways,
// add extra checks more fields are supported.
if evType.Disabled == typeModel.Disabled {
return nil
}
// Open events if we have enabled the type, otherwise clean them up.
if !typeModel.Disabled {
return openQREvents(r, typeModel)
}

deleted, err := EventsQ(r).
FilterByType(typeModel.Name).
FilterByStatus(data.EventOpen, data.EventFulfilled).
Delete()
if err != nil {
return fmt.Errorf("delete disabled events: %w", err)
}

Log(r).Infof("Deleted %d events on disabling event type %s", deleted, typeModel.Name)
return nil
})

if err != nil {
Log(r).WithError(err).Error("Failed to update event type")
ape.RenderErr(w, problems.InternalError())
return
}

if len(res) == 0 {
Log(r).Error("Count of updated event_types = 0")
ape.RenderErr(w, problems.InternalError())
return
}

EventTypes(r).Push(typeModel)
resp := newEventTypeResponse(res[0], r.Header.Get(langHeader))
resp := newEventTypeResponse(updated[0], r.Header.Get(langHeader))
resp.Data.Attributes.QrCodeValue = typeModel.QRCodeValue
ape.Render(w, resp)
}
10 changes: 8 additions & 2 deletions internal/service/requests/create_event_type.go
Original file line number Diff line number Diff line change
@@ -19,17 +19,23 @@ func NewCreateEventType(r *http.Request) (req resources.EventTypeResponse, err e
attr := req.Data.Attributes
return req, val.Errors{
// only QR code events can be currently created or updated
// localization is not supported currently
"data/id": val.Validate(req.Data.ID, val.Required),
"data/type": val.Validate(req.Data.Type, val.Required, val.In(resources.EVENT_TYPE)),
"data/attributes/action_url": val.Validate(attr.ActionUrl, is.URL),
"data/attributes/description": val.Validate(attr.Description, val.Required),
"data/attributes/frequency": val.Validate(attr.Frequency, val.Required, val.In(string(models.Unlimited))),
"data/attributes/logo": val.Validate(attr.Logo, is.URL),
"data/attributes/name": val.Validate(attr.Name, val.Required, val.In(req.Data.ID)),
"data/attributes/flag": val.Validate(attr.Flag, val.Empty),
"data/attributes/qr_code_value": val.Validate(attr.QrCodeValue, val.Required),
"data/attributes/qr_code_value": val.Validate(attr.QrCodeValue, val.Required, is.Base64),
"data/attributes/reward": val.Validate(attr.Reward, val.Required, val.Min(1)),
"data/attributes/short_description": val.Validate(attr.ShortDescription, val.Required),
"data/attributes/title": val.Validate(attr.Title, val.Required),
// these fields are not currently supported, because cron jobs implementation is required
"data/attributes/starts_at": val.Validate(attr.StartsAt, val.Empty),
"data/attributes/expires_at": val.Validate(attr.ExpiresAt, val.Empty),
// read-only fields due to reusing the same model
"data/attributes/flag": val.Validate(attr.Flag, val.Empty),
"data/attributes/usage_count": val.Validate(attr.UsageCount, val.Empty),
}.Filter()
}
8 changes: 8 additions & 0 deletions internal/service/requests/update_event_type.go
Original file line number Diff line number Diff line change
@@ -28,5 +28,13 @@ func NewUpdateEventType(r *http.Request) (req resources.EventTypeResponse, err e
"data/attributes/frequency": val.Validate(attr.Frequency, val.In(string(models.Unlimited))),
"data/attributes/logo": val.Validate(attr.Logo, is.URL),
"data/attributes/reward": val.Validate(attr.Reward, val.Min(1)),
// not updatable, as QR code includes event type name
"data/attributes/qr_code_value": val.Validate(attr.QrCodeValue, val.Empty),
// these fields are not currently supported, because cron jobs implementation is required
"data/attributes/starts_at": val.Validate(attr.StartsAt, val.Empty),
"data/attributes/expires_at": val.Validate(attr.ExpiresAt, val.Empty),
// read-only fields due to reusing the same model
"data/attributes/flag": val.Validate(attr.Flag, val.Empty),
"data/attributes/usage_count": val.Validate(attr.UsageCount, val.Empty),
}.Filter()
}

0 comments on commit 86ff2f1

Please sign in to comment.