From 8fab2d03bce82e4dee798ebffb1e93c557f62a4b Mon Sep 17 00:00:00 2001 From: Zach Berwaldt Date: Thu, 7 Mar 2024 23:16:22 -0500 Subject: 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. --- api/cmd/main_test.go | 9 +++--- api/internal/controllers/auth.go | 11 ++++++- api/internal/controllers/stats.go | 6 +++- api/internal/database/database.go | 4 +-- fe/src/http.ts | 60 +++++++++++++++++++++++++++++++++++++++ fe/src/lib/DataView.svelte | 53 ++++++++++++++++++++++++++++++---- fe/src/lib/Table.svelte | 13 +++++---- 7 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 fe/src/http.ts 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 ( "net/http" "net/http/httptest" "testing" + "water/api/internal/router" "github.com/spf13/viper" "github.com/stretchr/testify/assert" @@ -26,7 +27,7 @@ func getTestUserCredentials() (string, string) { } func TestAuthRoute(t *testing.T) { - router := setupRouter() + r := router.SetupRouter() username, password := getTestUserCredentials() @@ -39,7 +40,7 @@ func TestAuthRoute(t *testing.T) { t.Fatalf("Failed to create request: %v", err) } req.SetBasicAuth(username, password) - router.ServeHTTP(w, req) + r.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code, "response should return a 200 code") @@ -56,12 +57,12 @@ func TestAuthRoute(t *testing.T) { } func TestAuthRouteFailure(t *testing.T) { - router := setupRouter() + r := router.SetupRouter() w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/api/v1/auth", nil) req.SetBasicAuth("asdf", "asdf") - router.ServeHTTP(w, req) + r.ServeHTTP(w, req) assert.Equal(t, http.StatusUnauthorized, w.Code, "should return a 401 code") } 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 ( "water/api/internal/database" ) + + +// AuthHandler is a function that handles users' authentication. It checks if the request +// has valid credentials, authenticates the user and sets the user's session. +// If the authentication is successful, it will allow the user to access protected routes. func AuthHandler (c *gin.Context) { username, password, ok := c.Request.BasicAuth() if !ok { @@ -55,7 +60,11 @@ func AuthHandler (c *gin.Context) { c.JSON(http.StatusOK, gin.H{"token": apiToken, "user": user, "preferences": preference}) } -// generatToken will g + +// generateToken is a helper function used in the AuthHandler. It generates a random token for API authentication. +// This function creates an empty byte slice of length 32 and fills it with cryptographic random data using the rand.Read function. +// If an error occurs during the generation, it will return an empty string. +// The generated cryptographic random data is then encoded into a base64 string and returned. func generateToken() string { token := make([]byte, 32) _, 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 ( "water/api/internal/models" ) +// TODO: add comments to all exported members of package. + +// GetAllStatistics connects to the database and queries for all statistics in the database. +// If none have been found it will return an error, otherwise a 200 code is sent along with the list of statistics. func GetAllStatistics(c *gin.Context) { db := database.EstablishDBConnection() defer func(db *sql.DB) { @@ -78,7 +82,7 @@ func PostNewStatistic(c *gin.Context) { c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) } -func GetWeeklyStatistics (c *gin.Context) { +func GetWeeklyStatistics(c *gin.Context) { db := database.EstablishDBConnection() defer func(db *sql.DB) { 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 ( ) func SetupDatabase() { - _, err := sql.Open("sqlite3", "water.db") + _, err := sql.Open("sqlite3", "water.sqlite3") if err != nil { log.Fatal(err) } } func EstablishDBConnection() *sql.DB { - db, err := sql.Open("sqlite3", "../../db/water.sqlite3") + db, err := sql.Open("sqlite3", "../db/water.sqlite3") if err != nil { panic(err) } 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 @@ +export default class HttpClient { + private static instance: HttpClient; + baseURL: string; + + private constructor(baseURL: string) { + this.baseURL = baseURL; + } + + private getURL(endpoint: string): URL { + return new URL(endpoint, this.baseURL) + } + + public static getInstance(): HttpClient { + if (!HttpClient.instance) { + const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? 'http://localhost:8080/api/v1'; + HttpClient.instance = new HttpClient(baseUrl); + } + + return HttpClient.instance; + } + + async get({ endpoint }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + const response = await fetch(url, { + method: 'GET', + headers: headers, + }); + return response.json(); + } + + async post({ endpoint }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + const response = await fetch(url, { + method: 'POST', + body: JSON.stringify(body), + headers: headers, + }); + return response.json(); + } + + async patch({ endpoint, authenticated, headers }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + if (authenticated) { + + } + const response: Response = await fetch(url) + } + + async delete({ endpoint, authenticated }: IHttpParameters): Promise { + const url = this.getURL(endpoint); + if (authenticated) { } + const response = await fetch() + } +} + +interface IHttpParameters { + endpoint: string; + authenticated: boolean; + headers: Headers +} 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 @@ - - + + - + 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 @@ export let omit: string[] = ["id"]; export let title: string | undefined = undefined; + export let sortBy: string = 'date'; + + type SortComparator = (a, b) => number + function getDataKeys(data: any[]): string[] { if (!data || data.length === 0) return []; return Object.keys(data[0]) @@ -16,11 +20,8 @@ return Object.entries(row).filter((r) => !omit.includes(r[0])); } - - let limitedData: Array = []; - - if (data && (data as any[]).length > 0) { - limitedData = (data as any[]).slice(0, 4); + function sort(arr: Array>, fn: SortComparator = (a , b) => new Date(b[sortBy]) - new Date(a[sortBy])) { + return arr.sort(fn) } const formatter = new Intl.DateTimeFormat("en", { @@ -62,7 +63,7 @@ {/if} {#if data} - {#each limitedData as row} + {#each sort(data) as row} {#each getRow(row) as datum} {formatDatum(datum)} -- cgit v1.1