aboutsummaryrefslogtreecommitdiff
path: root/api/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'api/main.go')
-rw-r--r--api/main.go384
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
3import ( 3import (
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
17func CORSMiddleware() gin.HandlerFunc { 18func 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
34func generateToken() string { 39func 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
40func establishDBConnection() *sql.DB { 45func 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
49func checkForTokenInContext(c *gin.Context) (string, error) { 53func 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
66func TokenRequired() gin.HandlerFunc { 68func 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
80func setupRouter() *gin.Engine { 82func 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}