diff options
| -rwxr-xr-x | api/bin/water | bin | 0 -> 15831520 bytes | |||
| -rw-r--r-- | api/internal/controllers/auth.go | 21 | ||||
| -rw-r--r-- | api/internal/controllers/preferences.go | 13 | ||||
| -rw-r--r-- | api/internal/controllers/stats.go | 27 | ||||
| -rw-r--r-- | api/internal/controllers/user.go | 14 | ||||
| -rw-r--r-- | api/internal/database/database.go | 18 | ||||
| -rwxr-xr-x | api/main | bin | 0 -> 17796784 bytes | |||
| -rw-r--r-- | api/water-api.nginx.conf | 8 | ||||
| -rw-r--r-- | api/water.service | 14 | ||||
| -rw-r--r-- | db/README.md | 10 | ||||
| -rw-r--r-- | db/scripts/init.sh | 6 | ||||
| -rw-r--r-- | fe/index.html | 2 | ||||
| -rw-r--r-- | fe/src/utils.ts | 2 | ||||
| -rw-r--r-- | fe/water.nginx.conf | 12 |
14 files changed, 114 insertions, 33 deletions
diff --git a/api/bin/water b/api/bin/water new file mode 100755 index 0000000..b311c73 --- /dev/null +++ b/api/bin/water | |||
| Binary files differ | |||
diff --git a/api/internal/controllers/auth.go b/api/internal/controllers/auth.go index ab2fbbb..b06c6ef 100644 --- a/api/internal/controllers/auth.go +++ b/api/internal/controllers/auth.go | |||
| @@ -5,21 +5,21 @@ import ( | |||
| 5 | "database/sql" | 5 | "database/sql" |
| 6 | "encoding/base64" | 6 | "encoding/base64" |
| 7 | "errors" | 7 | "errors" |
| 8 | "github.com/gin-gonic/gin" | ||
| 9 | "net/http" | 8 | "net/http" |
| 10 | "water/api/internal/models" | 9 | "water/api/internal/models" |
| 11 | 10 | ||
| 11 | "github.com/gin-gonic/gin" | ||
| 12 | |||
| 13 | "water/api/internal/database" | ||
| 14 | |||
| 12 | _ "github.com/mattn/go-sqlite3" | 15 | _ "github.com/mattn/go-sqlite3" |
| 13 | "golang.org/x/crypto/bcrypt" | 16 | "golang.org/x/crypto/bcrypt" |
| 14 | "water/api/internal/database" | ||
| 15 | ) | 17 | ) |
| 16 | 18 | ||
| 17 | |||
| 18 | |||
| 19 | // AuthHandler is a function that handles users' authentication. It checks if the request | 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. | 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. | 21 | // If the authentication is successful, it will allow the user to access protected routes. |
| 22 | func AuthHandler (c *gin.Context) { | 22 | func AuthHandler(c *gin.Context) { |
| 23 | username, password, ok := c.Request.BasicAuth() | 23 | username, password, ok := c.Request.BasicAuth() |
| 24 | if !ok { | 24 | if !ok { |
| 25 | c.Header("WWW-Authenticate", `Basic realm="Please enter your username and password."`) | 25 | c.Header("WWW-Authenticate", `Basic realm="Please enter your username and password."`) |
| @@ -27,7 +27,11 @@ func AuthHandler (c *gin.Context) { | |||
| 27 | return | 27 | return |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | db := database.EstablishDBConnection() | 30 | db, err := database.EstablishDBConnection() |
| 31 | if err != nil { | ||
| 32 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 33 | return | ||
| 34 | } | ||
| 31 | defer func(db *sql.DB) { | 35 | defer func(db *sql.DB) { |
| 32 | err := db.Close() | 36 | err := db.Close() |
| 33 | if err != nil { | 37 | if err != nil { |
| @@ -44,6 +48,8 @@ func AuthHandler (c *gin.Context) { | |||
| 44 | if errors.Is(err, sql.ErrNoRows) { | 48 | if errors.Is(err, sql.ErrNoRows) { |
| 45 | c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) | 49 | c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) |
| 46 | return | 50 | return |
| 51 | } else { | ||
| 52 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 47 | } | 53 | } |
| 48 | } | 54 | } |
| 49 | 55 | ||
| @@ -64,7 +70,6 @@ func AuthHandler (c *gin.Context) { | |||
| 64 | c.JSON(http.StatusOK, gin.H{"token": apiToken, "user": user, "preferences": preference}) | 70 | c.JSON(http.StatusOK, gin.H{"token": apiToken, "user": user, "preferences": preference}) |
| 65 | } | 71 | } |
| 66 | 72 | ||
| 67 | |||
| 68 | // generateToken is a helper function used in the AuthHandler. It generates a random token for API authentication. | 73 | // generateToken is a helper function used in the AuthHandler. It generates a random token for API authentication. |
| 69 | // This function creates an empty byte slice of length 32 and fills it with cryptographic random data using the rand.Read function. | 74 | // This function creates an empty byte slice of length 32 and fills it with cryptographic random data using the rand.Read function. |
| 70 | // If an error occurs during the generation, it will return an empty string. | 75 | // If an error occurs during the generation, it will return an empty string. |
| @@ -76,4 +81,4 @@ func generateToken() string { | |||
| 76 | return "" | 81 | return "" |
| 77 | } | 82 | } |
| 78 | return base64.StdEncoding.EncodeToString(token) | 83 | return base64.StdEncoding.EncodeToString(token) |
| 79 | } \ No newline at end of file | 84 | } |
diff --git a/api/internal/controllers/preferences.go b/api/internal/controllers/preferences.go index a1bcf4f..da52e74 100644 --- a/api/internal/controllers/preferences.go +++ b/api/internal/controllers/preferences.go | |||
| @@ -1,22 +1,27 @@ | |||
| 1 | package controllers | 1 | package controllers |
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "github.com/gin-gonic/gin" | ||
| 5 | "net/http" | ||
| 6 | "database/sql" | 4 | "database/sql" |
| 5 | "net/http" | ||
| 7 | "water/api/internal/database" | 6 | "water/api/internal/database" |
| 8 | "water/api/internal/models" | 7 | "water/api/internal/models" |
| 8 | |||
| 9 | "github.com/gin-gonic/gin" | ||
| 9 | ) | 10 | ) |
| 10 | 11 | ||
| 11 | func GetSizes(c *gin.Context) { | 12 | func GetSizes(c *gin.Context) { |
| 12 | db := database.EstablishDBConnection() | 13 | db, err := database.EstablishDBConnection() |
| 14 | if err != nil { | ||
| 15 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 16 | return | ||
| 17 | } | ||
| 13 | defer func(db *sql.DB) { | 18 | defer func(db *sql.DB) { |
| 14 | err := db.Close() | 19 | err := db.Close() |
| 15 | if err != nil { | 20 | if err != nil { |
| 16 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | 21 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| 17 | } | 22 | } |
| 18 | }(db) | 23 | }(db) |
| 19 | 24 | ||
| 20 | rows, err := db.Query("SELECT id, size, unit FROM Sizes") | 25 | rows, err := db.Query("SELECT id, size, unit FROM Sizes") |
| 21 | if err != nil { | 26 | if err != nil { |
| 22 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | 27 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
diff --git a/api/internal/controllers/stats.go b/api/internal/controllers/stats.go index 2234787..889b923 100644 --- a/api/internal/controllers/stats.go +++ b/api/internal/controllers/stats.go | |||
| @@ -2,10 +2,11 @@ package controllers | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "database/sql" | 4 | "database/sql" |
| 5 | "github.com/gin-gonic/gin" | ||
| 6 | "net/http" | 5 | "net/http" |
| 7 | "water/api/internal/database" | 6 | "water/api/internal/database" |
| 8 | "water/api/internal/models" | 7 | "water/api/internal/models" |
| 8 | |||
| 9 | "github.com/gin-gonic/gin" | ||
| 9 | ) | 10 | ) |
| 10 | 11 | ||
| 11 | // TODO: add comments to all exported members of package. | 12 | // TODO: add comments to all exported members of package. |
| @@ -13,7 +14,11 @@ import ( | |||
| 13 | // GetAllStatistics connects to the database and queries for all statistics in the database. | 14 | // 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. | 15 | // If none have been found it will return an error, otherwise a 200 code is sent along with the list of statistics. |
| 15 | func GetAllStatistics(c *gin.Context) { | 16 | func GetAllStatistics(c *gin.Context) { |
| 16 | db := database.EstablishDBConnection() | 17 | db, err := database.EstablishDBConnection() |
| 18 | if err != nil { | ||
| 19 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 20 | return | ||
| 21 | } | ||
| 17 | defer func(db *sql.DB) { | 22 | defer func(db *sql.DB) { |
| 18 | err := db.Close() | 23 | err := db.Close() |
| 19 | if err != nil { | 24 | if err != nil { |
| @@ -59,7 +64,11 @@ func PostNewStatistic(c *gin.Context) { | |||
| 59 | return | 64 | return |
| 60 | } | 65 | } |
| 61 | 66 | ||
| 62 | db := database.EstablishDBConnection() | 67 | db, err := database.EstablishDBConnection() |
| 68 | if err != nil { | ||
| 69 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 70 | return | ||
| 71 | } | ||
| 63 | defer func(db *sql.DB) { | 72 | defer func(db *sql.DB) { |
| 64 | err := db.Close() | 73 | err := db.Close() |
| 65 | if err != nil { | 74 | if err != nil { |
| @@ -83,7 +92,11 @@ func PostNewStatistic(c *gin.Context) { | |||
| 83 | } | 92 | } |
| 84 | 93 | ||
| 85 | func GetWeeklyStatistics(c *gin.Context) { | 94 | func GetWeeklyStatistics(c *gin.Context) { |
| 86 | db := database.EstablishDBConnection() | 95 | db, err := database.EstablishDBConnection() |
| 96 | if err != nil { | ||
| 97 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 98 | return | ||
| 99 | } | ||
| 87 | defer func(db *sql.DB) { | 100 | defer func(db *sql.DB) { |
| 88 | err := db.Close() | 101 | err := db.Close() |
| 89 | if err != nil { | 102 | if err != nil { |
| @@ -118,7 +131,11 @@ func GetWeeklyStatistics(c *gin.Context) { | |||
| 118 | } | 131 | } |
| 119 | 132 | ||
| 120 | func GetDailyUserStatistics(c *gin.Context) { | 133 | func GetDailyUserStatistics(c *gin.Context) { |
| 121 | db := database.EstablishDBConnection() | 134 | db, err := database.EstablishDBConnection() |
| 135 | if err != nil { | ||
| 136 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 137 | return | ||
| 138 | } | ||
| 122 | defer func(db *sql.DB) { | 139 | defer func(db *sql.DB) { |
| 123 | err := db.Close() | 140 | err := db.Close() |
| 124 | if err != nil { | 141 | if err != nil { |
diff --git a/api/internal/controllers/user.go b/api/internal/controllers/user.go index dbb09cf..fa9617a 100644 --- a/api/internal/controllers/user.go +++ b/api/internal/controllers/user.go | |||
| @@ -15,7 +15,11 @@ func GetUser(c *gin.Context) { | |||
| 15 | c.JSON(http.StatusOK, gin.H{"message": "User found"}) | 15 | c.JSON(http.StatusOK, gin.H{"message": "User found"}) |
| 16 | } | 16 | } |
| 17 | func GetUserPreferences(c *gin.Context) { | 17 | func GetUserPreferences(c *gin.Context) { |
| 18 | db := database.EstablishDBConnection() | 18 | db, err := database.EstablishDBConnection() |
| 19 | if err != nil { | ||
| 20 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 21 | return | ||
| 22 | } | ||
| 19 | defer func(db *sql.DB) { | 23 | defer func(db *sql.DB) { |
| 20 | err := db.Close() | 24 | err := db.Close() |
| 21 | if err != nil { | 25 | if err != nil { |
| @@ -41,7 +45,11 @@ func GetUserPreferences(c *gin.Context) { | |||
| 41 | } | 45 | } |
| 42 | 46 | ||
| 43 | func UpdateUserPreferences(c *gin.Context) { | 47 | func UpdateUserPreferences(c *gin.Context) { |
| 44 | db := database.EstablishDBConnection() | 48 | db, err := database.EstablishDBConnection() |
| 49 | if err != nil { | ||
| 50 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | ||
| 51 | return | ||
| 52 | } | ||
| 45 | defer func(db *sql.DB) { | 53 | defer func(db *sql.DB) { |
| 46 | err := db.Close() | 54 | err := db.Close() |
| 47 | if err != nil { | 55 | if err != nil { |
| @@ -58,7 +66,7 @@ func UpdateUserPreferences(c *gin.Context) { | |||
| 58 | 66 | ||
| 59 | log.Printf("newPreferences: %v", newPreferences) | 67 | log.Printf("newPreferences: %v", newPreferences) |
| 60 | 68 | ||
| 61 | _, err := db.Exec("UPDATE Preferences SET color = ?, size_id = ? WHERE id = ?", newPreferences.Color, newPreferences.SizeID, newPreferences.ID) | 69 | _, err = db.Exec("UPDATE Preferences SET color = ?, size_id = ? WHERE id = ?", newPreferences.Color, newPreferences.SizeID, newPreferences.ID) |
| 62 | if err != nil { | 70 | if err != nil { |
| 63 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) | 71 | c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) |
| 64 | return | 72 | return |
diff --git a/api/internal/database/database.go b/api/internal/database/database.go index 1866655..1a55127 100644 --- a/api/internal/database/database.go +++ b/api/internal/database/database.go | |||
| @@ -2,23 +2,29 @@ package database | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "database/sql" | 4 | "database/sql" |
| 5 | _ "github.com/mattn/go-sqlite3" | 5 | "fmt" |
| 6 | "log" | 6 | "log" |
| 7 | "path/filepath" | 7 | "path/filepath" |
| 8 | "water/api/internal/config" | 8 | "water/api/internal/config" |
| 9 | |||
| 10 | _ "github.com/mattn/go-sqlite3" | ||
| 9 | ) | 11 | ) |
| 10 | 12 | ||
| 11 | func EstablishDBConnection() *sql.DB { | 13 | func EstablishDBConnection() (*sql.DB, error) { |
| 12 | c, err := config.Load() | 14 | c, err := config.Load() |
| 15 | if err != nil { | ||
| 16 | return nil, fmt.Errorf("error while reading config file: %w", err) | ||
| 17 | } | ||
| 13 | 18 | ||
| 14 | driver := c.GetString("DB_DRIVER") | 19 | driver := c.GetString("DB_DRIVER") |
| 15 | path, err := filepath.Abs(c.GetString("DB_PATH")) | 20 | path, err := filepath.Abs(c.GetString("DB_PATH")) |
| 21 | log.Println("my db path: ", path) | ||
| 16 | if err != nil { | 22 | if err != nil { |
| 17 | log.Fatal("There was and error getting the absolute path of the database.") | 23 | return nil, fmt.Errorf("failed to get absolute path of the database: %w", err) |
| 18 | } | 24 | } |
| 19 | db, err := sql.Open(driver, path) | 25 | db, err := sql.Open(driver, path) |
| 20 | if err != nil { | 26 | if err != nil { |
| 21 | panic(err) | 27 | return nil, fmt.Errorf("error while opening the database: %w", err) |
| 22 | } | 28 | } |
| 23 | return db | 29 | return db, nil |
| 24 | } \ No newline at end of file | 30 | } |
diff --git a/api/main b/api/main new file mode 100755 index 0000000..1eeaf51 --- /dev/null +++ b/api/main | |||
| Binary files differ | |||
diff --git a/api/water-api.nginx.conf b/api/water-api.nginx.conf new file mode 100644 index 0000000..73b0d7c --- /dev/null +++ b/api/water-api.nginx.conf | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | server { | ||
| 2 | listen 443 ssl; | ||
| 3 | listen [::]:443 ssl; | ||
| 4 | server_name water-api.example.com; | ||
| 5 | location / { | ||
| 6 | proxy_pass http://localhost:8888; | ||
| 7 | } | ||
| 8 | } | ||
diff --git a/api/water.service b/api/water.service new file mode 100644 index 0000000..eb8a779 --- /dev/null +++ b/api/water.service | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | [Unit] | ||
| 2 | Description=Water is a backend for tracking water consumption | ||
| 3 | StartLimitIntervalSec=600 | ||
| 4 | StartLimitBurst=2 | ||
| 5 | |||
| 6 | [Service] | ||
| 7 | WorkingDirectory=/path/to/water/binary | ||
| 8 | User=water | ||
| 9 | Restart=on-failure | ||
| 10 | RestartSec=30 | ||
| 11 | PermissionsStartOnly=true | ||
| 12 | |||
| 13 | [Install] | ||
| 14 | WantedBy=multi-user.target | ||
diff --git a/db/README.md b/db/README.md index f36e555..1d5ad71 100644 --- a/db/README.md +++ b/db/README.md | |||
| @@ -1,3 +1,11 @@ | |||
| 1 | # The Database | 1 | # The Database |
| 2 | 2 | ||
| 3 | This document describes how to set up the database for your instance of the water application. \ No newline at end of file | 3 | This document describes how to set up the database for your instance of the water application. |
| 4 | |||
| 5 | ## Setup | ||
| 6 | |||
| 7 | The first step is to decide where you are going to keep your database. Once you have decided on where that is going to be add it to your environment: | ||
| 8 | |||
| 9 | ```sh | ||
| 10 | export DB_PATH="path/to/database" | ||
| 11 | ``` \ No newline at end of file | ||
diff --git a/db/scripts/init.sh b/db/scripts/init.sh index 1a8bbde..3baec36 100644 --- a/db/scripts/init.sh +++ b/db/scripts/init.sh | |||
| @@ -1,10 +1,8 @@ | |||
| 1 | PROJECT_DIR=$(pwd) | 1 | PROJECT_DIR=$(pwd) |
| 2 | 2 | ||
| 3 | DB_PATH="$PROJECT_DIR/db/test.sqlite3" | 3 | DB_PATH="$PROJECT_DIR/test.sqlite3" |
| 4 | |||
| 5 | SQL_DIR="$PROJECT_DIR/db/sql" | ||
| 6 | |||
| 7 | 4 | ||
| 5 | SQL_DIR="$PROJECT_DIR/sql" | ||
| 8 | 6 | ||
| 9 | insert_user() { | 7 | insert_user() { |
| 10 | read -p "Enter a username: " username | 8 | read -p "Enter a username: " username |
diff --git a/fe/index.html b/fe/index.html index b6c5f0a..3b39d6f 100644 --- a/fe/index.html +++ b/fe/index.html | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | <meta charset="UTF-8" /> | 4 | <meta charset="UTF-8" /> |
| 5 | <link rel="icon" type="image/svg+xml" href="/vite.svg" /> | 5 | <link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
| 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | 6 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| 7 | <title>Vite + Svelte + TS</title> | 7 | <title>Track Your Water</title> |
| 8 | </head> | 8 | </head> |
| 9 | <body> | 9 | <body> |
| 10 | <div id="app"></div> | 10 | <div id="app"></div> |
diff --git a/fe/src/utils.ts b/fe/src/utils.ts index 9fddf41..41c733d 100644 --- a/fe/src/utils.ts +++ b/fe/src/utils.ts | |||
| @@ -10,5 +10,5 @@ export function processFormInput(form: HTMLFormElement) { | |||
| 10 | 10 | ||
| 11 | export function apiURL (path: string): string { | 11 | export function apiURL (path: string): string { |
| 12 | const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? "http://localhost:8080/api/v1"; | 12 | const baseUrl = import.meta.env?.VITE_API_BASE_URL ?? "http://localhost:8080/api/v1"; |
| 13 | return `${baseUrl}${path}` | 13 | return `${baseUrl}/${path}` |
| 14 | } | 14 | } |
diff --git a/fe/water.nginx.conf b/fe/water.nginx.conf new file mode 100644 index 0000000..a715845 --- /dev/null +++ b/fe/water.nginx.conf | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | server { | ||
| 2 | root /srv/www/water; | ||
| 3 | index index.html index.htm; | ||
| 4 | |||
| 5 | listen 443 ssl; | ||
| 6 | listen [::]:443 ssl; | ||
| 7 | server_name water.example.com; | ||
| 8 | |||
| 9 | location / { | ||
| 10 | try_files $uri $uri/ =404; | ||
| 11 | } | ||
| 12 | } | ||
