Skip to content

Commit 75ceb96

Browse files
committed
feat: implement events
1 parent 5bf1af1 commit 75ceb96

File tree

5 files changed

+599
-0
lines changed

5 files changed

+599
-0
lines changed

internal/events/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Events
2+
3+
負責觸發事件和加減點數的 service。

internal/events/constants.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package events
2+
3+
type EventType string
4+
5+
const (
6+
EventTypeLogin EventType = "login"
7+
)

internal/events/events.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package events
2+
3+
import (
4+
"context"
5+
"log/slog"
6+
"time"
7+
8+
"github.com/database-playground/backend-v2/ent"
9+
)
10+
11+
// EventService is the service for triggering events.
12+
type EventService struct {
13+
entClient *ent.Client
14+
pointsGranter *PointsGranter
15+
}
16+
17+
// NewEventService creates a new EventService.
18+
func NewEventService(entClient *ent.Client) *EventService {
19+
return &EventService{
20+
entClient: entClient,
21+
pointsGranter: NewPointsGranter(entClient),
22+
}
23+
}
24+
25+
// Event is the event to be triggered.
26+
type Event struct {
27+
Type EventType
28+
Payload map[string]any
29+
UserID int
30+
}
31+
32+
// TriggerEvent triggers an event.
33+
func (s *EventService) TriggerEvent(ctx context.Context, event Event) error {
34+
err := s.entClient.Events.Create().
35+
SetType(string(event.Type)).
36+
SetPayload(event.Payload).
37+
SetUserID(event.UserID).
38+
SetTriggeredAt(time.Now()).
39+
Exec(ctx)
40+
if err != nil {
41+
return err
42+
}
43+
44+
if event.Type == EventTypeLogin {
45+
ok, err := s.pointsGranter.GrantDailyLoginPoints(ctx, event.UserID)
46+
if err != nil {
47+
return err
48+
}
49+
if ok {
50+
slog.Info("granted daily login points", "user_id", event.UserID)
51+
}
52+
}
53+
54+
return nil
55+
}

internal/events/points.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package events
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/database-playground/backend-v2/ent"
8+
"github.com/database-playground/backend-v2/ent/events"
9+
"github.com/database-playground/backend-v2/ent/points"
10+
"github.com/database-playground/backend-v2/ent/user"
11+
)
12+
13+
const (
14+
PointDescriptionDailyLogin = "daily login"
15+
PointDescriptionWeeklyLogin = "weekly login"
16+
)
17+
18+
const (
19+
PointValueDailyLogin = 20
20+
PointValueWeeklyLogin = 50
21+
)
22+
23+
// PointsGranter determines if the criteria is met to grant points to a user.
24+
type PointsGranter struct {
25+
entClient *ent.Client
26+
}
27+
28+
// NewPointsGranter creates a new PointsGranter.
29+
func NewPointsGranter(entClient *ent.Client) *PointsGranter {
30+
return &PointsGranter{
31+
entClient: entClient,
32+
}
33+
}
34+
35+
// GrantDailyLoginPoints grants the "daily login" points to a user.
36+
func (d *PointsGranter) GrantDailyLoginPoints(ctx context.Context, userID int) (bool, error) {
37+
// Check if we have granted the "daily login" points for this user today.
38+
hasPointsRecord, err := d.entClient.Points.Query().
39+
Where(points.HasUserWith(user.ID(userID))).
40+
Where(points.DescriptionEQ(PointDescriptionDailyLogin)).
41+
Where(points.CreatedAtGTE(time.Now().AddDate(0, 0, -1))).Exist(ctx)
42+
if err != nil {
43+
return false, err
44+
}
45+
if hasPointsRecord {
46+
return false, nil
47+
}
48+
49+
// Check if the user has logged in today.
50+
hasTodayLoginRecord, err := d.entClient.Events.Query().
51+
Where(events.Type(string(EventTypeLogin))).
52+
Where(events.UserID(userID)).
53+
Where(events.TriggeredAtGTE(time.Now().AddDate(0, 0, -1))).
54+
Exist(ctx)
55+
if err != nil {
56+
return false, err
57+
}
58+
if !hasTodayLoginRecord {
59+
return false, nil
60+
}
61+
62+
// Grant the "daily login" points to the user.
63+
err = d.entClient.Points.Create().
64+
SetUserID(userID).
65+
SetDescription(PointDescriptionDailyLogin).
66+
SetPoints(PointValueDailyLogin).
67+
Exec(ctx)
68+
if err != nil {
69+
return false, err
70+
}
71+
72+
return true, nil
73+
}
74+
75+
// GrantWeeklyLoginPoints grants the "weekly login" points to a user.
76+
func (d *PointsGranter) GrantWeeklyLoginPoints(ctx context.Context, userID int) (bool, error) {
77+
// Check if we have granted the "weekly login" points for this user this week.
78+
hasPointsRecord, err := d.entClient.Points.Query().
79+
Where(points.HasUserWith(user.ID(userID))).
80+
Where(points.DescriptionEQ(PointDescriptionWeeklyLogin)).
81+
Where(points.CreatedAtGTE(time.Now().AddDate(0, 0, -7))).Exist(ctx)
82+
if err != nil {
83+
return false, err
84+
}
85+
if hasPointsRecord {
86+
return false, nil
87+
}
88+
89+
// Check if the user has logged in every day this week.
90+
weekLoginRecords, err := d.entClient.Events.Query().
91+
Where(events.Type(string(EventTypeLogin))).
92+
Where(events.UserID(userID)).
93+
All(ctx)
94+
if err != nil {
95+
return false, err
96+
}
97+
98+
// aggreated by day
99+
weekLoginRecordsByDay := make(map[time.Time]int)
100+
for _, record := range weekLoginRecords {
101+
weekLoginRecordsByDay[record.TriggeredAt.Truncate(24*time.Hour)]++
102+
}
103+
104+
if len(weekLoginRecordsByDay) != 7 {
105+
return false, nil
106+
}
107+
108+
// Grant the "weekly login" points to the user.
109+
err = d.entClient.Points.Create().
110+
SetUserID(userID).
111+
SetDescription(PointDescriptionWeeklyLogin).
112+
SetPoints(PointValueWeeklyLogin).
113+
Exec(ctx)
114+
if err != nil {
115+
return false, err
116+
}
117+
return true, nil
118+
}

0 commit comments

Comments
 (0)