diff options
| author | Zach Berwaldt <zberwaldt@tutamail.com> | 2024-03-06 21:53:07 -0500 |
|---|---|---|
| committer | Zach Berwaldt <zberwaldt@tutamail.com> | 2024-03-06 21:53:07 -0500 |
| commit | 5fa57845052655883120ba4d19a85d8756fb8d8c (patch) | |
| tree | d2d5ad1dd3fd8d9acaca9ced09612b50218f06b0 /api | |
| parent | cf2113e77edabf8e3a632c7b76c769752039ba88 (diff) | |
[FEAT] Refactor API main file and models
This commit refactors the `main.go` file in the API directory, as well as the related models in the `models.go` file. The changes include:
- Reordering imports and removing unnecessary imports
- Fixing error messages to be more descriptive
- Handling database connections more efficiently with deferred closures
- Handling errors and returning appropriate error responses
- Adding proper JSON bindings for POST requests
- Adding new views in the database scripts for aggregated statistics and daily user statistics
No changes were made to imports and requires.
Diffstat (limited to 'api')
| -rw-r--r-- | api/main.go | 215 | ||||
| -rw-r--r-- | api/models.go (renamed from api/lib/models.go) | 18 |
2 files changed, 138 insertions, 95 deletions
diff --git a/api/main.go b/api/main.go index 57feb09..17a3c3a 100644 --- a/api/main.go +++ b/api/main.go | |||
| @@ -1,18 +1,17 @@ | |||
| 1 | package main | 1 | package main |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "net/http" | ||
| 5 | "crypto/rand" | 4 | "crypto/rand" |
| 6 | "encoding/base64" | ||
| 7 | "database/sql" | 5 | "database/sql" |
| 8 | "strings" | 6 | "encoding/base64" |
| 9 | "errors" | 7 | "errors" |
| 10 | "log" | 8 | "log" |
| 9 | "net/http" | ||
| 10 | "strings" | ||
| 11 | 11 | ||
| 12 | "github.com/gin-gonic/gin" | 12 | "github.com/gin-gonic/gin" |
| 13 | _ "github.com/mattn/go-sqlite3" | 13 | _ "github.com/mattn/go-sqlite3" |
| 14 | "golang.org/x/crypto/bcrypt" | 14 | "golang.org/x/crypto/bcrypt" |
| 15 | "water/api/lib" | ||
| 16 | ) | 15 | ) |
| 17 | 16 | ||
| 18 | func CORSMiddleware() gin.HandlerFunc { | 17 | func CORSMiddleware() gin.HandlerFunc { |
| @@ -22,15 +21,12 @@ func CORSMiddleware() gin.HandlerFunc { | |||
| 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") | 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") |
| 23 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") | 22 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") |
| 24 | 23 | ||
| 25 | log.Println("I am here") | ||
| 26 | |||
| 27 | if c.Request.Method == "OPTIONS" { | 24 | if c.Request.Method == "OPTIONS" { |
| 28 | log.Println(c.Request.Header) | 25 | log.Println(c.Request.Header) |
| 29 | c.AbortWithStatus(204) | 26 | c.AbortWithStatus(http.StatusNoContent) |
| 30 | return | 27 | return |
| 31 | } | 28 | } |
| 32 | 29 | ||
| 33 | log.Println(c.Request.Header) | ||
| 34 | c.Next() | 30 | c.Next() |
| 35 | } | 31 | } |
| 36 | } | 32 | } |
| @@ -38,7 +34,10 @@ func CORSMiddleware() gin.HandlerFunc { | |||
| 38 | // generatToken will g | 34 | // generatToken will g |
| 39 | func generateToken() string { | 35 | func generateToken() string { |
| 40 | token := make([]byte, 32) | 36 | token := make([]byte, 32) |
| 41 | rand.Read(token) | 37 | _, err := rand.Read(token) |
| 38 | if err != nil { | ||
| 39 | return "" | ||
| 40 | } | ||
| 42 | return base64.StdEncoding.EncodeToString(token) | 41 | return base64.StdEncoding.EncodeToString(token) |
| 43 | } | 42 | } |
| 44 | 43 | ||
| @@ -53,13 +52,13 @@ func establishDBConnection() *sql.DB { | |||
| 53 | func checkForTokenInContext(c *gin.Context) (string, error) { | 52 | func checkForTokenInContext(c *gin.Context) (string, error) { |
| 54 | authorizationHeader := c.GetHeader("Authorization") | 53 | authorizationHeader := c.GetHeader("Authorization") |
| 55 | if authorizationHeader == "" { | 54 | if authorizationHeader == "" { |
| 56 | return "", errors.New("Authorization header is missing") | 55 | return "", errors.New("authorization header is missing") |
| 57 | } | 56 | } |
| 58 | 57 | ||
| 59 | parts := strings.Split(authorizationHeader, " ") | 58 | parts := strings.Split(authorizationHeader, " ") |
| 60 | 59 | ||
| 61 | if len(parts) != 2 || parts[0] != "Bearer" { | 60 | if len(parts) != 2 || parts[0] != "Bearer" { |
| 62 | return "", errors.New("Invalid Authorization header format") | 61 | return "", errors.New("invalid Authorization header format") |
| 63 | } | 62 | } |
| 64 | 63 | ||
| 65 | return parts[1], nil | 64 | return parts[1], nil |
| @@ -98,15 +97,21 @@ func setupRouter() *gin.Engine { | |||
| 98 | } | 97 | } |
| 99 | 98 | ||
| 100 | db := establishDBConnection() | 99 | db := establishDBConnection() |
| 101 | defer db.Close() | 100 | defer func(db *sql.DB) { |
| 101 | err := db.Close() | ||
| 102 | if err != nil { | ||
| 103 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 104 | return | ||
| 105 | } | ||
| 106 | }(db) | ||
| 102 | 107 | ||
| 103 | var user models.User | 108 | var user User |
| 104 | var preference models.Preference | 109 | var preference Preference |
| 105 | var size models.Size | 110 | var size Size |
| 106 | 111 | ||
| 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) | 112 | 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) |
| 108 | if err := row.Scan(&user.Name, &user.UUID, &user.Password, &preference.Color, &size.Size, &size.Unit); err != nil { | 113 | if err := row.Scan(&user.Name, &user.UUID, &user.Password, &preference.Color, &size.Size, &size.Unit); err != nil { |
| 109 | if err == sql.ErrNoRows { | 114 | if errors.Is(err, sql.ErrNoRows) { |
| 110 | c.AbortWithStatus(http.StatusUnauthorized) | 115 | c.AbortWithStatus(http.StatusUnauthorized) |
| 111 | return | 116 | return |
| 112 | } | 117 | } |
| @@ -129,128 +134,147 @@ func setupRouter() *gin.Engine { | |||
| 129 | { | 134 | { |
| 130 | stats.GET("/", func(c *gin.Context) { | 135 | stats.GET("/", func(c *gin.Context) { |
| 131 | db := establishDBConnection() | 136 | db := establishDBConnection() |
| 132 | defer db.Close() | 137 | defer func(db *sql.DB) { |
| 138 | err := db.Close() | ||
| 139 | if err != nil { | ||
| 140 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 141 | return | ||
| 142 | } | ||
| 143 | }(db) | ||
| 133 | 144 | ||
| 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") | 145 | 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") |
| 135 | if err != nil { | 146 | if err != nil { |
| 136 | c.JSON(500, gin.H{"error": err.Error()}) | 147 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| 137 | return | 148 | return |
| 138 | } | 149 | } |
| 139 | defer rows.Close() | 150 | defer func(rows *sql.Rows) { |
| 151 | err := rows.Close() | ||
| 152 | if err != nil { | ||
| 153 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 154 | return | ||
| 155 | } | ||
| 156 | }(rows) | ||
| 140 | 157 | ||
| 141 | var data []models.Statistic | 158 | var data []Statistic |
| 142 | 159 | ||
| 143 | for rows.Next() { | 160 | for rows.Next() { |
| 144 | var stat models.Statistic | 161 | var stat Statistic |
| 145 | var user models.User | 162 | var user User |
| 146 | if err := rows.Scan(&stat.Date, &stat.Quantity, &user.UUID, &user.Name); err != nil { | 163 | if err := rows.Scan(&stat.Date, &stat.Quantity, &user.UUID, &user.Name); err != nil { |
| 147 | c.JSON(500, gin.H{"error": err.Error()}) | 164 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| 148 | return | 165 | return |
| 149 | } | 166 | } |
| 150 | stat.User = user | 167 | stat.User = user |
| 151 | data = append(data, stat) | 168 | data = append(data, stat) |
| 152 | } | 169 | } |
| 153 | 170 | ||
| 171 | c.JSON(http.StatusOK, data) | ||
| 172 | }) | ||
| 154 | 173 | ||
| 155 | // TODO: return to this and figure out how to best collect the data you are looking for for each user (zach and parker) | 174 | 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)") | 175 | var stat StatisticPost |
| 157 | if err != nil { | 176 | |
| 158 | c.JSON(500, gin.H{"error": err.Error()}) | 177 | if err := c.BindJSON(&stat); err != nil { |
| 178 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | ||
| 159 | return | 179 | return |
| 160 | } | 180 | } |
| 161 | defer rows.Close() | ||
| 162 | 181 | ||
| 163 | var dailySummaries []models.DailySummary | 182 | db := establishDBConnection() |
| 164 | for rows.Next() { | 183 | defer func(db *sql.DB) { |
| 165 | var summary models.DailySummary | 184 | err := db.Close() |
| 166 | if err := rows.Scan(&summary.Date, &summary.Total); err != nil { | 185 | if err != nil { |
| 167 | c.JSON(500, gin.H{"error": err.Error()}) | 186 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| 168 | return | 187 | return |
| 169 | } | 188 | } |
| 170 | dailySummaries = append(dailySummaries, summary) | 189 | }(db) |
| 171 | } | ||
| 172 | 190 | ||
| 173 | c.JSON(http.StatusOK, gin.H{"stats": data, "totals": dailySummaries}) | 191 | result, err := db.Exec("INSERT INTO statistics (date, user_id, quantity) values (?, ?, ?)", stat.Date, stat.UserID, stat.Quantity) |
| 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") | ||
| 175 | 192 | ||
| 176 | if err != nil { | 193 | if err != nil { |
| 177 | c.JSON(500, gin.H{"error": err.Error()}) | 194 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| 178 | return | ||
| 179 | } | 195 | } |
| 180 | defer rows.Close() | ||
| 181 | 196 | ||
| 182 | var totals []interface{} | 197 | id, err := result.LastInsertId() |
| 183 | for rows.Next() { | 198 | if err != nil { |
| 184 | var stat models.Statistic | 199 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) |
| 185 | var user models.User | ||
| 186 | if err := rows.Scan(&stat.Date, &stat.Quantity, &user.UUID, &user.Name); err != nil { | ||
| 187 | c.JSON(500, gin.H{"error": err.Error()}) | ||
| 188 | return | ||
| 189 | } | ||
| 190 | stat.User = user | ||
| 191 | totals = append(totals, stat) | ||
| 192 | } | 200 | } |
| 193 | 201 | ||
| 194 | c.JSON(http.StatusOK, gin.H{"stats": data, "totals": totals}) | 202 | c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) |
| 195 | }) | 203 | }) |
| 196 | 204 | ||
| 197 | stats.POST("/", func(c *gin.Context) { | 205 | stats.GET("weekly/", func(c *gin.Context) { |
| 198 | var stat models.Statistic | 206 | db := establishDBConnection() |
| 207 | defer func(db *sql.DB) { | ||
| 208 | err := db.Close() | ||
| 209 | if err != nil { | ||
| 210 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 211 | return | ||
| 212 | } | ||
| 213 | }(db) | ||
| 199 | 214 | ||
| 200 | if err := c.BindJSON(&stat); err != nil { | 215 | rows, err := db.Query("SELECT date, total FROM `WeeklyStatisticsView`") |
| 201 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | 216 | if err != nil { |
| 217 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 202 | return | 218 | return |
| 203 | } | 219 | } |
| 220 | defer func(rows *sql.Rows) { | ||
| 221 | err := rows.Close() | ||
| 222 | if err != nil { | ||
| 223 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 224 | return | ||
| 225 | } | ||
| 226 | }(rows) | ||
| 227 | |||
| 228 | var data []WeeklyStatistic | ||
| 229 | for rows.Next() { | ||
| 230 | var weeklyStat WeeklyStatistic | ||
| 231 | if err := rows.Scan(&weeklyStat.Date, &weeklyStat.Total); err != nil { | ||
| 232 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 233 | } | ||
| 234 | data = append(data, weeklyStat) | ||
| 235 | } | ||
| 236 | |||
| 237 | c.JSON(http.StatusOK, data) | ||
| 238 | }) | ||
| 204 | 239 | ||
| 240 | stats.GET("totals/", func(c *gin.Context) { | ||
| 205 | db := establishDBConnection() | 241 | db := establishDBConnection() |
| 206 | defer db.Close() | 242 | defer func(db *sql.DB) { |
| 243 | err := db.Close() | ||
| 244 | if err != nil { | ||
| 245 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 246 | return | ||
| 247 | } | ||
| 248 | }(db) | ||
| 207 | 249 | ||
| 208 | result, err := db.Exec("INSERT INTO statistics (date, user_id, quantity) values (?, ?, ?)", stat.Date, 1, stat.Quantity) | 250 | rows, err := db.Query("SELECT name, total FROM DailyUserStatistics") |
| 209 | 251 | ||
| 210 | if err != nil { | 252 | if err != nil { |
| 211 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | 253 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| 254 | return | ||
| 212 | } | 255 | } |
| 256 | defer func(rows *sql.Rows) { | ||
| 257 | err := rows.Close() | ||
| 258 | if err != nil { | ||
| 259 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 260 | return | ||
| 261 | } | ||
| 262 | }(rows) | ||
| 213 | 263 | ||
| 214 | id, err := result.LastInsertId() | 264 | var data []DailyUserTotals |
| 215 | if err != nil { | 265 | for rows.Next() { |
| 216 | c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) | 266 | var stat DailyUserTotals |
| 267 | if err := rows.Scan(&stat.Name, &stat.Total); err != nil { | ||
| 268 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 269 | return | ||
| 270 | } | ||
| 271 | data = append(data, stat) | ||
| 217 | } | 272 | } |
| 218 | 273 | ||
| 219 | c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) | 274 | c.JSON(http.StatusOK, data) |
| 220 | }) | ||
| 221 | 275 | ||
| 222 | stats.GET("/totals/", func(c *gin.Context) { | ||
| 223 | c.JSON(http.StatusOK, gin.H{"status": "ok"}) | ||
| 224 | }) | 276 | }) |
| 225 | 277 | ||
| 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) { | 278 | stats.GET("user/:uuid", func(c *gin.Context) { |
| 255 | c.JSON(http.StatusOK, gin.H{"status": "ok", "uuid": c.Param("uuid")}) | 279 | c.JSON(http.StatusOK, gin.H{"status": "ok", "uuid": c.Param("uuid")}) |
| 256 | }) | 280 | }) |
| @@ -270,5 +294,8 @@ func setupRouter() *gin.Engine { | |||
| 270 | func main() { | 294 | func main() { |
| 271 | r := setupRouter() | 295 | r := setupRouter() |
| 272 | // Listen and Server in 0.0.0.0:8080 | 296 | // Listen and Server in 0.0.0.0:8080 |
| 273 | r.Run(":8080") | 297 | err := r.Run(":8080") |
| 298 | if err != nil { | ||
| 299 | return | ||
| 300 | } | ||
| 274 | } | 301 | } |
diff --git a/api/lib/models.go b/api/models.go index f959519..0845d1d 100644 --- a/api/lib/models.go +++ b/api/models.go | |||
| @@ -1,4 +1,4 @@ | |||
| 1 | package models | 1 | package main |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "time" | 4 | "time" |
| @@ -12,6 +12,12 @@ type Statistic struct { | |||
| 12 | Quantity int `json:"quantity"` | 12 | Quantity int `json:"quantity"` |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | type StatisticPost struct { | ||
| 16 | Date time.Time `json:"date"` | ||
| 17 | Quantity int64 `json:"quantity"` | ||
| 18 | UserID int64 `json:"user_id"` | ||
| 19 | } | ||
| 20 | |||
| 15 | type User struct { | 21 | type User struct { |
| 16 | ID int64 `json:"-"` | 22 | ID int64 `json:"-"` |
| 17 | Name string `json:"name"` | 23 | Name string `json:"name"` |
| @@ -39,3 +45,13 @@ type Size struct { | |||
| 39 | Size int64 `json:"size"` | 45 | Size int64 `json:"size"` |
| 40 | Unit string `json:"unit"` | 46 | Unit string `json:"unit"` |
| 41 | } | 47 | } |
| 48 | |||
| 49 | type WeeklyStatistic struct { | ||
| 50 | Date string `json:"date"` | ||
| 51 | Total int64 `json:"total"` | ||
| 52 | } | ||
| 53 | |||
| 54 | type DailyUserTotals struct { | ||
| 55 | Name string `json:"name"` | ||
| 56 | Total int64 `json:"total"` | ||
| 57 | } \ No newline at end of file | ||
