aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZach Berwaldt <zberwaldt@tutamail.com>2024-03-07 23:16:22 -0500
committerZach Berwaldt <zberwaldt@tutamail.com>2024-03-07 23:16:22 -0500
commit8fab2d03bce82e4dee798ebffb1e93c557f62a4b (patch)
tree3d1c769e5f0a1791f45b549b96df30e7f66b7a67
parent831b6f0167b9c1747d128b4a5a648d4de42ff0a9 (diff)
feat: Update authentication route and add comments to exported members
- The authentication route in the API has been updated to use a new router setup function. - Comments have been added to all exported members of the `auth.go` module in the internal controllers package.
-rw-r--r--api/cmd/main_test.go9
-rw-r--r--api/internal/controllers/auth.go11
-rw-r--r--api/internal/controllers/stats.go6
-rw-r--r--api/internal/database/database.go4
-rw-r--r--fe/src/http.ts60
-rw-r--r--fe/src/lib/DataView.svelte53
-rw-r--r--fe/src/lib/Table.svelte13
7 files changed, 136 insertions, 20 deletions
diff --git a/api/cmd/main_test.go b/api/cmd/main_test.go
index a6c8381..049cf6e 100644
--- a/api/cmd/main_test.go
+++ b/api/cmd/main_test.go
@@ -6,6 +6,7 @@ import (
6 "net/http" 6 "net/http"
7 "net/http/httptest" 7 "net/http/httptest"
8 "testing" 8 "testing"
9 "water/api/internal/router"
9 10
10 "github.com/spf13/viper" 11 "github.com/spf13/viper"
11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/assert"
@@ -26,7 +27,7 @@ func getTestUserCredentials() (string, string) {
26} 27}
27 28
28func TestAuthRoute(t *testing.T) { 29func TestAuthRoute(t *testing.T) {
29 router := setupRouter() 30 r := router.SetupRouter()
30 31
31 username, password := getTestUserCredentials() 32 username, password := getTestUserCredentials()
32 33
@@ -39,7 +40,7 @@ func TestAuthRoute(t *testing.T) {
39 t.Fatalf("Failed to create request: %v", err) 40 t.Fatalf("Failed to create request: %v", err)
40 } 41 }
41 req.SetBasicAuth(username, password) 42 req.SetBasicAuth(username, password)
42 router.ServeHTTP(w, req) 43 r.ServeHTTP(w, req)
43 44
44 assert.Equal(t, http.StatusOK, w.Code, "response should return a 200 code") 45 assert.Equal(t, http.StatusOK, w.Code, "response should return a 200 code")
45 46
@@ -56,12 +57,12 @@ func TestAuthRoute(t *testing.T) {
56} 57}
57 58
58func TestAuthRouteFailure(t *testing.T) { 59func TestAuthRouteFailure(t *testing.T) {
59 router := setupRouter() 60 r := router.SetupRouter()
60 61
61 w := httptest.NewRecorder() 62 w := httptest.NewRecorder()
62 req, _ := http.NewRequest("POST", "/api/v1/auth", nil) 63 req, _ := http.NewRequest("POST", "/api/v1/auth", nil)
63 req.SetBasicAuth("asdf", "asdf") 64 req.SetBasicAuth("asdf", "asdf")
64 router.ServeHTTP(w, req) 65 r.ServeHTTP(w, req)
65 66
66 assert.Equal(t, http.StatusUnauthorized, w.Code, "should return a 401 code") 67 assert.Equal(t, http.StatusUnauthorized, w.Code, "should return a 401 code")
67} 68}
diff --git a/api/internal/controllers/auth.go b/api/internal/controllers/auth.go
index de9ed05..58653d0 100644
--- a/api/internal/controllers/auth.go
+++ b/api/internal/controllers/auth.go
@@ -14,6 +14,11 @@ import (
14 "water/api/internal/database" 14 "water/api/internal/database"
15) 15)
16 16
17
18
19// AuthHandler is a function that handles users' authentication. It checks if the request
20// has valid credentials, authenticates the user and sets the user's session.
21// If the authentication is successful, it will allow the user to access protected routes.
17func AuthHandler (c *gin.Context) { 22func AuthHandler (c *gin.Context) {
18 username, password, ok := c.Request.BasicAuth() 23 username, password, ok := c.Request.BasicAuth()
19 if !ok { 24 if !ok {
@@ -55,7 +60,11 @@ func AuthHandler (c *gin.Context) {
55 c.JSON(http.StatusOK, gin.H{"token": apiToken, "user": user, "preferences": preference}) 60 c.JSON(http.StatusOK, gin.H{"token": apiToken, "user": user, "preferences": preference})
56} 61}
57 62
58// generatToken will g 63
64// generateToken is a helper function used in the AuthHandler. It generates a random token for API authentication.
65// This function creates an empty byte slice of length 32 and fills it with cryptographic random data using the rand.Read function.
66// If an error occurs during the generation, it will return an empty string.
67// The generated cryptographic random data is then encoded into a base64 string and returned.
59func generateToken() string { 68func generateToken() string {
60 token := make([]byte, 32) 69 token := make([]byte, 32)
61 _, err := rand.Read(token) 70 _, err := rand.Read(token)
diff --git a/api/internal/controllers/stats.go b/api/internal/controllers/stats.go
index d8ed434..2234787 100644
--- a/api/internal/controllers/stats.go
+++ b/api/internal/controllers/stats.go
@@ -8,6 +8,10 @@ import (
8 "water/api/internal/models" 8 "water/api/internal/models"
9) 9)
10 10
11// TODO: add comments to all exported members of package.
12
13// GetAllStatistics connects to the database and queries for all statistics in the database.
14// If none have been found it will return an error, otherwise a 200 code is sent along with the list of statistics.
11func GetAllStatistics(c *gin.Context) { 15func GetAllStatistics(c *gin.Context) {
12 db := database.EstablishDBConnection() 16 db := database.EstablishDBConnection()
13 defer func(db *sql.DB) { 17 defer func(db *sql.DB) {
@@ -78,7 +82,7 @@ func PostNewStatistic(c *gin.Context) {
78 c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) 82 c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id})
79} 83}
80 84
81func GetWeeklyStatistics (c *gin.Context) { 85func GetWeeklyStatistics(c *gin.Context) {
82 db := database.EstablishDBConnection() 86 db := database.EstablishDBConnection()
83 defer func(db *sql.DB) { 87 defer func(db *sql.DB) {
84 err := db.Close() 88 err := db.Close()
diff --git a/api/internal/database/database.go b/api/internal/database/database.go
index 19ae818..7af9780 100644
--- a/api/internal/database/database.go
+++ b/api/internal/database/database.go
@@ -7,14 +7,14 @@ import (
7) 7)
8 8
9func SetupDatabase() { 9func SetupDatabase() {
10 _, err := sql.Open("sqlite3", "water.db") 10 _, err := sql.Open("sqlite3", "water.sqlite3")
11 if err != nil { 11 if err != nil {
12 log.Fatal(err) 12 log.Fatal(err)
13 } 13 }
14} 14}
15 15
16func EstablishDBConnection() *sql.DB { 16func EstablishDBConnection() *sql.DB {
17 db, err := sql.Open("sqlite3", "../../db/water.sqlite3") 17 db, err := sql.Open("sqlite3", "../db/water.sqlite3")
18 if err != nil { 18 if err != nil {
19 panic(err) 19 panic(err)
20 } 20 }
diff --git a/fe/src/http.ts b/fe/src/http.ts
new file mode 100644
index 0000000..cc5a906
--- /dev/null
+++ b/fe/src/http.ts
@@ -0,0 +1,60 @@
1export default class HttpClient {
2 private static instance: HttpClient;
3 baseURL: string;
4
5 private constructor(baseURL: string) {
6 this.baseURL = baseURL;
7 }
8
9 private getURL(endpoint: string): URL {
10 return new URL(endpoint, this.baseURL)
11 }
12
13 public static getInstance(): HttpClient {
14 if (!HttpClient.instance) {
15 const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? 'http://localhost:8080/api/v1';
16 HttpClient.instance = new HttpClient(baseUrl);
17 }
18
19 return HttpClient.instance;
20 }
21
22 async get({ endpoint }: IHttpParameters): Promise<Response> {
23 const url = this.getURL(endpoint);
24 const response = await fetch(url, {
25 method: 'GET',
26 headers: headers,
27 });
28 return response.json();
29 }
30
31 async post({ endpoint }: IHttpParameters): Promise<Response> {
32 const url = this.getURL(endpoint);
33 const response = await fetch(url, {
34 method: 'POST',
35 body: JSON.stringify(body),
36 headers: headers,
37 });
38 return response.json();
39 }
40
41 async patch({ endpoint, authenticated, headers }: IHttpParameters): Promise<Response> {
42 const url = this.getURL(endpoint);
43 if (authenticated) {
44
45 }
46 const response: Response = await fetch(url)
47 }
48
49 async delete({ endpoint, authenticated }: IHttpParameters): Promise<Response> {
50 const url = this.getURL(endpoint);
51 if (authenticated) { }
52 const response = await fetch()
53 }
54}
55
56interface IHttpParameters {
57 endpoint: string;
58 authenticated: boolean;
59 headers: Headers
60}
diff --git a/fe/src/lib/DataView.svelte b/fe/src/lib/DataView.svelte
index 7d62a43..0a6b81b 100644
--- a/fe/src/lib/DataView.svelte
+++ b/fe/src/lib/DataView.svelte
@@ -1,5 +1,6 @@
1<script lang="ts"> 1<script lang="ts">
2 import { onDestroy, onMount } from "svelte"; 2 import { onDestroy, onMount } from "svelte";
3 import HttpClient from "../http";
3 import { token } from "../stores/auth"; 4 import { token } from "../stores/auth";
4 import { addFormOpen } from "../stores/forms"; 5 import { addFormOpen } from "../stores/forms";
5 import Table from "./Table.svelte"; 6 import Table from "./Table.svelte";
@@ -36,7 +37,7 @@
36 } 37 }
37 38
38 async function fetchDailyUserStatistics() { 39 async function fetchDailyUserStatistics() {
39 const res = await fetch("http://localhost:8080/api/v1/stats/totals/", { 40 const res = await fetch("http://localhost:8080/api/v1/stats/daily/", {
40 method: "GET", 41 method: "GET",
41 headers: { 42 headers: {
42 Authorization: `Bearer ${$token}` 43 Authorization: `Bearer ${$token}`
@@ -99,9 +100,29 @@
99 }, 100 },
100 options: { 101 options: {
101 responsive: true, 102 responsive: true,
103 maintainAspectRatio: false,
104 scales: {
105 y: {
106 suggestedMax: 30,
107 beginAtZero: true,
108 ticks: {
109 autoSkip: true,
110 stepSize: 5
111 }
112 }
113 },
102 plugins: { 114 plugins: {
103 legend: { 115 legend: {
104 display: false 116 display: false
117 },
118 title: {
119 display: true,
120 text: "Weekly Breakdown"
121 },
122 subtitle: {
123 display: true,
124 text: "Water consumption over the last week",
125 padding: {bottom: 10}
105 } 126 }
106 } 127 }
107 } 128 }
@@ -127,9 +148,29 @@
127 }, 148 },
128 options: { 149 options: {
129 responsive: true, 150 responsive: true,
151 maintainAspectRatio: false,
152 scales: {
153 y: {
154 beginAtZero: true,
155 suggestedMax: 10,
156 ticks: {
157 autoSkip: true,
158 stepSize: 1
159 }
160 }
161 },
130 plugins: { 162 plugins: {
131 legend: { 163 legend: {
132 display: false 164 display: false
165 },
166 title: {
167 display: true,
168 text: "Daily Total"
169 },
170 subtitle: {
171 display: true,
172 text: "Water Drank Today",
173 padding: {bottom: 10}
133 } 174 }
134 } 175 }
135 } 176 }
@@ -137,13 +178,13 @@
137 } 178 }
138 179
139 function updateWeeklyTotalsChart(result) { 180 function updateWeeklyTotalsChart(result) {
140 [,lastSevenDaysData] = result; 181 [, lastSevenDaysData] = result;
141 lineChart.data.datasets[0].data = lastSevenDaysData; 182 lineChart.data.datasets[0].data = lastSevenDaysData;
142 lineChart.update(); 183 lineChart.update();
143 } 184 }
144 185
145 function updateDailyUserTotalsChart(result) { 186 function updateDailyUserTotalsChart(result) {
146 [,userTotalsData] = result; 187 [, userTotalsData] = result;
147 barChart.data.datasets[0].data = userTotalsData; 188 barChart.data.datasets[0].data = userTotalsData;
148 barChart.update(); 189 barChart.update();
149 } 190 }
@@ -163,10 +204,10 @@
163</script> 204</script>
164 205
165<Column --width="500px"> 206<Column --width="500px">
166 <Card> 207 <Card --height="300px">
167 <canvas bind:this={barCanvasRef} width="" /> 208 <canvas bind:this={barCanvasRef} />
168 </Card> 209 </Card>
169 <Card> 210 <Card --height="300px">
170 <canvas bind:this={lineCanvasRef} /> 211 <canvas bind:this={lineCanvasRef} />
171 </Card> 212 </Card>
172</Column> 213</Column>
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte
index d1cd7da..621157e 100644
--- a/fe/src/lib/Table.svelte
+++ b/fe/src/lib/Table.svelte
@@ -5,6 +5,10 @@
5 export let omit: string[] = ["id"]; 5 export let omit: string[] = ["id"];
6 export let title: string | undefined = undefined; 6 export let title: string | undefined = undefined;
7 7
8 export let sortBy: string = 'date';
9
10 type SortComparator = (a, b) => number
11
8 function getDataKeys(data: any[]): string[] { 12 function getDataKeys(data: any[]): string[] {
9 if (!data || data.length === 0) return []; 13 if (!data || data.length === 0) return [];
10 return Object.keys(data[0]) 14 return Object.keys(data[0])
@@ -16,11 +20,8 @@
16 return Object.entries(row).filter((r) => !omit.includes(r[0])); 20 return Object.entries(row).filter((r) => !omit.includes(r[0]));
17 } 21 }
18 22
19 23 function sort(arr: Array<Record<string, any>>, fn: SortComparator = (a , b) => new Date(b[sortBy]) - new Date(a[sortBy])) {
20 let limitedData: Array<any> = []; 24 return arr.sort(fn)
21
22 if (data && (data as any[]).length > 0) {
23 limitedData = (data as any[]).slice(0, 4);
24 } 25 }
25 26
26 const formatter = new Intl.DateTimeFormat("en", { 27 const formatter = new Intl.DateTimeFormat("en", {
@@ -62,7 +63,7 @@
62 {/if} 63 {/if}
63 <tbody> 64 <tbody>
64 {#if data} 65 {#if data}
65 {#each limitedData as row} 66 {#each sort(data) as row}
66 <tr> 67 <tr>
67 {#each getRow(row) as datum} 68 {#each getRow(row) as datum}
68 <td>{formatDatum(datum)}</td> 69 <td>{formatDatum(datum)}</td>