summaryrefslogtreecommitdiff
path: root/pages
diff options
context:
space:
mode:
authorEwy~ <ewy0@protonmail.com>2026-03-31 17:18:35 +0200
committerEwy~ <ewy0@protonmail.com>2026-03-31 17:18:35 +0200
commitcf085e93b7abc53b8ecc78c07210384e0ff8d516 (patch)
tree270bc83c7e4d1c468bd7fcc61d2d65d6f447eda6 /pages
initial commit
Diffstat (limited to 'pages')
-rw-r--r--pages/create.go85
-rw-r--r--pages/create.gohtml22
-rw-r--r--pages/get.go53
-rw-r--r--pages/landing.go19
-rw-r--r--pages/landing.gohtml40
-rw-r--r--pages/receive.go5
6 files changed, 224 insertions, 0 deletions
diff --git a/pages/create.go b/pages/create.go
new file mode 100644
index 0000000..03f4159
--- /dev/null
+++ b/pages/create.go
@@ -0,0 +1,85 @@
+package pages
+
+import (
+ "delayed.link/storage"
+ _ "embed"
+ "html/template"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "time"
+)
+
+func CreateLink(w http.ResponseWriter, r *http.Request) *storage.Link {
+ err := r.ParseForm()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return nil
+ }
+ form := r.PostForm
+
+ minutesInFutureStr := form.Get("minutes_in_future")
+ if strings.TrimSpace(minutesInFutureStr) == "" {
+ http.Error(w, "minutes_in_future is required", http.StatusBadRequest)
+ return nil
+ }
+ minutesInFuture, err := strconv.Atoi(minutesInFutureStr)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return nil
+ }
+ if minutesInFuture < 0 {
+ http.Error(w, "minutes_in_future has to be > 0", http.StatusBadRequest)
+ }
+ numberOfOpensStr := form.Get("number_of_opens")
+ if strings.TrimSpace(minutesInFutureStr) == "" {
+ http.Error(w, "number_of_opens is required", http.StatusBadRequest)
+ return nil
+ }
+ numberOfOpens, err := strconv.Atoi(numberOfOpensStr)
+ if numberOfOpens < 0 || numberOfOpens > 100 {
+ http.Error(w, "number_of_opens needs to be between 1 and 100", http.StatusBadRequest)
+ return nil
+ }
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return nil
+ }
+ payload := form.Get("payload")
+ //TODO: Verify payload
+ if _, err := url.Parse(payload); err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return nil
+ }
+
+ l := storage.Link{
+ Target: payload,
+ OpensFrom: time.Now().Add(time.Duration(minutesInFuture) * time.Minute),
+ OpensLeft: numberOfOpens,
+ }
+
+ id, err := l.Save(storage.Current)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return nil
+ }
+ (&l).Id = id
+
+ return &l
+}
+
+//go:embed create.gohtml
+var recContent string
+var recTmpl = template.Must(template.New("delayed.link").Parse(recContent))
+
+func Create(w http.ResponseWriter, r *http.Request) {
+ l := CreateLink(w, r)
+ if l == nil {
+ return
+ }
+ err := recTmpl.Execute(w, l)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
diff --git a/pages/create.gohtml b/pages/create.gohtml
new file mode 100644
index 0000000..ac9890c
--- /dev/null
+++ b/pages/create.gohtml
@@ -0,0 +1,22 @@
+<!doctype html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
+ <meta http-equiv="X-UA-Compatible" content="ie=edge">
+ <link rel="icon" href="data:," />
+ <title>delayed.link: created</title>
+</head>
+<body>
+<p>
+ <a href="/l/{{.Id}}">This link will unlock at {{.OpensFrom}}</a>
+</p>
+<p>
+ It will be able to be opened {{ .OpensLeft }} times.
+</p>
+<p>
+ This page is completely ephemeral. Once you close it, you cannot access the link again.
+</p>
+</body>
+</html> \ No newline at end of file
diff --git a/pages/get.go b/pages/get.go
new file mode 100644
index 0000000..7f7df2f
--- /dev/null
+++ b/pages/get.go
@@ -0,0 +1,53 @@
+package pages
+
+import (
+ "delayed.link/storage"
+ _ "embed"
+ "errors"
+ "fmt"
+ "github.com/google/uuid"
+ "net/http"
+ "time"
+)
+
+func Get(w http.ResponseWriter, r *http.Request) {
+ id := r.PathValue("id")
+ uid, err := uuid.Parse(id)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ l, err := storage.Current.Load(uid)
+ if err != nil && errors.Is(err, storage.NotFoundError) {
+ // if not found error, return 404
+ http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ return
+ } else if err != nil {
+ // else return internal error
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ valid := l.Valid()
+ if errors.Is(valid, storage.NotFoundError) {
+ http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ return
+ }
+ if errors.Is(valid, storage.NoOpensError) {
+ http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
+ return
+ }
+ if errors.Is(valid, storage.NotYetOpenError) {
+ http.Error(w, http.StatusText(http.StatusLocked), http.StatusLocked)
+ msg := fmt.Sprintf("\n\nThis link will be usable in %v minutes.", int(l.OpensFrom.Sub(time.Now()).Minutes()))
+ w.Write([]byte(msg))
+ return
+ }
+
+ err = l.Use()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+
+ http.Redirect(w, r, l.Target, http.StatusFound)
+}
diff --git a/pages/landing.go b/pages/landing.go
new file mode 100644
index 0000000..d8c4f54
--- /dev/null
+++ b/pages/landing.go
@@ -0,0 +1,19 @@
+package pages
+
+import (
+ _ "embed"
+ "html/template"
+ "net/http"
+)
+
+//go:embed landing.gohtml
+var landContent string
+var landTmpl = template.Must(template.New("delayed.link").Parse(landContent))
+
+func Land(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ err := landTmpl.Execute(w, nil)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
diff --git a/pages/landing.gohtml b/pages/landing.gohtml
new file mode 100644
index 0000000..ceb55d9
--- /dev/null
+++ b/pages/landing.gohtml
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8">
+ <link rel="icon" href="data:," />
+ <title>delayed.link</title>
+ <style>
+ main {
+ min-height: 105vh;
+ }
+ </style>
+</head>
+<body>
+<main>
+ <form method="POST" action="/">
+ <label>
+ Lock time in minutes
+ <input type="number" name="minutes_in_future"/>
+ </label>
+ <label>
+ Maximum times the link can be opened
+ <input type="number" name="number_of_opens" min="1" max="100"/>
+ </label>
+ <label>
+ URL to save for later
+ <input type="text" name="payload"/>
+ </label>
+ <input type="submit" value="Create"/>
+ </form>
+</main>
+<footer>
+ THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN
+ WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
+ EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH THE CUSTOMER. SHOULD
+ THE SOFTWARE PROVE DEFECTIVE, THE CUSTOMER ASSUMES THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION EXCEPT
+ TO THE EXTENT SET OUT UNDER THE HARDWARE WARRANTY IN THESE TERMS.
+</footer>
+</body>
+</html> \ No newline at end of file
diff --git a/pages/receive.go b/pages/receive.go
new file mode 100644
index 0000000..023a20b
--- /dev/null
+++ b/pages/receive.go
@@ -0,0 +1,5 @@
+package pages
+
+import (
+ _ "embed"
+)