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 | |
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.
-rw-r--r-- | api/main.go | 215 | ||||
-rw-r--r-- | api/models.go (renamed from api/lib/models.go) | 18 | ||||
-rw-r--r-- | db/scripts/water_init.sql | 27 | ||||
-rw-r--r-- | fe/src/lib/DataView.svelte | 130 | ||||
-rw-r--r-- | fe/src/lib/forms/AddForm.svelte | 13 |
5 files changed, 241 insertions, 162 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 | ||
diff --git a/db/scripts/water_init.sql b/db/scripts/water_init.sql index 6a4de24..3b79ed5 100644 --- a/db/scripts/water_init.sql +++ b/db/scripts/water_init.sql | |||
@@ -18,7 +18,7 @@ CREATE TABLE IF NOT EXISTS Statistics ( | |||
18 | CREATE TABLE IF NOT EXISTS Preferences ( | 18 | CREATE TABLE IF NOT EXISTS Preferences ( |
19 | id INTEGER PRIMARY KEY, | 19 | id INTEGER PRIMARY KEY, |
20 | color TEXT NOT NULL DEFAULT "#000000", | 20 | color TEXT NOT NULL DEFAULT "#000000", |
21 | user_id INT NOT NULL, | 21 | user_id INT UNIQUE NOT NULL, |
22 | size_id INT NOT NULL DEFAULT 1, | 22 | size_id INT NOT NULL DEFAULT 1, |
23 | FOREIGN KEY(user_id) REFERENCES Users(id) ON DELETE CASCADE, | 23 | FOREIGN KEY(user_id) REFERENCES Users(id) ON DELETE CASCADE, |
24 | FOREIGN KEY(size_id) REFERENCES Sizes(id) | 24 | FOREIGN KEY(size_id) REFERENCES Sizes(id) |
@@ -70,3 +70,28 @@ END; | |||
70 | -- | 70 | -- |
71 | CREATE VIEW IF NOT EXISTS aggregated_stats AS | 71 | CREATE VIEW IF NOT EXISTS aggregated_stats AS |
72 | SELECT u.uuid, SUM(s.quantity * s.size) from Statistics s INNER JOIN Users u ON u.id = s.user_id INNER JOIN Preferences p ON p.user_id = u.id INNER JOIN Size s ON s.id = p.size_id; | 72 | SELECT u.uuid, SUM(s.quantity * s.size) from Statistics s INNER JOIN Users u ON u.id = s.user_id INNER JOIN Preferences p ON p.user_id = u.id INNER JOIN Size s ON s.id = p.size_id; |
73 | |||
74 | CREATE VIEW IF NOT EXISTS `DailyUserStatistics` AS | ||
75 | SELECT users.name, IFNULL(SUM(statistics.quantity), 0) as total, preferences.color as color | ||
76 | FROM users | ||
77 | LEFT JOIN statistics ON users.id = statistics.user_id AND DATE(statistics.date) = DATE('now', '-1 day') | ||
78 | LEFT JOIN preferences ON users.id = preferences.user_id | ||
79 | GROUP BY users.name; | ||
80 | |||
81 | |||
82 | CREATE VIEW IF NOT EXISTS `WeeklyStatisticsView` AS | ||
83 | WITH DateSequence(Dates) AS | ||
84 | ( | ||
85 | SELECT Date(CURRENT_DATE, '-7 day') | ||
86 | UNION ALL | ||
87 | SELECT Date(Dates, '+1 day') | ||
88 | FROM DateSequence | ||
89 | WHERE Date(Dates, '+1 day') < Date(CURRENT_DATE) | ||
90 | ) | ||
91 | SELECT DateSequence.Dates as 'date', | ||
92 | IFNULL(SUM(statistics.quantity), 0) AS 'total' | ||
93 | FROM DateSequence | ||
94 | LEFT JOIN statistics | ||
95 | ON Date(statistics.date) = DateSequence.Dates | ||
96 | GROUP BY DateSequence.Dates | ||
97 | ORDER BY DateSequence.Dates; \ No newline at end of file | ||
diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte index 2b1b8b9..7d62a43 100644 --- a/fe/src/lib/DataView.svelte +++ b/fe/src/lib/DataView.svelte | |||
@@ -9,8 +9,6 @@ | |||
9 | import AddForm from "./forms/AddForm.svelte"; | 9 | import AddForm from "./forms/AddForm.svelte"; |
10 | 10 | ||
11 | let json: Promise<any>; | 11 | let json: Promise<any>; |
12 | let totals: Promise<any>; | ||
13 | let userStats: Promise<any>; | ||
14 | 12 | ||
15 | let barCanvasRef: HTMLCanvasElement; | 13 | let barCanvasRef: HTMLCanvasElement; |
16 | let lineCanvasRef: HTMLCanvasElement; | 14 | let lineCanvasRef: HTMLCanvasElement; |
@@ -18,6 +16,10 @@ | |||
18 | let lineChart: any; | 16 | let lineChart: any; |
19 | 17 | ||
20 | let lastSevenDays: string[]; | 18 | let lastSevenDays: string[]; |
19 | let lastSevenDaysData: number[]; | ||
20 | |||
21 | let userTotalsLabels: string[]; | ||
22 | let userTotalsData: number[]; | ||
21 | 23 | ||
22 | async function fetchData() { | 24 | async function fetchData() { |
23 | const res = await fetch("http://localhost:8080/api/v1/stats/", { | 25 | const res = await fetch("http://localhost:8080/api/v1/stats/", { |
@@ -33,24 +35,27 @@ | |||
33 | } | 35 | } |
34 | } | 36 | } |
35 | 37 | ||
36 | async function fetchTotals() { | 38 | async function fetchDailyUserStatistics() { |
37 | const res = await fetch("http://localhost:8080/api/v1/stats/totals/", { | 39 | const res = await fetch("http://localhost:8080/api/v1/stats/totals/", { |
38 | method: 'GET', | 40 | method: "GET", |
39 | mode: 'no-cors', | ||
40 | headers: { | 41 | headers: { |
41 | Authorization: `Bearer ${$token}` | 42 | Authorization: `Bearer ${$token}` |
42 | } | 43 | } |
43 | }); | 44 | }); |
44 | 45 | ||
45 | if (res.ok) { | 46 | if (res.ok) { |
46 | totals = res.json(); | 47 | const json = await res.json(); |
48 | let labels = json.map(d => d.name); | ||
49 | let data = json.map(d => d.total); | ||
50 | return [labels, data]; | ||
47 | } else { | 51 | } else { |
48 | throw new Error("There was a problem with your request"); | 52 | throw new Error("There was a problem with your request"); |
49 | } | 53 | } |
54 | |||
50 | } | 55 | } |
51 | 56 | ||
52 | async function fetchStatsForUser() { | 57 | async function fetchWeeklyTotals() { |
53 | const res = await fetch("http://localhost:8080/api/v1/stats/user/1aa668f3-7527-4a67-9c24-fdf307542eeb", { | 58 | const res = await fetch("http://localhost:8080/api/v1/stats/weekly/", { |
54 | method: "GET", | 59 | method: "GET", |
55 | headers: { | 60 | headers: { |
56 | Authorization: `Bearer ${$token}` | 61 | Authorization: `Bearer ${$token}` |
@@ -58,22 +63,15 @@ | |||
58 | }); | 63 | }); |
59 | 64 | ||
60 | if (res.ok) { | 65 | if (res.ok) { |
61 | userStats = res.json(); | 66 | const json = await res.json(); |
67 | let labels = json.map(d => d.date); | ||
68 | let data = json.map(d => d.total); | ||
69 | return [labels, data]; | ||
62 | } else { | 70 | } else { |
63 | throw new Error("There was a problem with your request"); | 71 | throw new Error("There was a problem with your request"); |
64 | } | 72 | } |
65 | } | 73 | } |
66 | 74 | ||
67 | function getLastSevenDays() { | ||
68 | const result = []; | ||
69 | for (let i = 0; i < 7; i++) { | ||
70 | let d = new Date(); | ||
71 | d.setDate(d.getDate() - i); | ||
72 | result.push(d.toISOString().substring(0, 10)); | ||
73 | } | ||
74 | return result; | ||
75 | } | ||
76 | |||
77 | function closeDialog() { | 75 | function closeDialog() { |
78 | addFormOpen.set(false); | 76 | addFormOpen.set(false); |
79 | } | 77 | } |
@@ -81,61 +79,79 @@ | |||
81 | function onStatisticAdd() { | 79 | function onStatisticAdd() { |
82 | closeDialog(); | 80 | closeDialog(); |
83 | fetchData(); | 81 | fetchData(); |
82 | fetchWeeklyTotals().then(updateWeeklyTotalsChart).catch(err => console.error(err)); | ||
83 | fetchDailyUserStatistics().then(updateDailyUserTotalsChart).catch(err => console.error(err)); | ||
84 | } | 84 | } |
85 | 85 | ||
86 | onMount(() => { | 86 | function setupWeeklyTotalsChart(result) { |
87 | fetchData(); | 87 | [lastSevenDays, lastSevenDaysData] = result; |
88 | // fetchTotals(); | 88 | lineChart = new Chart(lineCanvasRef, { |
89 | fetchStatsForUser(); | 89 | type: "line", |
90 | lastSevenDays = getLastSevenDays(); | ||
91 | barChart = new Chart(barCanvasRef, { | ||
92 | type: "bar", | ||
93 | data: { | 90 | data: { |
94 | labels: lastSevenDays, | 91 | labels: lastSevenDays, |
95 | datasets: [ | 92 | datasets: [ |
96 | { | 93 | { |
97 | label: "Zach", | 94 | label: "Totals", |
98 | data: [1, 2, 8, 2, 5, 5, 1], | 95 | data: lastSevenDaysData, |
99 | backgroundColor: "rgba(255, 192, 192, 0.2)", | 96 | backgroundColor: "rgba(255, 192, 192, 0.2)" |
100 | borderColor: "rgba(75, 192, 192, 1)", | ||
101 | borderWidth: 1 | ||
102 | }, { | ||
103 | label: "Parker", | ||
104 | data: [6, 1, 1, 4, 3, 5, 1], | ||
105 | backgroundColor: "rgba(75, 192, 192, 0.2)", | ||
106 | borderColor: "rgba(75, 192, 192, 1)", | ||
107 | borderWidth: 1 | ||
108 | } | 97 | } |
109 | ] | 98 | ] |
110 | }, | 99 | }, |
111 | options: { | 100 | options: { |
112 | responsive: true | 101 | responsive: true, |
102 | plugins: { | ||
103 | legend: { | ||
104 | display: false | ||
105 | } | ||
106 | } | ||
113 | } | 107 | } |
114 | }); | 108 | }); |
115 | lineChart = new Chart(lineCanvasRef, { | 109 | } |
116 | type: "line", | 110 | |
111 | function setupDailyUserTotalsChart(result) { | ||
112 | [userTotalsLabels, userTotalsData] = result; | ||
113 | |||
114 | barChart = new Chart(barCanvasRef, { | ||
115 | type: "bar", | ||
117 | data: { | 116 | data: { |
118 | labels: lastSevenDays, | 117 | labels: userTotalsLabels, |
119 | datasets: [ | 118 | datasets: [ |
120 | { | 119 | { |
121 | label: "Zach", | 120 | data: userTotalsData, |
122 | data: [1, 2, 8, 2, 5, 5, 1], | 121 | backgroundColor: [ |
123 | backgroundColor: "rgba(255, 192, 192, 0.2)", | 122 | "#330000", |
124 | borderColor: "rgba(75, 192, 192, 1)", | 123 | "rgba(100, 200, 192, 0.2)" |
125 | borderWidth: 1 | 124 | ] |
126 | }, { | ||
127 | label: "Parker", | ||
128 | data: [6, 1, 1, 4, 3, 5, 1], | ||
129 | backgroundColor: "rgba(75, 192, 192, 0.2)", | ||
130 | borderColor: "rgba(75, 192, 192, 1)", | ||
131 | borderWidth: 1 | ||
132 | } | 125 | } |
133 | ] | 126 | ] |
134 | }, | 127 | }, |
135 | options: { | 128 | options: { |
136 | responsive: true | 129 | responsive: true, |
130 | plugins: { | ||
131 | legend: { | ||
132 | display: false | ||
133 | } | ||
134 | } | ||
137 | } | 135 | } |
138 | }); | 136 | }); |
137 | } | ||
138 | |||
139 | function updateWeeklyTotalsChart(result) { | ||
140 | [,lastSevenDaysData] = result; | ||
141 | lineChart.data.datasets[0].data = lastSevenDaysData; | ||
142 | lineChart.update(); | ||
143 | } | ||
144 | |||
145 | function updateDailyUserTotalsChart(result) { | ||
146 | [,userTotalsData] = result; | ||
147 | barChart.data.datasets[0].data = userTotalsData; | ||
148 | barChart.update(); | ||
149 | } | ||
150 | |||
151 | onMount(() => { | ||
152 | fetchData(); | ||
153 | fetchWeeklyTotals().then(setupWeeklyTotalsChart); | ||
154 | fetchDailyUserStatistics().then(setupDailyUserTotalsChart); | ||
139 | }); | 155 | }); |
140 | 156 | ||
141 | onDestroy(() => { | 157 | onDestroy(() => { |
@@ -164,14 +180,6 @@ | |||
164 | <p>{error}</p> | 180 | <p>{error}</p> |
165 | {/await} | 181 | {/await} |
166 | </Card> | 182 | </Card> |
167 | <Card> | ||
168 | <button on:click={() => fetchTotals()}>Get totals</button> | ||
169 | {#await totals then data} | ||
170 | {JSON.stringify(data)} | ||
171 | {:catch error} | ||
172 | <p>{error}</p> | ||
173 | {/await} | ||
174 | </Card> | ||
175 | </Column> | 183 | </Column> |
176 | <!-- <Chart /> --> | 184 | <!-- <Chart /> --> |
177 | 185 | ||
diff --git a/fe/src/lib/forms/AddForm.svelte b/fe/src/lib/forms/AddForm.svelte index f22e5f4..4520b1b 100644 --- a/fe/src/lib/forms/AddForm.svelte +++ b/fe/src/lib/forms/AddForm.svelte | |||
@@ -34,20 +34,23 @@ | |||
34 | dispatch("close"); | 34 | dispatch("close"); |
35 | } | 35 | } |
36 | 36 | ||
37 | async function handleSubmitStat() { | 37 | async function handleSubmitStat() |
38 | const response = await fetch("http://localhost:8080/api/v1/stats/", { | 38 | { |
39 | const { date, quantity } = statistic; | ||
40 | await fetch("http://localhost:8080/api/v1/stats/", { | ||
39 | method: "POST", | 41 | method: "POST", |
40 | headers: { | 42 | headers: { |
41 | Authorization: `Bearer ${$token}` | 43 | Authorization: `Bearer ${$token}` |
42 | }, | 44 | }, |
43 | body: JSON.stringify({ | 45 | body: JSON.stringify({ |
44 | date: new Date(), | 46 | date: new Date(date), |
45 | user_id: 1, | 47 | user_id: 2, |
46 | quantity: 3 | 48 | quantity |
47 | }) | 49 | }) |
48 | }); | 50 | }); |
49 | dispatch("submit"); | 51 | dispatch("submit"); |
50 | } | 52 | } |
53 | |||
51 | </script> | 54 | </script> |
52 | 55 | ||
53 | <dialog {open} on:submit={handleSubmitStat}> | 56 | <dialog {open} on:submit={handleSubmitStat}> |