From e37c73e33a4aaf7fb8d25b5af03627f20bcda19f Mon Sep 17 00:00:00 2001 From: Doog <157747121+doogongithub@users.noreply.github.com> Date: Sat, 24 Feb 2024 20:08:35 -0500 Subject: add gitignore --- .gitignore | 49 +++++++++++++++ api/go.mod | 6 +- api/go.sum | 9 +++ api/lib/models.go | 23 +++++++ api/main.go | 127 ++++++++++++++++++++++++++++++++------ db/scripts/water_init.sql | 10 +-- db/water.sqlite3 | Bin 40960 -> 24576 bytes fe/src/App.svelte | 147 +++----------------------------------------- fe/src/app.css | 2 +- fe/src/lib/DataView.svelte | 67 ++++++++++++++++++++ fe/src/lib/Layout.svelte | 57 +++++++++++++++++ fe/src/lib/LoginForm.svelte | 64 +++++++++++++++++++ fe/src/lib/Table.svelte | 61 +++++++++++++++--- fe/src/stores/auth.ts | 48 +++++++++++++++ fe/svelte.config.js | 1 + 15 files changed, 500 insertions(+), 171 deletions(-) create mode 100644 .gitignore create mode 100644 api/lib/models.go create mode 100644 fe/src/lib/DataView.svelte create mode 100644 fe/src/lib/Layout.svelte create mode 100644 fe/src/lib/LoginForm.svelte create mode 100644 fe/src/stores/auth.ts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4e424ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,49 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +vendor/ + +# Go workspace file +go.work + + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Dependency directories +node_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional REPL history +.node_repl_history + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + + diff --git a/api/go.mod b/api/go.mod index 02f7c09..08ad9e1 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,12 +3,16 @@ module water/api go 1.18 require ( + github.com/gin-gonic/gin v1.9.1 + github.com/mattn/go-sqlite3 v1.14.22 +) + +require ( github.com/bytedance/sonic v1.11.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/gin-gonic/gin v1.9.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.18.0 // indirect diff --git a/api/go.sum b/api/go.sum index eff6af1..5174feb 100644 --- a/api/go.sum +++ b/api/go.sum @@ -10,6 +10,7 @@ github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0= github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= @@ -17,6 +18,7 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -25,6 +27,7 @@ github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtP github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= @@ -36,6 +39,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -43,6 +48,7 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -52,6 +58,7 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -70,8 +77,10 @@ golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/api/lib/models.go b/api/lib/models.go new file mode 100644 index 0000000..92e5703 --- /dev/null +++ b/api/lib/models.go @@ -0,0 +1,23 @@ +package models + +import "time" + +type Statistic struct { + ID int64 `json:"id"` + Date time.Time `json:"date"` + UserID int64 `json:"user_id"` + Quantity int `json:"quantity"` +} + +type User struct { + ID int64 + Name string +} + +type Token struct { + ID int64 + UserID int64 + Token string + CreatedAt time.Time + ExpiredAt time.Time +} diff --git a/api/main.go b/api/main.go index ebae5d1..292a5f9 100644 --- a/api/main.go +++ b/api/main.go @@ -4,8 +4,13 @@ import ( "net/http" "crypto/rand" "encoding/base64" + "database/sql" + "strings" + "errors" "github.com/gin-gonic/gin" + _ "github.com/mattn/go-sqlite3" + "water/api/lib" ) func CORSMiddleware() gin.HandlerFunc { @@ -30,6 +35,44 @@ func generateToken() string { return base64.StdEncoding.EncodeToString(token) } +func establishDBConnection() *sql.DB { + db, err := sql.Open("sqlite3", "../db/water.sqlite3") + if err != nil { + panic(err) + } + return db +} + +func checkForTokenInContext(c *gin.Context) (string, error) { + authorizationHeader := c.GetHeader("Authorization") + if authorizationHeader == "" { + return "", errors.New("Authorization header is missing") + } + + parts := strings.Split(authorizationHeader, " ") + + if len(parts) != 2 || parts[0] != "Bearer" { + return "", errors.New("Invalid Authorization header format") + } + + return parts[1], nil +} + + +func TokenRequired() gin.HandlerFunc { + return func(c *gin.Context) { + _, err := checkForTokenInContext(c) + + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + c.Next() + } +} + type User struct { Username string Password string @@ -44,6 +87,8 @@ func setupRouter() *gin.Engine { // gin.DisableConsoleColor() r := gin.Default() r.Use(CORSMiddleware()) + r.Use(gin.Logger()) + r.Use(gin.Recovery()) api := r.Group("api/v1") @@ -68,26 +113,70 @@ func setupRouter() *gin.Engine { }) stats := api.Group("stats") + stats.Use(TokenRequired()) + { + stats.GET("/", func(c *gin.Context) { + db := establishDBConnection() + defer db.Close() + + rows, err := db.Query("SELECT * FROM statistics"); + if err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + defer rows.Close() + + var data []models.Statistic + for rows.Next() { + var stat models.Statistic + if err := rows.Scan(&stat.ID, &stat.Date, &stat.UserID, &stat.Quantity); err != nil { + c.JSON(500, gin.H{"error": err.Error()}) + return + } + data = append(data, stat) + } + + c.JSON(http.StatusOK, data) + }) + + stats.POST("/", func(c *gin.Context) { + var stat models.Statistic + + if err := c.BindJSON(&stat); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + db := establishDBConnection() + defer db.Close() + + result, err := db.Exec("INSERT INTO statistics (date, user_id, quantity) values (?, ?, ?)", stat.Date, stat.UserID, stat.Quantity) + + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + id, err := result.LastInsertId() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + } + + c.JSON(http.StatusCreated, gin.H{"status": "created", "id": id}) + }) + + stats.GET("/:uuid", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok", "uuid": c.Param("uuid")}) + }) + + stats.PATCH("/:uuid", func(c *gin.Context) { + c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) + }) + + stats.DELETE("/:uuid", func(c *gin.Context) { + c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) + }) + } - stats.GET("/", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"status": "ok"}) - }) - - stats.POST("/", func(c *gin.Context) { - c.JSON(http.StatusCreated, gin.H{"status": "created"}) - }) - - stats.GET("/:uuid", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{"status": "ok", "uuid": c.Param("uuid")}) - }) - - stats.PATCH("/:uuid", func(c *gin.Context) { - c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) - }) - - stats.DELETE("/:uuid", func(c *gin.Context) { - c.JSON(http.StatusNoContent, gin.H{"status": "No Content"}) - }) return r } diff --git a/db/scripts/water_init.sql b/db/scripts/water_init.sql index d7b912a..0751c41 100644 --- a/db/scripts/water_init.sql +++ b/db/scripts/water_init.sql @@ -1,13 +1,13 @@ -- user table for users. CREATE TABLE IF NOT EXISTS Users ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, name TEXT NOT NULL, UNIQUE(name) ); -- statistics table for users to log their consumption CREATE TABLE IF NOT EXISTS Statistics ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, date DATETIME NOT NULL, user_id INT NOT NULL, quantity INT @@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS Statistics ( -- preferences table for a user. CREATE TABLE IF NOT EXISTS Preferences ( - id INT PRIMARY KEY, + id INTEGER PRIMARY KEY, color TEXT NOT NULL DEFAULT "#000000", user_id INT NOT NULL, size_id INT NOT NULL DEFAULT 1, @@ -25,8 +25,8 @@ CREATE TABLE IF NOT EXISTS Preferences ( -- lookup table for sizes. CREATE TABLE IF NOT EXISTS Sizes ( - id INT PRIMARY KEY, - size INT NOT NULL + id INTEGER PRIMARY KEY, + size INT NOT NULL, unit TEXT DEFAULT "oz" ); diff --git a/db/water.sqlite3 b/db/water.sqlite3 index 97f9214..c800708 100644 Binary files a/db/water.sqlite3 and b/db/water.sqlite3 differ diff --git a/fe/src/App.svelte b/fe/src/App.svelte index cc4e594..8811c52 100644 --- a/fe/src/App.svelte +++ b/fe/src/App.svelte @@ -1,146 +1,19 @@
- {#if !authenticated} - -
-
- - -
-
- - -
- {#if error} -

{error}

- {/if} - -
-
+ + {#if !$authenticated} + {:else} -
- -
-
- - - - - - -
- - - - {#await data} -

...fetching

- {:then data} - {#if data} -

Status

-

{data.status}

- -
-
- {:else} -

No data yet

- {/if} - {:catch errror} -

{error.message}

- {/await} + {/if} + diff --git a/fe/src/lib/LoginForm.svelte b/fe/src/lib/LoginForm.svelte new file mode 100644 index 0000000..22c0faf --- /dev/null +++ b/fe/src/lib/LoginForm.svelte @@ -0,0 +1,64 @@ + + + +
+
+ + +
+
+ + +
+ {#if error} +

{error}

+ {/if} + + +
diff --git a/fe/src/lib/Table.svelte b/fe/src/lib/Table.svelte index 2df9f8c..5572280 100644 --- a/fe/src/lib/Table.svelte +++ b/fe/src/lib/Table.svelte @@ -1,8 +1,38 @@
{#if title} @@ -11,16 +41,27 @@ {#if !noheader} - + {#each getDataKeys(data) as header} + + {/each} {/if} + {#if data} + {#each data as row} - + {#each getRow(row) as datum} + + + {/each} + {/each} + {:else} + + There is not data. + + {/if} {#if !nofooter} @@ -38,4 +79,8 @@ table { margin: 8px; border: solid 1px black; } + +th { + text-transform: capitalize; +} diff --git a/fe/src/stores/auth.ts b/fe/src/stores/auth.ts new file mode 100644 index 0000000..7e70cda --- /dev/null +++ b/fe/src/stores/auth.ts @@ -0,0 +1,48 @@ +import type { Invalidator, Subscriber, Unsubscriber } from 'svelte/store'; +import { writable, derived } from 'svelte/store'; + +type Nullable = T | null; + +interface User { + uuid: string; + username: string; +} + +interface TokenStore { + subscribe: (run: Subscriber>, invalidate: Invalidator>) => Unsubscriber, + authenticate: (newToken: string) => void, + unauthenticate: () => void +} + +function createTokenStore(): TokenStore { + const storedToken = localStorage.getItem("token"); + const { subscribe, set } = writable(storedToken); + + function authenticate(newToken: string): void { + try { + localStorage.setItem("token", newToken); + set(newToken); + } catch (e) { + console.error('error', e); + } + } + + function unauthenticate(): void { + localStorage.removeItem("token"); + set(null); + } + + return { + subscribe, + authenticate, + unauthenticate + }; +} + +function onTokenChange ($token: Nullable): boolean { + return $token ? true : false; +} + +export const token = createTokenStore(); +export const authenticated = derived(token, onTokenChange); +export const user = writable(null); diff --git a/fe/svelte.config.js b/fe/svelte.config.js index b0683fd..b29bf40 100644 --- a/fe/svelte.config.js +++ b/fe/svelte.config.js @@ -4,4 +4,5 @@ export default { // Consult https://svelte.dev/docs#compile-time-svelte-preprocess // for more information about preprocessors preprocess: vitePreprocess(), + dev: true } -- cgit v1.1
- Data Header - {header}
Data{formatDatum(datum)}