diff options
Diffstat (limited to 'api/main.go')
-rw-r--r-- | api/main.go | 384 |
1 files changed, 229 insertions, 155 deletions
diff --git a/api/main.go b/api/main.go index 91b7929..57feb09 100644 --- a/api/main.go +++ b/api/main.go | |||
@@ -2,193 +2,267 @@ package main | |||
2 | 2 | ||
3 | import ( | 3 | import ( |
4 | "net/http" | 4 | "net/http" |
5 | "crypto/rand" | 5 | "crypto/rand" |
6 | "encoding/base64" | 6 | "encoding/base64" |
7 | "database/sql" | 7 | "database/sql" |
8 | "strings" | 8 | "strings" |
9 | "errors" | 9 | "errors" |
10 | "log" | ||
10 | 11 | ||
11 | "github.com/gin-gonic/gin" | 12 | "github.com/gin-gonic/gin" |
12 | _ "github.com/mattn/go-sqlite3" | 13 | _ "github.com/mattn/go-sqlite3" |
13 | "golang.org/x/crypto/bcrypt" | 14 | "golang.org/x/crypto/bcrypt" |
14 | "water/api/lib" | 15 | "water/api/lib" |
15 | ) | 16 | ) |
16 | 17 | ||
17 | func CORSMiddleware() gin.HandlerFunc { | 18 | func CORSMiddleware() gin.HandlerFunc { |
18 | return func(c *gin.Context) { | 19 | return func(c *gin.Context) { |
19 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") | 20 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") |
20 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") | 21 | c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") |
21 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") | 22 | c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") |
22 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") | 23 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") |
23 | 24 | ||
24 | if c.Request.Method == "OPTIONS" { | 25 | log.Println("I am here") |
25 | c.AbortWithStatus(204) | 26 | |
26 | return | 27 | if c.Request.Method == "OPTIONS" { |
27 | } | 28 | log.Println(c.Request.Header) |
28 | 29 | c.AbortWithStatus(204) | |
29 | c.Next() | 30 | return |
30 | } | 31 | } |
32 | |||
33 | log.Println(c.Request.Header) | ||
34 | c.Next() | ||
35 | } | ||
31 | } | 36 | } |
32 | 37 | ||
33 | // generatToken will g | 38 | // generatToken will g |
34 | func generateToken() string { | 39 | func generateToken() string { |
35 | token := make([]byte, 32) | 40 | token := make([]byte, 32) |
36 | rand.Read(token) | 41 | rand.Read(token) |
37 | return base64.StdEncoding.EncodeToString(token) | 42 | return base64.StdEncoding.EncodeToString(token) |
38 | } | 43 | } |
39 | 44 | ||
40 | func establishDBConnection() *sql.DB { | 45 | func establishDBConnection() *sql.DB { |
41 | db, err := sql.Open("sqlite3", "../db/water.sqlite3") | 46 | db, err := sql.Open("sqlite3", "../db/water.sqlite3") |
42 | if err != nil { | 47 | if err != nil { |
43 | panic(err) | 48 | panic(err) |
44 | } | 49 | } |
45 | return db | 50 | return db |
46 | } | 51 | } |
47 | 52 | ||
48 | |||
49 | func checkForTokenInContext(c *gin.Context) (string, error) { | 53 | func checkForTokenInContext(c *gin.Context) (string, error) { |
50 | authorizationHeader := c.GetHeader("Authorization") | 54 | authorizationHeader := c.GetHeader("Authorization") |
51 | if authorizationHeader == "" { | 55 | if authorizationHeader == "" { |
52 | return "", errors.New("Authorization header is missing") | 56 | return "", errors.New("Authorization header is missing") |
53 | } | 57 | } |
54 | 58 | ||
55 | parts := strings.Split(authorizationHeader, " ") | 59 | parts := strings.Split(authorizationHeader, " ") |
56 | 60 | ||
57 | if len(parts) != 2 || parts[0] != "Bearer" { | 61 | if len(parts) != 2 || parts[0] != "Bearer" { |
58 | return "", errors.New("Invalid Authorization header format") | 62 | return "", errors.New("Invalid Authorization header format") |
59 | } | 63 | } |
60 | 64 | ||
61 | 65 | return parts[1], nil | |
62 | return parts[1], nil | ||
63 | } | 66 | } |
64 | 67 | ||
65 | |||
66 | func TokenRequired() gin.HandlerFunc { | 68 | func TokenRequired() gin.HandlerFunc { |
67 | return func(c *gin.Context) { | 69 | return func(c *gin.Context) { |
68 | _, err := checkForTokenInContext(c) | 70 | _, err := checkForTokenInContext(c) |
69 | 71 | ||
70 | if err != nil { | 72 | if err != nil { |
71 | c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) | 73 | c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) |
72 | c.Abort() | 74 | c.Abort() |
73 | return | 75 | return |
74 | } | 76 | } |
75 | 77 | ||
76 | c.Next() | 78 | c.Next() |
77 | } | 79 | } |
78 | } | 80 | } |
79 | 81 | ||
80 | func setupRouter() *gin.Engine { | 82 | func setupRouter() *gin.Engine { |
81 | // Disable Console Color | 83 | // Disable Console Color |
82 | // gin.DisableConsoleColor() | 84 | // gin.DisableConsoleColor() |
83 | r := gin.Default() | 85 | r := gin.Default() |
84 | r.Use(CORSMiddleware()) | 86 | r.Use(CORSMiddleware()) |
85 | r.Use(gin.Logger()) | 87 | r.Use(gin.Logger()) |
86 | r.Use(gin.Recovery()) | 88 | r.Use(gin.Recovery()) |
87 | 89 | ||
88 | api := r.Group("api/v1") | 90 | api := r.Group("api/v1") |
89 | 91 | ||
90 | api.POST("/auth", func(c *gin.Context) { | 92 | api.POST("/auth", func(c *gin.Context) { |
91 | username, password, ok := c.Request.BasicAuth() | 93 | username, password, ok := c.Request.BasicAuth() |
92 | if !ok { | 94 | if !ok { |
93 | c.Header("WWW-Authenticate", `Basic realm="Please enter your username and password."`) | 95 | c.Header("WWW-Authenticate", `Basic realm="Please enter your username and password."`) |
94 | c.AbortWithStatus(http.StatusUnauthorized) | 96 | c.AbortWithStatus(http.StatusUnauthorized) |
95 | return | 97 | return |
96 | } | 98 | } |
97 | 99 | ||
98 | db := establishDBConnection() | 100 | db := establishDBConnection() |
99 | defer db.Close() | 101 | defer db.Close() |
100 | 102 | ||
101 | var user models.User | 103 | var user models.User |
102 | var preference models.Preference | 104 | var preference models.Preference |
103 | var size models.Size | 105 | var size models.Size |
104 | 106 | ||
105 | row := db.QueryRow("SELECT name, uuid, password, color, size, unit FROM Users u INNER JOIN Preferences p ON p.user_id = u.id INNER JOIN Sizes s ON p.size_id = s.id WHERE u.name = ?", username) | 107 | row := db.QueryRow("SELECT name, uuid, password, color, size, unit FROM Users u INNER JOIN Preferences p ON p.user_id = u.id INNER JOIN Sizes s ON p.size_id = s.id WHERE u.name = ?", username) |
106 | if err := row.Scan(&user.Name, &user.UUID, &user.Password, &preference.Color, &size.Size, &size.Unit); err != nil { | 108 | if err := row.Scan(&user.Name, &user.UUID, &user.Password, &preference.Color, &size.Size, &size.Unit); err != nil { |
107 | if err == sql.ErrNoRows { | 109 | if err == sql.ErrNoRows { |
108 | c.AbortWithStatus(http.StatusUnauthorized) | 110 | c.AbortWithStatus(http.StatusUnauthorized) |
109 | return | 111 | return |
110 | } | 112 | } |
111 | } | 113 | } |
112 | 114 | ||
113 | if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { | 115 | if err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password)); err != nil { |
114 | c.AbortWithStatus(http.StatusUnauthorized) | 116 | c.AbortWithStatus(http.StatusUnauthorized) |
115 | return | 117 | return |
116 | } | 118 | } |
117 | 119 | ||
118 | preference.Size = size | 120 | preference.Size = size |
119 | 121 | ||
120 | // Generate a simple API token | 122 | // Generate a simple API token |
121 | apiToken := generateToken() | 123 | apiToken := generateToken() |
122 | c.JSON(http.StatusOK, gin.H{"token": apiToken, "user": user, "preferences": preference}) | 124 | c.JSON(http.StatusOK, gin.H{"token": apiToken, "user": user, "preferences": preference}) |
123 | }) | 125 | }) |
124 | 126 | ||
125 | stats := api.Group("stats") | 127 | stats := api.Group("/stats") |
126 | stats.Use(TokenRequired()) | 128 | stats.Use(TokenRequired()) |
127 | { | 129 | { |
128 | stats.GET("/", func(c *gin.Context) { | 130 | stats.GET("/", func(c *gin.Context) { |
129 | db := establishDBConnection() | 131 | db := establishDBConnection() |
130 | defer db.Close() | 132 | defer db.Close() |
131 | 133 | ||
132 | rows, err := db.Query("SELECT s.date, s.quantity, u.uuid, u.name FROM Statistics s INNER JOIN Users u ON u.id = s.user_id"); | 134 | rows, err := db.Query("SELECT s.date, s.quantity, u.uuid, u.name FROM Statistics s INNER JOIN Users u ON u.id = s.user_id") |
133 | if err != nil { | 135 | if err != nil { |
134 | c.JSON(500, gin.H{"error": err.Error()}) | 136 | c.JSON(500, gin.H{"error": err.Error()}) |
135 | return | 137 | return |
136 | } | 138 | } |
137 | defer rows.Close() | 139 | defer rows.Close() |
138 | 140 | ||
139 | var data []models.Statistic | 141 | var data []models.Statistic |
140 | for rows.Next() { | 142 | |
141 | var stat models.Statistic | 143 | for rows.Next() { |
142 | var user models.User | 144 | var stat models.Statistic |
143 | if err := rows.Scan(&stat.Date, &stat.Quantity, &user.UUID, &user.Name); err != nil { | 145 | var user models.User |
144 | c.JSON(500, gin.H{"error": err.Error()}) | 146 | if err := rows.Scan(&stat.Date, &stat.Quantity, &user.UUID, &user.Name); err != nil { |
145 | return | 147 | c.JSON(500, gin.H{"error": err.Error()}) |
146 | } | 148 | return |
147 | stat.User = user | 149 | } |
148 | data = append(data, stat) | 150 | stat.User = user |
149 | } | 151 | data = append(data, stat) |
150 | 152 | } | |
151 | c.JSON(http.StatusOK, data) | 153 | |
152 | }) | 154 | |
153 | 155 | // TODO: return to this and figure out how to best collect the data you are looking for for each user (zach and parker) | |
154 | stats.POST("/", func(c *gin.Context) { | 156 | rows, err = db.Query("SELECT date(s.date), SUM(s.quantity) as total FROM Statistics s WHERE s.date >= date('now', '-7 days') GROUP BY DATE(s.date)") |
155 | var stat models.Statistic | 157 | if err != nil { |
156 | 158 | c.JSON(500, gin.H{"error": err.Error()}) | |
157 | if err := c.BindJSON(&stat); err != nil { | 159 | return |
158 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | 160 | } |
159 | return | 161 | defer rows.Close() |
160 | } | 162 | |
161 | 163 | var dailySummaries []models.DailySummary | |
162 | db := establishDBConnection() | 164 | for rows.Next() { |
163 | defer db.Close() | 165 | var summary models.DailySummary |
164 | 166 | if err := rows.Scan(&summary.Date, &summary.Total); err != nil { | |
165 | result, err := db.Exec("INSERT INTO statistics (date, user_id, quantity) values (?, ?, ?)", stat.Date, 1, stat.Quantity) | 167 | c.JSON(500, gin.H{"error": err.Error()}) |
166 | 168 | return | |
167 | if err != nil { | 169 | } |
168 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | 170 | dailySummaries = append(dailySummaries, summary) |
169 | } | 171 | } |
170 | 172 | ||
171 | id, err := result.LastInsertId() | 173 | c.JSON(http.StatusOK, gin.H{"stats": data, "totals": dailySummaries}) |
172 | if err != nil { | 174 | rows, err = db.Query("SELECT s.date, SUM(s.quantity) as total, u.uuid, u.name FROM Statistics s INNER JOIN Users u ON u.id = s.user_id WHERE s.date >= date('now', '-7 days') GROUP BY s.date, s.user_id") |
173 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | 175 | |
174 | } | 176 | if err != nil { |
175 | 177 | c.JSON(500, gin.H{"error": err.Error()}) | |
176 | c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) | 178 | return |
177 | }) | 179 | } |
178 | 180 | defer rows.Close() | |
179 | stats.GET("/:uuid", func(c *gin.Context) { | 181 | |
180 | c.JSON(http.StatusOK, gin.H{"status": "ok", "uuid": c.Param("uuid")}) | 182 | var totals []interface{} |
181 | }) | 183 | for rows.Next() { |
182 | 184 | var stat models.Statistic | |
183 | stats.PATCH("/:uuid", func(c *gin.Context) { | 185 | var user models.User |
184 | c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) | 186 | if err := rows.Scan(&stat.Date, &stat.Quantity, &user.UUID, &user.Name); err != nil { |
185 | }) | 187 | c.JSON(500, gin.H{"error": err.Error()}) |
186 | 188 | return | |
187 | stats.DELETE("/:uuid", func(c *gin.Context) { | 189 | } |
188 | c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) | 190 | stat.User = user |
189 | }) | 191 | totals = append(totals, stat) |
190 | } | 192 | } |
191 | 193 | ||
194 | c.JSON(http.StatusOK, gin.H{"stats": data, "totals": totals}) | ||
195 | }) | ||
196 | |||
197 | stats.POST("/", func(c *gin.Context) { | ||
198 | var stat models.Statistic | ||
199 | |||
200 | if err := c.BindJSON(&stat); err != nil { | ||
201 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
202 | return | ||
203 | } | ||
204 | |||
205 | db := establishDBConnection() | ||
206 | defer db.Close() | ||
207 | |||
208 | result, err := db.Exec("INSERT INTO statistics (date, user_id, quantity) values (?, ?, ?)", stat.Date, 1, stat.Quantity) | ||
209 | |||
210 | if err != nil { | ||
211 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
212 | } | ||
213 | |||
214 | id, err := result.LastInsertId() | ||
215 | if err != nil { | ||
216 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
217 | } | ||
218 | |||
219 | c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) | ||
220 | }) | ||
221 | |||
222 | stats.GET("/totals/", func(c *gin.Context) { | ||
223 | c.JSON(http.StatusOK, gin.H{"status": "ok"}) | ||
224 | }) | ||
225 | |||
226 | // stats.GET("/totals/", func(c *gin.Context) { | ||
227 | // db := establishDBConnection() | ||
228 | // defer db.Close() | ||
229 | // | ||
230 | // rows, err := db.Query("SELECT s.date, SUM(s.quantity) as total, u.uuid, u.name FROM Statistics s INNER JOIN Users u ON u.id = s.user_id WHERE s.date >= date('now', '-7 days') GROUP BY s.date, s.user_id") | ||
231 | // | ||
232 | // if err != nil { | ||
233 | // c.JSON(500, gin.H{"error": err.Error()}) | ||
234 | // return | ||
235 | // } | ||
236 | // defer rows.Close() | ||
237 | // | ||
238 | // var data []models.Statistic | ||
239 | // for rows.Next() { | ||
240 | // var stat models.Statistic | ||
241 | // var user models.User | ||
242 | // if err := rows.Scan(&stat.Date, &stat.Quantity, &user.UUID, &user.Name); err != nil { | ||
243 | // c.JSON(500, gin.H{"error": err.Error()}) | ||
244 | // return | ||
245 | // } | ||
246 | // stat.User = user | ||
247 | // data = append(data, stat) | ||
248 | // } | ||
249 | // | ||
250 | // c.JSON(http.StatusOK, data) | ||
251 | // | ||
252 | // }) | ||
253 | |||
254 | stats.GET("user/:uuid", func(c *gin.Context) { | ||
255 | c.JSON(http.StatusOK, gin.H{"status": "ok", "uuid": c.Param("uuid")}) | ||
256 | }) | ||
257 | |||
258 | stats.PATCH("user/:uuid", func(c *gin.Context) { | ||
259 | c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) | ||
260 | }) | ||
261 | |||
262 | stats.DELETE("user/:uuid", func(c *gin.Context) { | ||
263 | c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) | ||
264 | }) | ||
265 | } | ||
192 | 266 | ||
193 | return r | 267 | return r |
194 | } | 268 | } |