feat: add new features such as database watchdog

This commit is contained in:
DaanSelen
2026-04-21 16:27:04 +02:00
parent 610fdffdb8
commit c4a4fafb52
16 changed files with 428 additions and 158 deletions
+3
View File
@@ -2,6 +2,9 @@
*.db *.db
content/* content/*
*.pptx
*.odp
*.mp4
# ---> Go # ---> Go
# If you prefer the allow list template instead of the deny list, see community template: # If you prefer the allow list template instead of the deny list, see community template:
+12 -9
View File
@@ -6,11 +6,10 @@ import (
"eden-server/internal/runtime" "eden-server/internal/runtime"
"log/slog" "log/slog"
"os" "os"
"github.com/gin-gonic/gin"
) )
func main() { func main() {
// configure the logger so we have nice json
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger) slog.SetDefault(logger)
@@ -19,21 +18,25 @@ func main() {
env := runtime.GrabEnvironment() env := runtime.GrabEnvironment()
// checking directories to ensure its expected environment is ready // checking directories to ensure its expected environment is ready
slog.Info("ensuring operating environment") slog.Info("auditing operating environment")
if err := runtime.EnsureOperation(env.WorkDir); err != nil { if err := runtime.EnsureOperation(env.DataDirectory); err != nil {
slog.Error("failed to ensure the operating environment", "error", err) slog.Error("failed to ensure the operating environment", "error", err)
os.Exit(1) os.Exit(1)
} }
slog.Info("finished audit of environment")
// initiating the database connection for which we safe things // initiating the database connection for which we safe things
slog.Info("kicking off database connection") slog.Info("kicking off database connection")
db, err := database.KickoffDatabase() db, err := database.KickoffDatabase(env.DataDirectory)
if err != nil { if err != nil {
slog.Error("failed to initiate a database connection") slog.Error("failed to initiate a database connection")
} }
// get ready to kick off the http api slog.Info("kicking off database watchdog", "watch_interval", env.WatchInterval)
slog.Info("kicking off http api, letting gin take over") database.KickoffDatabaseWatchdog(env, db)
gin.SetMode(gin.ReleaseMode)
api.KickoffApi(env, db) // TO DO make gin log as json
// get ready to kick off the http api with Vue frontend
slog.Info("kicking off http api backend")
api.KickoffApi(logger, env, db)
} }
View File
+4
View File
@@ -5,11 +5,13 @@ go 1.25.0
require ( require (
github.com/gin-gonic/gin v1.12.0 github.com/gin-gonic/gin v1.12.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
gorm.io/datatypes v1.2.7
gorm.io/driver/sqlite v1.6.0 gorm.io/driver/sqlite v1.6.0
gorm.io/gorm v1.31.1 gorm.io/gorm v1.31.1
) )
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect github.com/bytedance/sonic/loader v0.5.0 // indirect
@@ -19,6 +21,7 @@ require (
github.com/go-playground/locales v0.14.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/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/go-sql-driver/mysql v1.8.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
@@ -42,4 +45,5 @@ require (
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect google.golang.org/protobuf v1.36.10 // indirect
gorm.io/driver/mysql v1.5.6 // indirect
) )
+30
View File
@@ -1,3 +1,5 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE=
@@ -23,15 +25,30 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
@@ -46,6 +63,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 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 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 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 h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -84,6 +103,8 @@ golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
@@ -95,7 +116,16 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
gorm.io/driver/sqlserver v1.6.0 h1:VZOBQVsVhkHU/NzNhRJKoANt5pZGQAS1Bwc6m6dgfnc=
gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOzehntWw=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
+28 -5
View File
@@ -9,19 +9,42 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
func KickoffApi(env runtime.Environment, db *gorm.DB) { const (
r := gin.Default() okMes string = "OK"
ieMes string = "An internal error occured, contact your administrator"
)
type RespObj struct {
Msg string `json:"msg"`
Data any `json:"data"`
}
// All error messages from slog must have an error field with the golang error
// See bottom the the kickoff function for details
func KickoffApi(logger *slog.Logger, env runtime.Environment, db *gorm.DB) {
gin.SetMode(gin.ReleaseMode)
// For a nice looking logger:
// r := gin.Default()
// JSON logger: https://gin-gonic.com/en/docs/logging/structured-logging/
r := gin.New()
r.Use(slogMiddleware(logger))
r.Use(gin.Recovery())
api := r.Group("/api") api := r.Group("/api")
spawnRoutes(api, env, db) spawnApiRoutes(api /*env,*/, db)
file := r.Group("/file")
spawnFileRoutes(file, env, db)
r.Static("/assets", "./web/frontend/dist/assets") r.Static("/assets", "./web/frontend/dist/assets")
r.NoRoute(func(c *gin.Context) { r.NoRoute(func(c *gin.Context) {
c.File("./web/frontend/dist/index.html") c.File("./web/frontend/dist/index.html")
}) })
err := r.Run(fmt.Sprintf("%s:%s", env.Hostname, env.Port)) err := r.Run(fmt.Sprintf("%s:%d", env.Hostname, env.Port))
if err != nil { if err != nil {
slog.Error("failed to start the Gin server due to: " + err.Error()) slog.Error("failed to start the Gin server", "error", err)
} }
} }
+34
View File
@@ -0,0 +1,34 @@
package api
import (
"log/slog"
"time"
"github.com/gin-gonic/gin"
)
func slogMiddleware(logger *slog.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
logger.Info("request",
slog.String("method", c.Request.Method),
slog.String("path", path),
slog.String("query", query),
slog.Int("status", c.Writer.Status()),
slog.Duration("latency", time.Since(start)),
slog.String("client_ip", c.ClientIP()),
slog.Int("body_size", c.Writer.Size()),
)
if len(c.Errors) > 0 {
for _, err := range c.Errors {
logger.Error("request error", slog.String("error", err.Error()))
}
}
}
}
-87
View File
@@ -1,87 +0,0 @@
package api
import (
"eden-server/internal/database"
"eden-server/internal/runtime"
"log/slog"
"net/http"
"path/filepath"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"gorm.io/gorm"
)
// 0: unspecified
// 1: video
// 2: presentation
// 3: internet URL
func categorizeFile(ext string) string {
switch ext {
case ".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4a":
return "video"
case ".pptx", ".ppt", ".key", ".odp":
return "presentation"
default:
return "unspecified"
}
}
func spawnRoutes(api *gin.RouterGroup, env runtime.Environment, db *gorm.DB) {
// The Root endpoint '/' displays a simple HTML template.
// from root: ./templates/index.html
api.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html",
gin.H{
"title": env.Codename,
"version": env.Version,
},
)
})
// prefix: api
// Display the information on what is going on at the moment
api.GET("/api/status", func(c *gin.Context) {
state, err := database.GetAppState(db)
if err != nil {
slog.Warn("unable to determine state")
}
c.JSON(http.StatusOK, state)
})
// define the upload route
api.POST("/api/upload", func(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
slog.Error("missing file upload", "error", err)
c.JSON(http.StatusBadRequest, gin.H{"msg": "file is required"})
return
}
ext := filepath.Ext(file.Filename)
catName := categorizeFile(ext)
safeName := uuid.New().String()[:8] + "_" + catName + ext
destPath := filepath.Join(env.WorkDir, "content", "fresh", safeName)
if err := c.SaveUploadedFile(file, destPath); err != nil {
slog.Error("failed to receive the file over http:", "error", err)
c.JSON(http.StatusInternalServerError, gin.H{"msg": "internal error"})
} else {
database.RegisterFile(db, catName, destPath)
c.JSON(http.StatusCreated, gin.H{"msg": "file has succesfully been uploaded"})
}
})
// define a route to check what is registered
api.GET("/api/available", func(c *gin.Context) {
files, err := database.GetFiles(db)
if err != nil {
slog.Error("failed to retrieve available files", "error", err)
c.JSON(http.StatusInternalServerError, gin.H{"msg": "internal error"})
return
}
c.JSON(http.StatusOK, files)
})
}
+62
View File
@@ -0,0 +1,62 @@
package api
import (
"eden-server/internal/database"
"log/slog"
"net/http"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
// 0: unspecified
// 1: video
// 2: presentation
// 3: internet URL
func categorizeFilemode(ext string) database.Mode {
switch ext {
case ".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4a":
return database.ModeVideo
case ".pptx", ".ppt", ".key", ".odp":
return database.ModePresentation
default:
return database.ModeUnspecified
}
}
func spawnApiRoutes(api *gin.RouterGroup /* env runtime.Environment,*/, db *gorm.DB) {
// prefix: api
// Display the information on what is going on at the moment
api.GET("/status", func(c *gin.Context) {
state, err := database.GetState(db)
if err != nil {
slog.Error("unable to determine state", "error", err)
c.JSON(http.StatusInternalServerError, RespObj{
Msg: ieMes,
})
return
}
c.JSON(http.StatusOK, RespObj{
Msg: okMes,
Data: state,
})
})
// define a route to check what is registered
api.GET("/available", func(c *gin.Context) {
files, err := database.GetFiles(db)
if err != nil {
slog.Error("failed to retrieve available files", "error", err)
c.JSON(http.StatusInternalServerError, RespObj{
Msg: ieMes,
})
return
}
c.JSON(http.StatusOK, RespObj{
Msg: okMes,
Data: files,
})
})
}
+79
View File
@@ -0,0 +1,79 @@
package api
import (
"eden-server/internal/crypto"
"eden-server/internal/database"
"eden-server/internal/runtime"
"log"
"log/slog"
"net/http"
"path/filepath"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"gorm.io/gorm"
)
func spawnFileRoutes(file *gin.RouterGroup, env runtime.Environment, db *gorm.DB) {
// /file/<file-name>
file.GET("/:filename", func(c *gin.Context) {
f := c.Param("filename")
p := filepath.Join(env.DataDirectory, "content", f)
c.File(p)
})
// define the upload route
// /file/upload
file.POST("/upload", func(c *gin.Context) {
f, err := c.FormFile("file")
if err != nil {
slog.Error("failed to get file details from request", "error", err)
c.JSON(http.StatusBadRequest, RespObj{
Msg: "a file is required",
})
return
}
e := filepath.Ext(f.Filename)
m := categorizeFilemode(e)
if m == database.ModeUnspecified {
slog.Warn("discarding file since its filetype is unsupported")
c.JSON(http.StatusUnsupportedMediaType, RespObj{
Msg: "unsupported filetype",
})
return
}
safeName := uuid.New().String()[:8] + "_" + string(m) + e
destPath := filepath.Join(env.DataDirectory, "content", safeName)
if err := c.SaveUploadedFile(f, destPath); err != nil {
slog.Error("failed to receive the file over http:", "error", err)
c.JSON(http.StatusInternalServerError, RespObj{
Msg: ieMes,
})
return
}
cSum, err := crypto.CalculateHash(destPath)
if err != nil {
slog.Error("failed to calculate hash of file at given path", "error", err)
c.JSON(http.StatusInternalServerError, RespObj{
Msg: ieMes,
})
}
log.Println(cSum)
fData := database.File{
Mode: m,
GivenName: f.Filename,
Filepath: destPath,
Checksum: cSum,
}
database.RegisterFile(db, fData)
c.JSON(http.StatusCreated, RespObj{
Msg: "file has succesfully been uploaded",
})
})
}
+27
View File
@@ -0,0 +1,27 @@
package crypto
import (
"crypto/sha512"
"encoding/hex"
"io"
"os"
)
func CalculateHash(p string) (string, error) {
f, err := os.Open(p)
if err != nil {
return "", err
}
defer f.Close()
h := sha512.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
sum := h.Sum(nil)
// return the sha checksum in hex
return hex.EncodeToString(sum), nil
// alternatively return in base64
//return base64.StdEncoding.EncodeToString(sum), nil
}
+41 -12
View File
@@ -1,36 +1,65 @@
package database package database
import ( import (
"eden-server/internal/runtime"
"path/filepath"
"time"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
"gorm.io/gorm" "gorm.io/gorm"
"gorm.io/gorm/schema"
) )
func KickoffDatabase() (*gorm.DB, error) { //var watchdogStop = make(chan struct{})
db, err := gorm.Open(sqlite.Open("garden.db"), &gorm.Config{
NamingStrategy: schema.NamingStrategy{ func KickoffDatabase(workDir string) (*gorm.DB, error) {
SingularTable: true, dbLoc := filepath.Join(workDir, "data", "garden.db")
}, db, err := gorm.Open(sqlite.Open(dbLoc), &gorm.Config{})
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := db.AutoMigrate(&AppState{}); err != nil { if err := db.AutoMigrate(&State{}); err != nil {
return nil, err return nil, err
} }
if err := db.AutoMigrate(&Files{}); err != nil { if err := db.AutoMigrate(&File{}); err != nil {
return nil, err return nil, err
} }
// create the first row if it does not exist yet // create the first row if it does not exist yet
if err := db.FirstOrCreate(&AppState{}, AppState{ if err := db.FirstOrCreate(&State{}, State{
ID: 1, ID: 0,
Mode: "unspecified", Mode: "unspecified",
Running: false,
}).Error; err != nil { }).Error; err != nil {
return nil, err return nil, err
} }
return db, nil return db, nil
} }
func KickoffDatabaseWatchdog(env runtime.Environment, db *gorm.DB) {
timeInterval := time.Second * time.Duration(env.WatchInterval)
ticker := time.NewTicker(timeInterval)
go func() {
defer ticker.Stop()
/*
// Possible future mechanism to stop the watchdog
// must be inside a non-conditional for loop
select {
case <-ticker.C: // ticker event
watchdog(env.DataDirectory, db)
case <-watchdogStop:
return
}
*/
// run the watchdog function once to see if all is well.
watchdog(env.DataDirectory, db)
// then defer to a decoupled/disowned golang goroutine
for range ticker.C {
watchdog(env.DataDirectory, db)
}
}()
}
+42 -12
View File
@@ -1,19 +1,49 @@
package database package database
type AppState struct { import (
"time"
"gorm.io/datatypes"
)
type Mode string
const (
ModeUnspecified Mode = "unspecified"
ModeVideo Mode = "video"
ModePresentation Mode = "presentation"
ModeInternet Mode = "internet"
)
type State struct {
ID int `gorm:"primaryKey"` ID int `gorm:"primaryKey"`
// Mode = // unspecified
// 0: unspecified // video
// 1: video // presentation
// 2: presentation // internet URL
// 3: internet URL Mode Mode `gorm:"type:varchar(20);not null"` // Must specify what kind of file it is
Mode string Targets datatypes.JSON
Running bool Location string // Must be the location where the file is downloadable on the API
UpdatedAt time.Time
} }
type Files struct { type Device struct {
ID int `gorm:"primaryKey"` ID int `gorm:"primaryKey"`
Mode string DeviceType string
Filename string Hostname string
Filepath string RemoteAddress string
Alive bool
Compliant bool
CreatedAt time.Time
UpdatedAt time.Time
}
type File struct {
ID int `gorm:"primaryKey"`
Mode Mode `gorm:"type:varchar(20);not null"`
GivenName string
Filepath string
Checksum string // base64 encoded sha512 checksum
CreatedAt time.Time
UpdatedAt time.Time
} }
+6 -14
View File
@@ -1,29 +1,21 @@
package database package database
import ( import (
"path/filepath"
"gorm.io/gorm" "gorm.io/gorm"
) )
func GetAppState(db *gorm.DB) (AppState, error) { func GetState(db *gorm.DB) (State, error) {
var state AppState var state State
return state, db.First(&state).Error return state, db.First(&state).Error
} }
func GetFiles(db *gorm.DB) ([]Files, error) { func GetFiles(db *gorm.DB) ([]File, error) {
var files []Files var files []File
return files, db.Find(&files).Error return files, db.Find(&files).Error
} }
func RegisterFile(db *gorm.DB, category, fullPath string) error { func RegisterFile(db *gorm.DB, f File) error {
file := Files{ return db.Create(&f).Error
Mode: category,
Filename: filepath.Base(fullPath),
Filepath: fullPath,
}
return db.Create(&file).Error
} }
+20
View File
@@ -0,0 +1,20 @@
package database
import (
"log"
"log/slog"
"gorm.io/gorm"
)
func watchdog(w string, db *gorm.DB) {
slog.Info("performing the watchdog cycle")
files, err := GetFiles(db)
if err != nil {
slog.Error("failed to retrieve the files indexed from the database", "error", err)
return
}
log.Println(files)
}
+35 -14
View File
@@ -1,44 +1,65 @@
package runtime package runtime
import ( import (
"log/slog"
"os" "os"
"path/filepath" "path/filepath"
"strconv"
) )
type Environment struct { type Environment struct {
Version string Version string
Codename string Codename string
DataDirectory string
ContentDirectory string
Hostname string Hostname string
Port string Port int
WorkDir string WatchInterval int
} }
func safeGrab(key, fallback string) string { // part of environment checking
func safeStringGrab(key, fallback string) string {
if v, ok := os.LookupEnv(key); ok { if v, ok := os.LookupEnv(key); ok {
return v return v
} }
return fallback return fallback
} }
func safeIntGrab(key string, fallback int) int {
if v, ok := os.LookupEnv(key); ok {
if i, err := strconv.Atoi(v); err == nil {
return i
}
}
return fallback
}
func GrabEnvironment() Environment { func GrabEnvironment() Environment {
cwd, err := os.Getwd()
if err != nil {
cwd = "."
}
fbBase := filepath.Join(cwd, "data")
fbContent := filepath.Join(fbBase, "content")
return Environment{ return Environment{
Version: safeGrab("VERSION", "0.0.1"), Version: safeStringGrab("VERSION", "0.0.1"),
Codename: safeGrab("CODENAME", "Magical Anomaly"), Codename: safeStringGrab("CODENAME", "Magical Anomaly"),
Hostname: safeGrab("HOSTNAME", "0.0.0.0"), DataDirectory: safeStringGrab("DATA_DIR", fbBase),
Port: safeGrab("PORT", "8080"), ContentDirectory: safeStringGrab("CONTENT_DIR", fbContent),
WorkDir: safeGrab("OPERATIONDIR", "."), Hostname: safeStringGrab("HOSTNAME", "0.0.0.0"),
Port: safeIntGrab("PORT", 8080),
WatchInterval: safeIntGrab("WATCHDOG_INTERVAL", 60),
} }
} }
// part of filesystem checking
func EnsureOperation(workDir string) error { func EnsureOperation(workDir string) error {
slog.Info("starting audit on: " + workDir)
nDirs := []string{ nDirs := []string{
workDir, workDir,
filepath.Join(workDir, "content"), filepath.Join(workDir, "data"),
filepath.Join(workDir, "content", "fresh"), filepath.Join(workDir, "data", "content"),
filepath.Join(workDir, "content", "archive"),
filepath.Join(workDir, "web"), filepath.Join(workDir, "web"),
} }