diff options
Diffstat (limited to 'pages')
| -rw-r--r-- | pages/create.go | 85 | ||||
| -rw-r--r-- | pages/create.gohtml | 22 | ||||
| -rw-r--r-- | pages/get.go | 53 | ||||
| -rw-r--r-- | pages/landing.go | 19 | ||||
| -rw-r--r-- | pages/landing.gohtml | 40 | ||||
| -rw-r--r-- | pages/receive.go | 5 |
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" +) |
