upload: basic folder upload support
This commit is contained in:
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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" \
|
||||||
|
|||||||
+68
-27
@@ -35,8 +35,9 @@ templ Upload(expirations []Expiry, uploadedLink string) {
|
|||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
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,33 +45,73 @@ templ Upload(expirations []Expiry, uploadedLink string) {
|
|||||||
}
|
}
|
||||||
desc.innerText += " Attached"
|
desc.innerText += " Attached"
|
||||||
}
|
}
|
||||||
fileInput.addEventListener("change", () => {
|
|
||||||
updateDescription(fileInput.files.length)
|
|
||||||
});
|
|
||||||
drop_zone.addEventListener("click", function (ev) {
|
|
||||||
fileInput.click();
|
|
||||||
})
|
|
||||||
drop_zone.addEventListener("drop", function (ev) {
|
|
||||||
ev.preventDefault();
|
|
||||||
drop_zone.classList.remove('dragover');
|
|
||||||
const dt = ev.dataTransfer;
|
|
||||||
if (!dt) return
|
|
||||||
|
|
||||||
const files = Array.from(dt.files); // iterable
|
fileInput.addEventListener("change", () => {
|
||||||
|
updateDescription(fileInput.files.length);
|
||||||
|
});
|
||||||
|
|
||||||
const newDT = new DataTransfer();
|
drop_zone.addEventListener("click", () => fileInput.click());
|
||||||
files.forEach(f => newDT.items.add(f));
|
|
||||||
fileInput.files = newDT.files;
|
drop_zone.addEventListener("dragover", (e) => {
|
||||||
updateDescription(fileInput.files.length)
|
e.preventDefault();
|
||||||
});
|
drop_zone.classList.add('dragover');
|
||||||
drop_zone.addEventListener("dragover", function (e) {
|
});
|
||||||
drop_zone.classList.add('dragover');
|
|
||||||
e.preventDefault();
|
drop_zone.addEventListener("dragleave", (e) => {
|
||||||
});
|
e.preventDefault();
|
||||||
drop_zone.addEventListener("dragleave", function (e) {
|
drop_zone.classList.remove('dragover');
|
||||||
drop_zone.classList.remove('dragover');
|
});
|
||||||
e.preventDefault();
|
|
||||||
});
|
drop_zone.addEventListener("drop", async (ev) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
drop_zone.classList.remove('dragover');
|
||||||
|
|
||||||
|
const items = ev.dataTransfer?.items;
|
||||||
|
if (!items) return;
|
||||||
|
|
||||||
|
const collected = [];
|
||||||
|
|
||||||
|
const readEntries = (reader) => new Promise((resolve, reject) => reader.readEntries(resolve, reject));
|
||||||
|
const getFile = (entry) => new Promise((resolve, reject) => entry.file(resolve, reject));
|
||||||
|
|
||||||
|
async function traverse(entry, path = "") {
|
||||||
|
if (entry.isFile) {
|
||||||
|
const file = await getFile(entry);
|
||||||
|
Object.defineProperty(file, "webkitRelativePath", {
|
||||||
|
value: path + file.name,
|
||||||
|
configurable: true });
|
||||||
|
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>
|
||||||
<div class="flex flex-col gap-3 w-full">
|
<div class="flex flex-col gap-3 w-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user