-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.go
executable file
·156 lines (131 loc) · 3.92 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package main
import (
"encoding/base64"
"encoding/json"
"log"
"net/http"
"reflect"
"strconv"
"strings"
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
)
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func getIDFromRequest(request *http.Request) string {
params := mux.Vars(request)
id := params["id"]
return id
}
type dbResponse struct {
*gorm.DB
Cursor string
Count uint
}
func decodeCursor(encodedCursor string) string {
decoded, _ := base64.StdEncoding.DecodeString(encodedCursor)
return string(decoded)
}
func GetEvents(w http.ResponseWriter, r *http.Request) {
db := getDB()
defer db.Close()
var events = []Event{}
var results *gorm.DB
// 1) Offset + limit pagination - inefficient and prone to error
offset := r.FormValue("offset")
if offset != "" {
db = db.Offset(offset)
}
// 2) Cursor (keyset, seek) pagination - efficient and reliable,
// but pushes some logic to the client, and does not support flexible ordering
cursor := r.FormValue("cursor")
if cursor != "" {
// The cursor in this case is a base64-encoded composite key, made up of the
// 1) created_at and 2) id of the last record.
// This is to ensure that we have unique cursors (there can be records with the same created_at)
// NOTE: this implementation exposes the internal UUID of records in an encoded form -
// this is not a good practice and ideally it would be encrypted before sending to the client,
// and decrypted when received.
decoded := decodeCursor(cursor)
createdAt := strings.Split(decoded, "#")[0]
id := strings.Split(decoded, "#")[1]
db = db.Where("created_at >= ? and (id > ? or created_at > ?)", createdAt, id, createdAt)
// We have to ensure consistent ordering for this to work
db = db.Order("created_at asc").Order("id asc")
}
if offset == "" && cursor == "" {
db = db.Order("created_at asc").Order("id asc")
}
db.LogMode(true)
orderby := r.FormValue("orderby")
if orderby != "" {
// WARNING: don't do this in production ever - don't accept arbitrary values and pass them to SQL as here.
// This is used only for making experimentation with ordering easier.
db = db.Order(orderby)
}
limit := r.FormValue("limit")
if limit != "" {
db = db.Limit(limit)
} else {
db = db.Limit(10000)
}
count := GetEventCount()
results = db.Find(&events)
var dbResp dbResponse
if len(events) == 0 {
dbResp = dbResponse{DB: results, Cursor: "", Count: count}
} else {
lastRecord := &events[len(events)-1]
lastCreatedAt := lastRecord.CreatedAt.Format("2006-01-02T15:04:05.000000")
lastId := lastRecord.ID
newCursor := lastCreatedAt + "#" + lastId.String()
dbResp = dbResponse{
DB: results,
Cursor: base64.StdEncoding.EncodeToString([]byte(newCursor)),
Count: count}
}
json.NewEncoder(w).Encode(dbResp)
}
func GetEventCount() uint {
db := getDB()
defer db.Close()
var count uint
db.Model(&Event{}).Count(&count)
return count
}
func UpdateEvent(w http.ResponseWriter, r *http.Request) {
id := getIDFromRequest(r)
db := getDB()
defer db.Close()
var event Event
db.First(&event, "id = ?", id)
s := reflect.ValueOf(&event).Elem()
typeOfT := s.Type()
var toUpdate = make(map[string]interface{})
for i := 0; i < s.NumField(); i++ {
attributeName := strings.ToLower(typeOfT.Field(i).Name)
postValue := r.PostFormValue(attributeName)
if postValue != "" {
toUpdate[attributeName] = postValue
}
}
log.Printf("to update: %v", toUpdate)
db.Model(event).Updates(toUpdate)
result := db.First(&event, "id = ?", id)
json.NewEncoder(w).Encode(result)
}
func createFakeEvent(w http.ResponseWriter, r *http.Request) {
count, _ := strconv.Atoi(r.FormValue("count"))
generateFakeEvent(count)
}
func main() {
router := mux.NewRouter()
// Events
router.HandleFunc("/events", GetEvents).Methods("GET")
router.HandleFunc("/events/{id}", UpdateEvent).Methods("PUT")
router.HandleFunc("/fakedata", createFakeEvent).Methods("POST")
log.Fatal(http.ListenAndServe(":8000", router))
}