Skip to content

Commit a2668d9

Browse files
authored
Event Code handling (HackIllinois#378)
* Generate event codes on startup and set invalid end time * List of profile events, modify code times, event code logic * Created profile eventlist modification * Documentation for event code generation and checkin endpoints * Alter checkin convention to post requests (both internal and external)
1 parent c59c574 commit a2668d9

File tree

17 files changed

+596
-5
lines changed

17 files changed

+596
-5
lines changed

common/utils/uuid.go

+6
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,9 @@ func GenerateUniqueID() string {
1010
rand.Read(id)
1111
return hex.EncodeToString(id)
1212
}
13+
14+
func GenerateUniqueCode() string {
15+
id := make([]byte, 3)
16+
rand.Read(id)
17+
return hex.EncodeToString(id)
18+
}

documentation/docs/reference/services/Event.md

+67
Original file line numberDiff line numberDiff line change
@@ -361,3 +361,70 @@ Response format:
361361
]
362362
}
363363
```
364+
365+
366+
367+
GET /event/code/{id}/
368+
----------------------------
369+
370+
Gets a struct that contains information about the event code (generated upon event creation) and expiration time.
371+
By convention, event checkin codes will be 6 bytes long.
372+
373+
Response format:
374+
```
375+
{
376+
"id": "52fdfc072182654f163f5f0f9a621d72",
377+
"code": "sample_code",
378+
"expiration": 1521388800
379+
}
380+
381+
```
382+
383+
PUT /event/code/{id}/
384+
----------------------------
385+
386+
Updates a struct that contains information about the event code (generated upon event creation) and expiration time.
387+
388+
Request format:
389+
```
390+
{
391+
"id": "52fdfc072182654f163f5f0f9a621d72",
392+
"code": "new_code",
393+
"expiration": 1521388800
394+
}
395+
396+
```
397+
398+
Response format:
399+
```
400+
{
401+
"id": "52fdfc072182654f163f5f0f9a621d72",
402+
"code": "new_code",
403+
"expiration": 1521388800
404+
}
405+
```
406+
407+
408+
POST /event/checkin/
409+
----------------------------
410+
411+
Retrieves a struct that contains information about the event checkin status, point increment value, and total point number.
412+
Takes in a struct that contains an event checkin code.
413+
414+
Request format:
415+
```
416+
{
417+
"code": "new_code",
418+
}
419+
420+
```
421+
422+
423+
Response format:
424+
```
425+
{
426+
"newPoints": 10,
427+
"totalPoints": 10,
428+
"status": "Success"
429+
}
430+
```

documentation/docs/reference/services/Profile.md

+56
Original file line numberDiff line numberDiff line change
@@ -299,3 +299,59 @@ Response format:
299299
}
300300
```
301301

302+
POST /profile/event/checkin/
303+
----------------------------
304+
305+
Validates the status of an event that the user is trying to check into.
306+
This is an internal endpoint hit during the checkin process (when the user posts a code to the event service).
307+
The response is a status string, and throws an error (except the case when the user is already checked in).
308+
In the case that the user has already been checked, status is set to "Event already redeemed" and a 200 status code is still used.
309+
310+
Request format:
311+
```
312+
{
313+
"id": "github123456",
314+
"eventID": "52fdfc072182654f163f5f0f9a621d72"
315+
}
316+
317+
```
318+
319+
320+
Response format:
321+
```
322+
{
323+
"status": "Success"
324+
}
325+
```
326+
327+
POST /profile/points/award/
328+
----------------------------
329+
330+
Takes a struct with a profile and a certain number of points to increment their score by, and returns this profile upon completion.
331+
332+
Request format:
333+
```
334+
{
335+
"id": "github123456",
336+
"points": 10
337+
}
338+
339+
```
340+
341+
342+
Response format:
343+
```
344+
{
345+
"id": "github123456",
346+
"firstName": "John",
347+
"lastName": "Doe",
348+
"points": 2021,
349+
"timezone": "Americas UTC+8",
350+
"avatarUrl": "https://github.com/.../profile.jpg",
351+
"discord": "patrick#1234",
352+
"teamStatus": "LOOKING_FOR_TEAM",
353+
"description": "Lorem Ipsum…",
354+
"interests": ["C++", "Machine Learning"]
355+
"points": 10
356+
}
357+
```

gateway/services/event.go

+30
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,24 @@ var EventRoutes = arbor.RouteCollection{
8585
"/event/",
8686
alice.New(middleware.AuthMiddleware([]models.Role{models.AdminRole}), middleware.IdentificationMiddleware).ThenFunc(UpdateEvent).ServeHTTP,
8787
},
88+
arbor.Route{
89+
"GetEventCode",
90+
"GET",
91+
"/event/code/{id}/",
92+
alice.New(middleware.AuthMiddleware([]models.Role{models.AdminRole, models.StaffRole}), middleware.IdentificationMiddleware).ThenFunc(GetEventCode).ServeHTTP,
93+
},
94+
arbor.Route{
95+
"UpdateEventCode",
96+
"PUT",
97+
"/event/code/{id}/",
98+
alice.New(middleware.AuthMiddleware([]models.Role{models.AdminRole, models.StaffRole}), middleware.IdentificationMiddleware).ThenFunc(PutEventCode).ServeHTTP,
99+
},
100+
arbor.Route{
101+
"Checkin",
102+
"POST",
103+
"/event/checkin/",
104+
alice.New(middleware.AuthMiddleware([]models.Role{models.AdminRole, models.AttendeeRole, models.ApplicantRole, models.StaffRole, models.MentorRole}), middleware.IdentificationMiddleware).ThenFunc(Checkin).ServeHTTP,
105+
},
88106
}
89107

90108
func GetEvent(w http.ResponseWriter, r *http.Request) {
@@ -107,6 +125,18 @@ func UpdateEvent(w http.ResponseWriter, r *http.Request) {
107125
arbor.PUT(w, config.EVENT_SERVICE+r.URL.String(), EventFormat, "", r)
108126
}
109127

128+
func GetEventCode(w http.ResponseWriter, r *http.Request) {
129+
arbor.GET(w, config.EVENT_SERVICE+r.URL.String(), EventFormat, "", r)
130+
}
131+
132+
func PutEventCode(w http.ResponseWriter, r *http.Request) {
133+
arbor.PUT(w, config.EVENT_SERVICE+r.URL.String(), EventFormat, "", r)
134+
}
135+
136+
func Checkin(w http.ResponseWriter, r *http.Request) {
137+
arbor.POST(w, config.EVENT_SERVICE+r.URL.String(), EventFormat, "", r)
138+
}
139+
110140
func MarkUserAsAttendingEvent(w http.ResponseWriter, r *http.Request) {
111141
arbor.POST(w, config.EVENT_SERVICE+r.URL.String(), EventFormat, "", r)
112142
}

gateway/services/profile.go

+20
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ var ProfileRoutes = arbor.RouteCollection{
6161
"/profile/filtered/",
6262
alice.New(middleware.AuthMiddleware([]models.Role{models.AdminRole, models.StaffRole}), middleware.IdentificationMiddleware).ThenFunc(GetFilteredProfiles).ServeHTTP,
6363
},
64+
arbor.Route{
65+
"RedeemEvent",
66+
"POST",
67+
"/profile/event/checkin/",
68+
alice.New(middleware.AuthMiddleware([]models.Role{models.AdminRole, models.StaffRole}), middleware.IdentificationMiddleware).ThenFunc(RedeemEvent).ServeHTTP,
69+
},
70+
arbor.Route{
71+
"AwardPoints",
72+
"POST",
73+
"/profile/points/award/",
74+
alice.New(middleware.AuthMiddleware([]models.Role{models.AdminRole, models.StaffRole}), middleware.IdentificationMiddleware).ThenFunc(AwardPoints).ServeHTTP,
75+
},
6476
// This needs to be the last route in order to prevent endpoints like "search", "leaderboard" from accidentally being routed as the {id} variable.
6577
arbor.Route{
6678
"GetUserProfileById",
@@ -105,3 +117,11 @@ func DeleteProfile(w http.ResponseWriter, r *http.Request) {
105117
func GetProfileLeaderboard(w http.ResponseWriter, r *http.Request) {
106118
arbor.GET(w, config.PROFILE_SERVICE+r.URL.String(), ProfileFormat, "", r)
107119
}
120+
121+
func RedeemEvent(w http.ResponseWriter, r *http.Request) {
122+
arbor.POST(w, config.PROFILE_SERVICE+r.URL.String(), ProfileFormat, "", r)
123+
}
124+
125+
func AwardPoints(w http.ResponseWriter, r *http.Request) {
126+
arbor.POST(w, config.PROFILE_SERVICE+r.URL.String(), ProfileFormat, "", r)
127+
}

services/event/config/config.go

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ var EVENT_PORT string
1313

1414
var CHECKIN_SERVICE string
1515

16+
var PROFILE_SERVICE string
17+
1618
var EVENT_CHECKIN_TIME_RESTRICTED bool
1719

1820
func Initialize() error {
@@ -46,6 +48,12 @@ func Initialize() error {
4648
return err
4749
}
4850

51+
PROFILE_SERVICE, err = cfg_loader.Get("PROFILE_SERVICE")
52+
53+
if err != nil {
54+
return err
55+
}
56+
4957
checkin_time_res_str, err := cfg_loader.Get("EVENT_CHECKIN_TIME_RESTRICTED")
5058

5159
if err != nil {

services/event/controller/controller.go

+115-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ func SetupController(route *mux.Route) {
2424
router.HandleFunc("/", CreateEvent).Methods("POST")
2525
router.HandleFunc("/", UpdateEvent).Methods("PUT")
2626
router.HandleFunc("/", GetAllEvents).Methods("GET")
27+
router.HandleFunc("/code/{id}/", GetEventCode).Methods("GET")
28+
router.HandleFunc("/code/{id}/", UpdateEventCode).Methods("PUT")
29+
30+
router.HandleFunc("/checkin/", Checkin).Methods("POST")
2731

2832
router.HandleFunc("/track/", MarkUserAsAttendingEvent).Methods("POST")
2933
router.HandleFunc("/track/event/{id}/", GetEventTrackingInfo).Methods("GET")
@@ -103,8 +107,9 @@ func CreateEvent(w http.ResponseWriter, r *http.Request) {
103107
json.NewDecoder(r.Body).Decode(&event)
104108

105109
event.ID = utils.GenerateUniqueID()
110+
var code = utils.GenerateUniqueCode()
106111

107-
err := service.CreateEvent(event.ID, event)
112+
err := service.CreateEvent(event.ID, code, event)
108113

109114
if err != nil {
110115
errors.WriteError(w, r, errors.DatabaseError(err.Error(), "Could not create new event."))
@@ -145,6 +150,115 @@ func UpdateEvent(w http.ResponseWriter, r *http.Request) {
145150
json.NewEncoder(w).Encode(updated_event)
146151
}
147152

153+
/*
154+
Endpoint to get the code associated with an event (or nil)
155+
*/
156+
func GetEventCode(w http.ResponseWriter, r *http.Request) {
157+
id := mux.Vars(r)["id"]
158+
159+
code, err := service.GetEventCode(id)
160+
161+
if err != nil {
162+
errors.WriteError(w, r, errors.DatabaseError(err.Error(), "Failed to receive event code information from database"))
163+
return
164+
}
165+
166+
json.NewEncoder(w).Encode(code)
167+
}
168+
169+
/*
170+
Endpoint to update an event code and end time
171+
*/
172+
func UpdateEventCode(w http.ResponseWriter, r *http.Request) {
173+
var eventCode models.EventCode
174+
json.NewDecoder(r.Body).Decode(&eventCode)
175+
176+
err := service.UpdateEventCode(eventCode.ID, eventCode)
177+
178+
if err != nil {
179+
errors.WriteError(w, r, errors.DatabaseError(err.Error(), "Could not update the code and timestamp of the event."))
180+
return
181+
}
182+
183+
updated_event, err := service.GetEventCode(eventCode.ID)
184+
185+
if err != nil {
186+
errors.WriteError(w, r, errors.DatabaseError(err.Error(), "Could not get updated event code and timestamp details."))
187+
return
188+
}
189+
190+
json.NewEncoder(w).Encode(updated_event)
191+
}
192+
193+
/*
194+
Endpoint to get the code associated with an event (or nil)
195+
*/
196+
func Checkin(w http.ResponseWriter, r *http.Request) {
197+
id := r.Header.Get("HackIllinois-Identity")
198+
199+
if id == "" {
200+
errors.WriteError(w, r, errors.MalformedRequestError("Must provide id in request.", "Must provide id in request."))
201+
return
202+
}
203+
204+
var checkin_request models.CheckinRequest
205+
json.NewDecoder(r.Body).Decode(&checkin_request)
206+
207+
valid, event_id, err := service.CanRedeemPoints(checkin_request.Code)
208+
209+
if err != nil {
210+
errors.WriteError(w, r, errors.DatabaseError(err.Error(), "Failed to receive event code information from database"))
211+
return
212+
}
213+
214+
result := models.CheckinResult{
215+
NewPoints: -1,
216+
TotalPoints: -1,
217+
Status: "Success",
218+
}
219+
220+
if !valid {
221+
result.Status = "InvalidTime"
222+
}
223+
224+
redemption_status, err := service.RedeemEvent(id, event_id)
225+
226+
if err != nil || redemption_status == nil {
227+
errors.WriteError(w, r, errors.UnknownError(err.Error(), "Failed to verify if user already had redeemed event points"))
228+
return
229+
}
230+
231+
if redemption_status.Status != "Success" {
232+
result.NewPoints = 0
233+
result.Status = "AlreadyCheckedIn"
234+
json.NewEncoder(w).Encode(result)
235+
return
236+
}
237+
238+
// Determine the current event and its point value
239+
240+
event, err := service.GetEvent(event_id)
241+
242+
if err != nil {
243+
errors.WriteError(w, r, errors.DatabaseError(err.Error(), "Could not fetch the event details and point value."))
244+
return
245+
}
246+
247+
result.NewPoints = event.Points
248+
249+
// Add this point value to given profile
250+
profile, err := service.AwardPoints(id, event.Points)
251+
252+
if err != nil {
253+
errors.WriteError(w, r, errors.UnknownError(err.Error(), "Failed to award user with points"))
254+
return
255+
}
256+
257+
result.TotalPoints = profile.Points
258+
259+
json.NewEncoder(w).Encode(result)
260+
}
261+
148262
/*
149263
Endpoint to get tracking info by event
150264
*/

0 commit comments

Comments
 (0)