diff --git a/.air.toml b/.air.toml index 0bcd6ef..dd564a2 100644 --- a/.air.toml +++ b/.air.toml @@ -5,7 +5,7 @@ tmp_dir = "tmp" [build] args_bin = ["serve"] bin = "./main.out" - cmd = "make build" + cmd = "go build -o main.out server/main.go" delay = 1000 exclude_dir = ["tmp", "vendor", "testdata", "venv", "scanners"] exclude_file = [] @@ -14,7 +14,7 @@ tmp_dir = "tmp" follow_symlink = false full_bin = "" include_dir = [] - include_ext = ["go", "tpl", "tmpl", "html", "templ", "toml"] + include_ext = ["go", "tpl", "tmpl", "html", "templ", "toml", "css", "js"] include_file = [] kill_delay = "0s" log = "build-errors.log" diff --git a/.gitignore b/.gitignore index cb07b4c..1a4ff37 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ *.swp -storage/ venv/ +/storage/ scanners/ole/venv/ # Created by https://www.toptal.com/developers/gitignore/api/linux,go,vim,visualstudiocode,macos,windows # Edit at https://www.toptal.com/developers/gitignore?templates=linux,go,vim,visualstudiocode,macos,windows diff --git a/.templui.json b/.templui.json new file mode 100644 index 0000000..171c637 --- /dev/null +++ b/.templui.json @@ -0,0 +1,5 @@ +{ + "componentsDir": "server/web/components", + "utilsDir": "server/web/utils", + "moduleName": "git.jmbit.de/jmb/scanfile" +} \ No newline at end of file diff --git a/Makefile b/Makefile index 7dd3fc6..c82c186 100644 --- a/Makefile +++ b/Makefile @@ -25,10 +25,18 @@ test: clean: echo "Cleaning..." rm -f main.out - rm -rf db.sqlite rm -rf sessions/* rm -rf storage/* +dev: + make -j3 templ-dev tailwind-dev watch + +templ-dev: + templ generate --watch --proxy="http://localhost:8080" --open-browser=false + +tailwind-dev: + tailwindcss -o server/web/assets/styles.css -i input.css --watch + # Live Reload watch: @if command -v air > /dev/null; then \ diff --git a/config.toml b/config.toml index cab2d47..bae0f3b 100644 --- a/config.toml +++ b/config.toml @@ -9,6 +9,9 @@ tls = false host = "localhost" user = "scanfile" port = 5432 -password = "CHANGEME" +password = "Sequel-Gutless-Epilogue1-Scorch-Nuclei" database = "scanfile" +[store] +path = "./storage/files/" + diff --git a/go.mod b/go.mod index e11365f..ab2e30e 100644 --- a/go.mod +++ b/go.mod @@ -4,9 +4,12 @@ go 1.24.1 require ( github.com/a-h/templ v0.3.865 + github.com/google/uuid v1.6.0 github.com/gorilla/securecookie v1.1.2 github.com/gorilla/sessions v1.4.0 + github.com/h2non/filetype v1.1.3 github.com/joho/godotenv v1.5.1 + github.com/lib/pq v1.10.9 github.com/spf13/viper v1.20.1 xorm.io/xorm v1.3.9 ) @@ -17,9 +20,6 @@ require ( github.com/go-viper/mapstructure/v2 v2.2.1 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang/snappy v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.5 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/mattn/go-sqlite3 v1.14.27 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -32,9 +32,7 @@ require ( github.com/spf13/pflag v1.0.6 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect - go.uber.org/atomic v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.37.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.25.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect diff --git a/go.sum b/go.sum index 304e724..482e4e1 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,6 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -github.com/a-h/templ v0.3.857 h1:6EqcJuGZW4OL+2iZ3MD+NnIcG7nGkaQeF2Zq5kf9ZGg= -github.com/a-h/templ v0.3.857/go.mod h1:qhrhAkRFubE7khxLZHsBFHfX+gWwVNKbzKeF9GlPV4M= github.com/a-h/templ v0.3.865 h1:nYn5EWm9EiXaDgWcMQaKiKvrydqgxDUtT1+4zU2C43A= github.com/a-h/templ v0.3.865/go.mod h1:oLBbZVQ6//Q6zpvSMPTuBK0F3qOtBdFBcGRspcT+VNQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -14,22 +12,16 @@ github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 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/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI= -github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 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/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= @@ -43,14 +35,10 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= +github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -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-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= -github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -64,11 +52,12 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 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.27 h1:drZCnuvf37yPfs95E5jd9s3XhdVWLal+6BOK6qrv6IU= github.com/mattn/go-sqlite3 v1.14.27/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= 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= @@ -79,8 +68,6 @@ github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -89,18 +76,12 @@ github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6O github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.8.0 h1:gEN9K4b8Xws4EX0+a0reLmhq8moKn7ntRlQYgjPeCDk= github.com/spf13/cast v1.8.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -109,46 +90,30 @@ github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= -golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= -golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= -golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= +golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -158,7 +123,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= @@ -181,8 +145,6 @@ modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM= -xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/builder v0.3.13 h1:a3jmiVVL19psGeXx8GIurTp7p0IIgqeDmwhcR6BAOAo= xorm.io/builder v0.3.13/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE= xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU= diff --git a/input.css b/input.css new file mode 100644 index 0000000..9bcb5b0 --- /dev/null +++ b/input.css @@ -0,0 +1,90 @@ +@import 'tailwindcss'; + +@custom-variant dark (&:where(.dark, .dark *)); + +@theme inline { + --color-border: var(--border); + --color-input: var(--input); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(---accent-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-ring: var(--ring); + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + + --container-2xl: 1400px; +} + +:root { + --background: hsl(0 0% 100%); + --foreground: hsl(240 10% 3.9%); + --muted: hsl(240 4.8% 95.9%); + --muted-foreground: hsl(240 3.8% 46.1%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(240 10% 3.9%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(240 10% 3.9%); + --border: hsl(240 5.9% 90%); + --input: hsl(240 5.9% 90%); + --primary: hsl(240 5.9% 10%); + --primary-foreground: hsl(0 0% 98%); + --secondary: hsl(240 4.8% 95.9%); + --secondary-foreground: hsl(240 5.9% 10%); + --accent: hsl(240 4.8% 95.9%); + --accent-foreground: hsl(240 5.9% 10%); + --destructive: hsl(0 84.2% 60.2%); + --destructive-foreground: hsl(0 0% 98%); + --ring: hsl(240 5.9% 10%); + --radius: 0.5rem; +} + +.dark { + --background: hsl(240 10% 3.9%); + --foreground: hsl(0 0% 98%); + --muted: hsl(240 3.7% 15.9%); + --muted-foreground: hsl(240 5% 64.9%); + --popover: hsl(240 10% 3.9%); + --popover-foreground: hsl(0 0% 98%); + --card: hsl(240 10% 3.9%); + --card-foreground: hsl(0 0% 98%); + --border: hsl(240 3.7% 15.9%); + --input: hsl(240 3.7% 15.9%); + --primary: hsl(0 0% 98%); + --primary-foreground: hsl(240 5.9% 10%); + --secondary: hsl(240 3.7% 15.9%); + --secondary-foreground: hsl(0 0% 98%); + --accent: hsl(240 3.7% 15.9%); + --accent-foreground: hsl(0 0% 98%); + --destructive: hsl(0 62.8% 30.6%); + --destructive-foreground: hsl(0 0% 98%); + --ring: hsl(240 4.9% 83.9%); + --radius: 0.5rem; +} + +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + font-feature-settings: + "rlig" 1, + "calt" 1; + } +} diff --git a/server/internal/config/config.go b/server/internal/config/config.go index ac557b2..c861571 100644 --- a/server/internal/config/config.go +++ b/server/internal/config/config.go @@ -36,11 +36,13 @@ func setDefaults() { viper.SetDefault("web.cert", "/etc/ssl/certs/ssl-cert-snakeoil.pem") viper.SetDefault("web.key", "/etc/ssl/key/ssl-cert-snakeoil.key") viper.SetDefault("web.loghttp", true) - viper.SetDefault("db.host", "localhost") - viper.SetDefault("db.port", 5432) - viper.SetDefault("db.user", "scanfile") - viper.SetDefault("db.database", "scanfile") - viper.SetDefault("db.password", "CHANGEME") + viper.SetDefault("web.maxfilesizemb", 100) + viper.SetDefault("db.host", "localhost") + viper.SetDefault("db.port", 5432) + viper.SetDefault("db.user", "scanfile") + viper.SetDefault("db.database", "scanfile") + viper.SetDefault("db.password", "CHANGEME") + viper.SetDefault("store.path", "./storage/files/") } func SaveConfig() error { diff --git a/server/internal/database/db.go b/server/internal/database/db.go index 1aa8eb1..d77fb28 100644 --- a/server/internal/database/db.go +++ b/server/internal/database/db.go @@ -5,7 +5,7 @@ import ( "log/slog" "net/url" - _ "github.com/jackc/pgx/v5" + _ "github.com/lib/pq" "github.com/spf13/viper" "xorm.io/xorm" "xorm.io/xorm/names" @@ -17,24 +17,34 @@ func Connect() (*xorm.Engine, error) { if engine != nil { return engine, nil } - dburl := url.URL{ - Scheme: "postgres", - User: url.UserPassword(viper.GetString("db.user"), viper.GetString("db.password")), - Host: fmt.Sprintf("%s:%d", viper.GetString("db.host"), viper.GetInt("db.port")), - Path: fmt.Sprintf("/%s", viper.GetString("db.database")), - } - engi, err := xorm.NewEngine("pgx", dburl.String()) - if err != nil { - slog.Error("Could not connect to DB", "url", dburl.Redacted(), "error", err) - return nil, err - } - engi.SetMapper(names.LintGonicMapper) - err = engi.Sync(new(User), new(File)) - if err != nil { - slog.Error("Error syncing DB schema", "error", err) - } - engine = engi + dburl := url.URL{ + Scheme: "postgres", + User: url.UserPassword(viper.GetString("db.user"), viper.GetString("db.password")), + Host: fmt.Sprintf("%s:%d", viper.GetString("db.host"), viper.GetInt("db.port")), + Path: fmt.Sprintf("/%s", viper.GetString("db.database")), + } + engi, err := xorm.NewEngine("postgres", dburl.String()) + if err != nil { + slog.Error("Could not connect to DB", "url", dburl.Redacted(), "error", err) + return nil, err + } + engi.SetMapper(names.LintGonicMapper) + err = engi.Sync(new(User), new(File)) + if err != nil { + slog.Error("Error syncing DB schema", "error", err) + } + engine = engi - return engi, nil + return engi, nil } +func Ping() error { + err := engine.DB().Ping() + if err != nil { + slog.Error("Error connecting to DB", "error", err) + return err + } else { + slog.Info("DB: Pong") + return nil + } +} diff --git a/server/internal/database/file.go b/server/internal/database/file.go index ca2c95a..585c902 100644 --- a/server/internal/database/file.go +++ b/server/internal/database/file.go @@ -1,19 +1,65 @@ package database -import "time" +import ( + "mime/multipart" + "time" + + "git.jmbit.de/jmb/scanfile/server/internal/store" +) type File struct { - Id int64 - Uuid string //used for file blob storage etc. - Name string //Name of the file - Description string //Any text to add to it for context - MimeType string - CreatedAt time.Time `xorm:"create"` - UpdatedAt time.Time `xorm:"updated"` + Id int64 + Uuid string //used for file blob storage etc. + Name string //Name of the file + Description string //Any text to add to it for context + MimeType string + Size int64 + CreatedAt time.Time `xorm:"create"` + UpdatedAt time.Time `xorm:"updated"` } -//Insert File to DB +// Insert File to DB func (f File) Insert() error { - _, err :=engine.InsertOne(f) - return err + _, err := engine.InsertOne(f) + return err +} + +// CreateFile() creates the filesystem object and the DB entry for a file +func CreateFile(name string, fileData multipart.File, size int64) (File, error) { + file := File{ + Name: name, + Size: size, + } + uuid, err := store.SaveFile(fileData) + if err != nil { + return file, err + } + file.Uuid = uuid + + file.MimeType, _ = store.GetFileType(file.Uuid) + + err = file.Insert() + if err != nil { + return file, nil + } + + return file, nil +} + +func FileByID(id int64) (File, error) { + file := File{Id: id} + success, err := engine.Get(&file) + if err != nil || success == false { + return file, err + } + return file, nil +} + +func FileByUUID(uuid string) (File, error) { + file := File{Uuid: uuid} + success, err := engine.Get(&file) + if err != nil || success == false { + return file, err + } + return file, nil } diff --git a/server/internal/database/user.go b/server/internal/database/user.go index ee17d87..979393a 100644 --- a/server/internal/database/user.go +++ b/server/internal/database/user.go @@ -1,8 +1,8 @@ package database type User struct { - Id int64 - Name string `xorm:"not null unique"` - PwHash string //Password hash - Role int64 `xorm:"default 0"` + Id int64 + Name string `xorm:"not null unique"` + PwHash string //Password hash + Role int64 `xorm:"default 0"` } diff --git a/server/internal/server/routes.go b/server/internal/server/routes.go index d9323e7..af93d8e 100644 --- a/server/internal/server/routes.go +++ b/server/internal/server/routes.go @@ -1,14 +1,17 @@ package server import ( - "git.jmbit.de/jmb/scanfile/server/web" "net/http" + + "git.jmbit.de/jmb/scanfile/server/web" ) func RegisterRoutes() *http.ServeMux { mux := http.NewServeMux() mux.HandleFunc("/", web.IndexWebHandler) mux.HandleFunc("/about", web.AboutWebHandler) + mux.HandleFunc("/files/{uuid}", web.FileViewWebHandler) + mux.HandleFunc("POST /upload", web.IndexUploadHandler) mux.Handle("/assets/", http.FileServer(http.FS(web.Files))) return mux } diff --git a/server/internal/server/server.go b/server/internal/server/server.go index 8385b62..13b99e2 100644 --- a/server/internal/server/server.go +++ b/server/internal/server/server.go @@ -15,9 +15,9 @@ func NewServer() *http.Server { host := viper.GetString("web.host") middlewareStack := middlewares.CreateStack() - middlewareStack = middlewares.CreateStack( - middlewares.Logging, - ) + middlewareStack = middlewares.CreateStack( + middlewares.Logging, + ) // Declare Server config server := &http.Server{ diff --git a/server/internal/store/filetype.go b/server/internal/store/filetype.go new file mode 100644 index 0000000..a094798 --- /dev/null +++ b/server/internal/store/filetype.go @@ -0,0 +1,34 @@ +package store + +import ( + "github.com/h2non/filetype" + "log/slog" + "os" +) + +// Returns the MIME type of a file +func GetFileType(fileId string) (string, error) { + path, err := absPath(fileId) + if err != nil { + return "application/octet-stream", nil + } + file, err := os.Open(path) + if err != nil { + slog.Error("Storage not accessible", "error", err, "fileid", fileId) + return "application/octet-stream", err + } + + // We only have to pass the file header = first 261 bytes + head := make([]byte, 261) + file.Read(head) + kind, err := filetype.Match(head) + if err != nil { + slog.Error("Could not determine file type", "error", err, "fileid", fileId) + return "application/octet-stream", err + } + if kind == filetype.Unknown { + return "application/octet-stream", nil + } + + return kind.MIME.Value, nil +} diff --git a/server/internal/store/store.go b/server/internal/store/store.go new file mode 100644 index 0000000..390958f --- /dev/null +++ b/server/internal/store/store.go @@ -0,0 +1,79 @@ +package store + +import ( + "io" + "log/slog" + "mime/multipart" + "os" + "path/filepath" + + "github.com/google/uuid" + "github.com/spf13/viper" +) + +func SetupStore() { + path, err := filepath.Abs(viper.GetString("store.path")) + if err != nil { + slog.Error("could get full create storage path", "error", err, "path", viper.GetString("store.path")) + panic(err) + } + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + slog.Error("could get create storage directory", "error", err, "path", viper.GetString("store.path")) + } + slog.Info("Initialized File storage directory") +} + +func SaveFile(file multipart.File) (string, error) { + path, err := filepath.Abs(viper.GetString("store.path")) + if err != nil { + slog.Error("could not save file,", "error", err) + return "", err + } + fileName, err := uuid.NewRandom() + if err != nil { + slog.Error("could not save file,", "error", err) + return "", err + } + osFile, err := os.Create(filepath.Join(path, fileName.String())) + if err != nil { + slog.Error("could not write to file on disk,", "error", err) + return "", err + } + _, err = io.Copy(osFile, file) + if err != nil { + slog.Error("could not save file,", "error", err) + return "", err + } + + return fileName.String(), nil +} + +func OpenFile(fileId string) (*os.File, error) { + path, err := filepath.Abs(viper.GetString("store.path")) + if err != nil { + slog.Error("Storage directory not accessible", "error", err) + return nil, err + } + file, err := os.Open(filepath.Join(path, fileId)) + + return file, err +} + +func DeleteFile(fileId string) error { + path, err := filepath.Abs(viper.GetString("store.path")) + if err != nil { + slog.Error("Storage directory not accessible", "error", err) + return err + } + return os.Remove(path) +} + +func absPath(fileID string) (string, error) { + path, err := filepath.Abs(viper.GetString("store.path")) + if err != nil { + slog.Error("could not get full path for file,", "error", err) + return "", err + } + return path, nil +} diff --git a/server/main.go b/server/main.go index b04483e..0040716 100644 --- a/server/main.go +++ b/server/main.go @@ -22,7 +22,9 @@ import ( "os" "git.jmbit.de/jmb/scanfile/server/internal/config" + "git.jmbit.de/jmb/scanfile/server/internal/database" "git.jmbit.de/jmb/scanfile/server/internal/server" + "git.jmbit.de/jmb/scanfile/server/internal/store" "github.com/spf13/viper" ) @@ -30,6 +32,9 @@ func main() { log.SetOutput(os.Stderr) slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, nil))) config.ReadConfigFile("") + database.Connect() + database.Ping() + store.SetupStore() if viper.GetBool("web.tls") { server.NewServer().ListenAndServeTLS(viper.GetString("web.cert"), viper.GetString("web.key")) } else { diff --git a/server/web/about.go b/server/web/about.go index 5a1a734..edc3510 100644 --- a/server/web/about.go +++ b/server/web/about.go @@ -1,6 +1,5 @@ package web - import ( "log/slog" "net/http" diff --git a/server/web/about_templ.go b/server/web/about_templ.go index 0908614..c727698 100644 --- a/server/web/about_templ.go +++ b/server/web/about_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.865 package web //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/server/web/assets/alpine.min.js b/server/web/assets/alpine.min.js new file mode 100644 index 0000000..2fdd6ec --- /dev/null +++ b/server/web/assets/alpine.min.js @@ -0,0 +1,5 @@ +(()=>{var nt=!1,it=!1,W=[],ot=-1;function Ut(e){Rn(e)}function Rn(e){W.includes(e)||W.push(e),Mn()}function Wt(e){let t=W.indexOf(e);t!==-1&&t>ot&&W.splice(t,1)}function Mn(){!it&&!nt&&(nt=!0,queueMicrotask(Nn))}function Nn(){nt=!1,it=!0;for(let e=0;ee.effect(t,{scheduler:r=>{st?Ut(r):r()}}),at=e.raw}function ct(e){N=e}function Yt(e){let t=()=>{};return[n=>{let i=N(n);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),$(i))},i},()=>{t()}]}function ve(e,t){let r=!0,n,i=N(()=>{let o=e();JSON.stringify(o),r?n=o:queueMicrotask(()=>{t(o,n),n=o}),r=!1});return()=>$(i)}var Xt=[],Zt=[],Qt=[];function er(e){Qt.push(e)}function te(e,t){typeof t=="function"?(e._x_cleanups||(e._x_cleanups=[]),e._x_cleanups.push(t)):(t=e,Zt.push(t))}function Ae(e){Xt.push(e)}function Oe(e,t,r){e._x_attributeCleanups||(e._x_attributeCleanups={}),e._x_attributeCleanups[t]||(e._x_attributeCleanups[t]=[]),e._x_attributeCleanups[t].push(r)}function lt(e,t){e._x_attributeCleanups&&Object.entries(e._x_attributeCleanups).forEach(([r,n])=>{(t===void 0||t.includes(r))&&(n.forEach(i=>i()),delete e._x_attributeCleanups[r])})}function tr(e){for(e._x_effects?.forEach(Wt);e._x_cleanups?.length;)e._x_cleanups.pop()()}var ut=new MutationObserver(mt),ft=!1;function ue(){ut.observe(document,{subtree:!0,childList:!0,attributes:!0,attributeOldValue:!0}),ft=!0}function dt(){kn(),ut.disconnect(),ft=!1}var le=[];function kn(){let e=ut.takeRecords();le.push(()=>e.length>0&&mt(e));let t=le.length;queueMicrotask(()=>{if(le.length===t)for(;le.length>0;)le.shift()()})}function m(e){if(!ft)return e();dt();let t=e();return ue(),t}var pt=!1,Se=[];function rr(){pt=!0}function nr(){pt=!1,mt(Se),Se=[]}function mt(e){if(pt){Se=Se.concat(e);return}let t=[],r=new Set,n=new Map,i=new Map;for(let o=0;o{s.nodeType===1&&s._x_marker&&r.add(s)}),e[o].addedNodes.forEach(s=>{if(s.nodeType===1){if(r.has(s)){r.delete(s);return}s._x_marker||t.push(s)}})),e[o].type==="attributes")){let s=e[o].target,a=e[o].attributeName,c=e[o].oldValue,l=()=>{n.has(s)||n.set(s,[]),n.get(s).push({name:a,value:s.getAttribute(a)})},u=()=>{i.has(s)||i.set(s,[]),i.get(s).push(a)};s.hasAttribute(a)&&c===null?l():s.hasAttribute(a)?(u(),l()):u()}i.forEach((o,s)=>{lt(s,o)}),n.forEach((o,s)=>{Xt.forEach(a=>a(s,o))});for(let o of r)t.some(s=>s.contains(o))||Zt.forEach(s=>s(o));for(let o of t)o.isConnected&&Qt.forEach(s=>s(o));t=null,r=null,n=null,i=null}function Ce(e){return z(B(e))}function k(e,t,r){return e._x_dataStack=[t,...B(r||e)],()=>{e._x_dataStack=e._x_dataStack.filter(n=>n!==t)}}function B(e){return e._x_dataStack?e._x_dataStack:typeof ShadowRoot=="function"&&e instanceof ShadowRoot?B(e.host):e.parentNode?B(e.parentNode):[]}function z(e){return new Proxy({objects:e},Dn)}var Dn={ownKeys({objects:e}){return Array.from(new Set(e.flatMap(t=>Object.keys(t))))},has({objects:e},t){return t==Symbol.unscopables?!1:e.some(r=>Object.prototype.hasOwnProperty.call(r,t)||Reflect.has(r,t))},get({objects:e},t,r){return t=="toJSON"?Pn:Reflect.get(e.find(n=>Reflect.has(n,t))||{},t,r)},set({objects:e},t,r,n){let i=e.find(s=>Object.prototype.hasOwnProperty.call(s,t))||e[e.length-1],o=Object.getOwnPropertyDescriptor(i,t);return o?.set&&o?.get?o.set.call(n,r)||!0:Reflect.set(i,t,r)}};function Pn(){return Reflect.ownKeys(this).reduce((t,r)=>(t[r]=Reflect.get(this,r),t),{})}function Te(e){let t=n=>typeof n=="object"&&!Array.isArray(n)&&n!==null,r=(n,i="")=>{Object.entries(Object.getOwnPropertyDescriptors(n)).forEach(([o,{value:s,enumerable:a}])=>{if(a===!1||s===void 0||typeof s=="object"&&s!==null&&s.__v_skip)return;let c=i===""?o:`${i}.${o}`;typeof s=="object"&&s!==null&&s._x_interceptor?n[o]=s.initialize(e,c,o):t(s)&&s!==n&&!(s instanceof Element)&&r(s,c)})};return r(e)}function Re(e,t=()=>{}){let r={initialValue:void 0,_x_interceptor:!0,initialize(n,i,o){return e(this.initialValue,()=>In(n,i),s=>ht(n,i,s),i,o)}};return t(r),n=>{if(typeof n=="object"&&n!==null&&n._x_interceptor){let i=r.initialize.bind(r);r.initialize=(o,s,a)=>{let c=n.initialize(o,s,a);return r.initialValue=c,i(o,s,a)}}else r.initialValue=n;return r}}function In(e,t){return t.split(".").reduce((r,n)=>r[n],e)}function ht(e,t,r){if(typeof t=="string"&&(t=t.split(".")),t.length===1)e[t[0]]=r;else{if(t.length===0)throw error;return e[t[0]]||(e[t[0]]={}),ht(e[t[0]],t.slice(1),r)}}var ir={};function y(e,t){ir[e]=t}function fe(e,t){let r=Ln(t);return Object.entries(ir).forEach(([n,i])=>{Object.defineProperty(e,`$${n}`,{get(){return i(t,r)},enumerable:!1})}),e}function Ln(e){let[t,r]=_t(e),n={interceptor:Re,...t};return te(e,r),n}function or(e,t,r,...n){try{return r(...n)}catch(i){re(i,e,t)}}function re(e,t,r=void 0){e=Object.assign(e??{message:"No error message given."},{el:t,expression:r}),console.warn(`Alpine Expression Error: ${e.message} + +${r?'Expression: "'+r+`" + +`:""}`,t),setTimeout(()=>{throw e},0)}var Me=!0;function ke(e){let t=Me;Me=!1;let r=e();return Me=t,r}function R(e,t,r={}){let n;return x(e,t)(i=>n=i,r),n}function x(...e){return sr(...e)}var sr=xt;function ar(e){sr=e}function xt(e,t){let r={};fe(r,e);let n=[r,...B(e)],i=typeof t=="function"?$n(n,t):Fn(n,t,e);return or.bind(null,e,t,i)}function $n(e,t){return(r=()=>{},{scope:n={},params:i=[]}={})=>{let o=t.apply(z([n,...e]),i);Ne(r,o)}}var gt={};function jn(e,t){if(gt[e])return gt[e];let r=Object.getPrototypeOf(async function(){}).constructor,n=/^[\n\s]*if.*\(.*\)/.test(e.trim())||/^(let|const)\s/.test(e.trim())?`(async()=>{ ${e} })()`:e,o=(()=>{try{let s=new r(["__self","scope"],`with (scope) { __self.result = ${n} }; __self.finished = true; return __self.result;`);return Object.defineProperty(s,"name",{value:`[Alpine] ${e}`}),s}catch(s){return re(s,t,e),Promise.resolve()}})();return gt[e]=o,o}function Fn(e,t,r){let n=jn(t,r);return(i=()=>{},{scope:o={},params:s=[]}={})=>{n.result=void 0,n.finished=!1;let a=z([o,...e]);if(typeof n=="function"){let c=n(n,a).catch(l=>re(l,r,t));n.finished?(Ne(i,n.result,a,s,r),n.result=void 0):c.then(l=>{Ne(i,l,a,s,r)}).catch(l=>re(l,r,t)).finally(()=>n.result=void 0)}}}function Ne(e,t,r,n,i){if(Me&&typeof t=="function"){let o=t.apply(r,n);o instanceof Promise?o.then(s=>Ne(e,s,r,n)).catch(s=>re(s,i,t)):e(o)}else typeof t=="object"&&t instanceof Promise?t.then(o=>e(o)):e(t)}var wt="x-";function C(e=""){return wt+e}function cr(e){wt=e}var De={};function d(e,t){return De[e]=t,{before(r){if(!De[r]){console.warn(String.raw`Cannot find directive \`${r}\`. \`${e}\` will use the default order of execution`);return}let n=G.indexOf(r);G.splice(n>=0?n:G.indexOf("DEFAULT"),0,e)}}}function lr(e){return Object.keys(De).includes(e)}function pe(e,t,r){if(t=Array.from(t),e._x_virtualDirectives){let o=Object.entries(e._x_virtualDirectives).map(([a,c])=>({name:a,value:c})),s=Et(o);o=o.map(a=>s.find(c=>c.name===a.name)?{name:`x-bind:${a.name}`,value:`"${a.value}"`}:a),t=t.concat(o)}let n={};return t.map(dr((o,s)=>n[o]=s)).filter(mr).map(zn(n,r)).sort(Kn).map(o=>Bn(e,o))}function Et(e){return Array.from(e).map(dr()).filter(t=>!mr(t))}var yt=!1,de=new Map,ur=Symbol();function fr(e){yt=!0;let t=Symbol();ur=t,de.set(t,[]);let r=()=>{for(;de.get(t).length;)de.get(t).shift()();de.delete(t)},n=()=>{yt=!1,r()};e(r),n()}function _t(e){let t=[],r=a=>t.push(a),[n,i]=Yt(e);return t.push(i),[{Alpine:K,effect:n,cleanup:r,evaluateLater:x.bind(x,e),evaluate:R.bind(R,e)},()=>t.forEach(a=>a())]}function Bn(e,t){let r=()=>{},n=De[t.type]||r,[i,o]=_t(e);Oe(e,t.original,o);let s=()=>{e._x_ignore||e._x_ignoreSelf||(n.inline&&n.inline(e,t,i),n=n.bind(n,e,t,i),yt?de.get(ur).push(n):n())};return s.runCleanups=o,s}var Pe=(e,t)=>({name:r,value:n})=>(r.startsWith(e)&&(r=r.replace(e,t)),{name:r,value:n}),Ie=e=>e;function dr(e=()=>{}){return({name:t,value:r})=>{let{name:n,value:i}=pr.reduce((o,s)=>s(o),{name:t,value:r});return n!==t&&e(n,t),{name:n,value:i}}}var pr=[];function ne(e){pr.push(e)}function mr({name:e}){return hr().test(e)}var hr=()=>new RegExp(`^${wt}([^:^.]+)\\b`);function zn(e,t){return({name:r,value:n})=>{let i=r.match(hr()),o=r.match(/:([a-zA-Z0-9\-_:]+)/),s=r.match(/\.[^.\]]+(?=[^\]]*$)/g)||[],a=t||e[r]||r;return{type:i?i[1]:null,value:o?o[1]:null,modifiers:s.map(c=>c.replace(".","")),expression:n,original:a}}}var bt="DEFAULT",G=["ignore","ref","data","id","anchor","bind","init","for","model","modelable","transition","show","if",bt,"teleport"];function Kn(e,t){let r=G.indexOf(e.type)===-1?bt:e.type,n=G.indexOf(t.type)===-1?bt:t.type;return G.indexOf(r)-G.indexOf(n)}function J(e,t,r={}){e.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:!0,composed:!0,cancelable:!0}))}function D(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>D(i,t));return}let r=!1;if(t(e,()=>r=!0),r)return;let n=e.firstElementChild;for(;n;)D(n,t,!1),n=n.nextElementSibling}function E(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var _r=!1;function gr(){_r&&E("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),_r=!0,document.body||E("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` + @@ -38,7 +39,7 @@ templ NavBar(title string) {
- Logo +
diff --git a/server/web/base_templ.go b/server/web/base_templ.go index b4ef1cd..7cf15b7 100644 --- a/server/web/base_templ.go +++ b/server/web/base_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.865 package web //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -46,7 +46,7 @@ func Base(title string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -106,7 +106,7 @@ func Base(title string) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Source

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\">Source

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -135,7 +135,7 @@ func NavBar(title string) templ.Component { templ_7745c5c3_Var7 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/server/web/fileView.go b/server/web/fileView.go new file mode 100644 index 0000000..73213f7 --- /dev/null +++ b/server/web/fileView.go @@ -0,0 +1,22 @@ +package web + +import ( + "log/slog" + "net/http" + + "git.jmbit.de/jmb/scanfile/server/internal/database" +) + +func FileViewWebHandler(w http.ResponseWriter, r *http.Request) { + file, err := database.FileByUUID(r.PathValue("uuid")) + if err != nil { + slog.Error("Error getting File in FileViewWebHandler", "error", err, "id", r.PathValue("uuid")) + http.Error(w, err.Error(), http.StatusBadRequest) + } + component := FileView(file) + err = component.Render(r.Context(), w) + if err != nil { + slog.Error("Error rendering in FileViewWebHandler", "error", err) + http.Error(w, err.Error(), http.StatusBadRequest) + } +} diff --git a/server/web/fileView.templ b/server/web/fileView.templ new file mode 100644 index 0000000..c70dfa2 --- /dev/null +++ b/server/web/fileView.templ @@ -0,0 +1,10 @@ +package web + +import "git.jmbit.de/jmb/scanfile/server/internal/database" + +templ FileView(file database.File) { + @Base(file.Name) { +

{file.Name}

+ + } +} diff --git a/server/web/fileView_templ.go b/server/web/fileView_templ.go new file mode 100644 index 0000000..efb85d8 --- /dev/null +++ b/server/web/fileView_templ.go @@ -0,0 +1,73 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.865 +package web + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "git.jmbit.de/jmb/scanfile/server/internal/database" + +func FileView(file database.File) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(file.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `server/web/fileView.templ`, Line: 7, Col: 34} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Base(file.Name).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/server/web/index.go b/server/web/index.go index c5fe424..1879d91 100644 --- a/server/web/index.go +++ b/server/web/index.go @@ -1,9 +1,11 @@ package web import ( + "fmt" "log/slog" "net/http" + "git.jmbit.de/jmb/scanfile/server/internal/database" "github.com/spf13/viper" ) @@ -18,18 +20,24 @@ func IndexWebHandler(w http.ResponseWriter, r *http.Request) { // IndexUploadHandler handles the upload of a new file func IndexUploadHandler(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { + if r.Method != http.MethodPost { http.Error(w, "Only POST allowed", http.StatusBadRequest) - return - } + return + } maxUploadSize := viper.GetInt64("web.maxfilesizemb") * 1024 * 1024 r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize) defer r.Body.Close() - file, fileHeader, err := r.FormFile("file") + fileData, fileHeader, err := r.FormFile("file") if err != nil { slog.Error("Error parsing form in IndexUploadHandler", "error", err) http.Error(w, err.Error(), http.StatusBadRequest) } + file, err := database.CreateFile(fileHeader.Filename, fileData, fileHeader.Size) + if err != nil { + slog.Error("Error saving file in IndexUploadHandler", "error", err) + http.Error(w, err.Error(), http.StatusBadRequest) + } + http.Redirect(w, r, fmt.Sprintf("/files/%s", file.Uuid), http.StatusSeeOther) } diff --git a/server/web/index.templ b/server/web/index.templ index 493cdee..2b3d84d 100644 --- a/server/web/index.templ +++ b/server/web/index.templ @@ -7,13 +7,12 @@ templ Index() {
-
- } } diff --git a/server/web/index_templ.go b/server/web/index_templ.go index 84f7399..fd819ab 100644 --- a/server/web/index_templ.go +++ b/server/web/index_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.857 +// templ: version: v0.3.865 package web //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -41,7 +41,7 @@ func Index() templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 0e06a41..85b94dd 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 2exit status 2exit status 2exit status 2exit status 2 \ No newline at end of file +exit status 2exit status 2exit status 2exit status 2exit status 2exit status 2exit status 1exit status 1exit status 1 \ No newline at end of file