upload: basic folder upload support

This commit is contained in:
Lukas Werner
2026-08-11 14:11:47 -07:00
parent 9644f101b3
commit c8ec356c3e
6 changed files with 80 additions and 32 deletions
+2 -2
View File
@@ -4,9 +4,9 @@ go 1.25.0
require ( require (
github.com/Oudwins/tailwind-merge-go v0.2.1 // indirect github.com/Oudwins/tailwind-merge-go v0.2.1 // indirect
github.com/a-h/templ v0.3.943 // indirect github.com/a-h/templ v0.3.1020 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/valkey-io/valkey-go v1.0.64 // indirect github.com/valkey-io/valkey-go v1.0.64 // indirect
golang.org/x/sys v0.34.0 // indirect golang.org/x/sys v0.41.0 // indirect
) )
+4
View File
@@ -2,6 +2,8 @@ github.com/Oudwins/tailwind-merge-go v0.2.1 h1:jxRaEqGtwwwF48UuFIQ8g8XT7YSualNuG
github.com/Oudwins/tailwind-merge-go v0.2.1/go.mod h1:kkZodgOPvZQ8f7SIrlWkG/w1g9JTbtnptnePIh3V72U= github.com/Oudwins/tailwind-merge-go v0.2.1/go.mod h1:kkZodgOPvZQ8f7SIrlWkG/w1g9JTbtnptnePIh3V72U=
github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY= github.com/a-h/templ v0.3.943 h1:o+mT/4yqhZ33F3ootBiHwaY4HM5EVaOJfIshvd5UNTY=
github.com/a-h/templ v0.3.943/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/a-h/templ v0.3.943/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo=
github.com/a-h/templ v0.3.1020 h1:ypAT/L5ySWEnZ6Zft/5yfoWXYYkhFNvEFOeeqecg4tw=
github.com/a-h/templ v0.3.1020/go.mod h1:A2DlK61v+K+NRoGnhmYbNYVmtYHcFO5/AisMvBdDxTM=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@@ -10,3 +12,5 @@ github.com/valkey-io/valkey-go v1.0.64 h1:3u4+b6D6zs9JQs254TLy4LqitCMHHr9XorP9GG
github.com/valkey-io/valkey-go v1.0.64/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY= github.com/valkey-io/valkey-go v1.0.64/go.mod h1:bHmwjIEOrGq/ubOJfh5uMRs7Xj6mV3mQ/ZXUbmqpjqY=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+1 -1
View File
@@ -8,7 +8,7 @@ ENV GOARCH=amd64
RUN apk add git RUN apk add git
WORKDIR /src/ WORKDIR /src/
RUN git clone https://git.hafen.run/lukas/oauth-guard.git RUN git clone https://git.pdx.land/lukas/oauth-guard.git
WORKDIR /src/oauth-guard WORKDIR /src/oauth-guard
RUN go mod download -x RUN go mod download -x
ENV CGO_ENABLED=0 ENV CGO_ENABLED=0
+3
View File
@@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log"
"net/http" "net/http"
"time" "time"
@@ -18,6 +19,7 @@ func UploadFiles(client valkey.Client) func(w http.ResponseWriter, r *http.Reque
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
err := r.ParseMultipartForm(125_000_000) // 1G max memory usage err := r.ParseMultipartForm(125_000_000) // 1G max memory usage
if err != nil { if err != nil {
log.Println("error in parsing form: ", err.Error())
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
@@ -26,6 +28,7 @@ func UploadFiles(client valkey.Client) func(w http.ResponseWriter, r *http.Reque
expiry_values := form.Value["expiry"] expiry_values := form.Value["expiry"]
expiry, err := time.ParseDuration(expiry_values[0]) expiry, err := time.ParseDuration(expiry_values[0])
if err != nil { if err != nil {
log.Println("error in getting expiry: ", err.Error())
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
+2 -2
View File
@@ -5,8 +5,8 @@ templ:
# Run air for Go hot reload # Run air for Go hot reload
server: server:
air \ air \
--build.cmd "go build -o tmp/bin/main ./main.go" \ --build.cmd "go build -o tmp/main ." \
--build.bin "tmp/bin/main" \ --build.bin "tmp/main" \
--build.delay "100" \ --build.delay "100" \
--build.exclude_dir "node_modules" \ --build.exclude_dir "node_modules" \
--build.include_ext "go" \ --build.include_ext "go" \
+61 -20
View File
@@ -37,6 +37,7 @@ templ Upload(expirations []Expiry, uploadedLink string) {
let drop_zone = document.getElementById("drop_zone"); let drop_zone = document.getElementById("drop_zone");
let desc = document.getElementById("desc"); let desc = document.getElementById("desc");
let fileInput = document.getElementById("files"); let fileInput = document.getElementById("files");
function updateDescription(len) { function updateDescription(len) {
desc.innerText = `${len} File` desc.innerText = `${len} File`
if (len > 1) { if (len > 1) {
@@ -44,32 +45,72 @@ templ Upload(expirations []Expiry, uploadedLink string) {
} }
desc.innerText += " Attached" desc.innerText += " Attached"
} }
fileInput.addEventListener("change", () => { fileInput.addEventListener("change", () => {
updateDescription(fileInput.files.length) updateDescription(fileInput.files.length);
}); });
drop_zone.addEventListener("click", function (ev) {
fileInput.click(); drop_zone.addEventListener("click", () => fileInput.click());
})
drop_zone.addEventListener("drop", function (ev) { drop_zone.addEventListener("dragover", (e) => {
e.preventDefault();
drop_zone.classList.add('dragover');
});
drop_zone.addEventListener("dragleave", (e) => {
e.preventDefault();
drop_zone.classList.remove('dragover');
});
drop_zone.addEventListener("drop", async (ev) => {
ev.preventDefault(); ev.preventDefault();
drop_zone.classList.remove('dragover'); drop_zone.classList.remove('dragover');
const dt = ev.dataTransfer;
if (!dt) return
const files = Array.from(dt.files); // iterable const items = ev.dataTransfer?.items;
if (!items) return;
const newDT = new DataTransfer(); const collected = [];
files.forEach(f => newDT.items.add(f));
fileInput.files = newDT.files; const readEntries = (reader) => new Promise((resolve, reject) => reader.readEntries(resolve, reject));
updateDescription(fileInput.files.length) const getFile = (entry) => new Promise((resolve, reject) => entry.file(resolve, reject));
});
drop_zone.addEventListener("dragover", function (e) { async function traverse(entry, path = "") {
drop_zone.classList.add('dragover'); if (entry.isFile) {
e.preventDefault(); const file = await getFile(entry);
}); Object.defineProperty(file, "webkitRelativePath", {
drop_zone.addEventListener("dragleave", function (e) { value: path + file.name,
drop_zone.classList.remove('dragover'); configurable: true });
e.preventDefault(); collected.push(file);
} else if (entry.isDirectory) {
const reader = entry.createReader();
const prefix = path + entry.name + "/";
let entries;
do {
entries = await readEntries(reader);
for (const child of entries) {
await traverse(child, prefix);
}
} while (entries.length > 0);
}
}
const tasks = [];
for (let i = 0; i < items.length; i++) {
const entry = items[i].webkitGetAsEntry();
if (entry) {
tasks.push(traverse(entry));
} else {
const file = items[i].getAsFile();
if (file) collected.push(file);
}
}
await Promise.all(tasks);
const dt = new DataTransfer();
collected.forEach(f => dt.items.add(f));
fileInput.files = dt.files;
updateDescription(fileInput.files.length);
}); });
</script> </script>
<style>.dragover {border-color: #00C950;}</style> <style>.dragover {border-color: #00C950;}</style>