Detailed changes
@@ -37,8 +37,6 @@ func (m *model) createHandler(writer http.ResponseWriter, request *http.Request)
return
}
- log.Println("Saving \"" + url + "\" mapped to \"" + name + "\"")
-
response := m.create(name, url)
writer.Write([]byte(response))
@@ -2,6 +2,9 @@ package main
import (
"fmt"
+ "log"
+ "strings"
+ "text/template"
"github.com/dgraph-io/badger/v3"
)
@@ -17,9 +20,19 @@ func (m model) create(name string, url string) string {
return fmt.Sprint(err)
}
+ log.Println("\"" + url + "\" mapped to \"" + name + "\"")
return fmt.Sprint("URL mapped to ", name, "\n")
}
+// Update modifies a shortened link
+func (m model) update(name string, url string, oldName string) string {
+ m.create(name, url)
+ if name != oldName {
+ m.delete(oldName)
+ }
+ return fmt.Sprint("\"" + url + "\" mapped to \"" + name + "\"")
+}
+
// Delete removes a shortened URL from the database given its name.
func (m model) delete(name string) string {
err := m.database.Update(func(txn *badger.Txn) error {
@@ -29,7 +42,8 @@ func (m model) delete(name string) string {
return fmt.Sprint(err)
}
- return fmt.Sprint("\"", name, "\" has been deleted")
+ log.Println("\"" + name + "\" has been deleted")
+ return fmt.Sprint("\"" + name + "\" has been deleted\n")
}
// nameExists returns true if the provided name is already stored in the
@@ -47,3 +61,42 @@ func (m model) nameExists(name string) bool {
}
return false
}
+
+// unauthenticated serves the unauthenticated home page to the visitor
+func unauthenticated() string {
+ home, err := templates.ReadFile("templates/home_unauthenticated.html")
+ if err != nil {
+ log.Fatalln(err)
+ }
+ return string(home)
+}
+
+// authenticated serves the authenticated dashboard to the user
+func (m model) authenticated() string {
+ dash, err := templates.ReadFile("templates/home_authenticated.html")
+ if err != nil {
+ log.Fatalln(err)
+ }
+ tmpl, err := template.New("authenticated").Parse(string(dash))
+ page := new(strings.Builder)
+ tmpl.Execute(page, m.genTable())
+ return page.String()
+}
+
+type EditFields struct {
+ URL string
+ Name string
+}
+
+// update serves the editing interface to the user
+func (m model) edit(name string, url string) string {
+ fields := EditFields{url, name}
+ editPage, err := templates.ReadFile("templates/edit.html")
+ if err != nil {
+ log.Fatalln(err)
+ }
+ tmpl, err := template.New("editPage").Parse(string(editPage))
+ page := new(strings.Builder)
+ tmpl.Execute(page, fields)
+ return page.String()
+}
@@ -12,9 +12,8 @@ import (
func (m model) readHandler(writer http.ResponseWriter, request *http.Request) {
token := request.Header.Get("Authorization")
token = strings.TrimPrefix(token, "Bearer ")
- cookie, _ := request.Cookie("access_token")
- if token != m.AccessToken && cookie.Value != m.AccessToken {
+ if token != m.AccessToken {
http.Error(writer, "401 Unauthorized: You do not have permission to view shortlinks", 403)
return
}
@@ -5,9 +5,10 @@ import (
"io"
"log"
"net/http"
+ "net/url"
"strings"
- "text/template"
+ "github.com/dchest/uniuri"
"github.com/dgraph-io/badger/v3"
)
@@ -34,22 +35,92 @@ func (m model) root(writer http.ResponseWriter, request *http.Request) {
}
cookie, err := request.Cookie("access_token")
- if err != nil {
- home, err := templates.ReadFile("templates/home_unauthenticated.html")
- if err != nil {
- log.Fatalln(err)
+ if err != nil || cookie.Value != m.AccessToken {
+ io.WriteString(writer, unauthenticated())
+ return
+ }
+
+ query := request.URL.Query()
+
+ action := query.Get("action")
+ if len(action) == 0 {
+ io.WriteString(writer, m.authenticated())
+ }
+
+ destination := query.Get("url")
+ name := query.Get("name")
+ oldName := query.Get("oldName")
+
+ var message string
+
+ if action == "create" {
+ if len(destination) == 0 {
+ message = "URL field is required"
+ http.Redirect(writer, request, "/?message="+message, 302)
+ return
}
- io.WriteString(writer, string(home))
+
+ if len(name) == 0 {
+ name = uniuri.NewLen(4)
+ for m.nameExists(name) {
+ name = uniuri.NewLen(4)
+ log.Println("Generated new name:", name)
+ }
+ } else if m.nameExists(name) {
+ http.Error(writer, "406 Not Acceptable: A shortened URL with this name already exists", 406)
+ message = "A shortened URL with this name already exists"
+ http.Redirect(writer, request, "/?message="+message, 302)
+ return
+ }
+
+ message = url.QueryEscape(m.create(name, destination))
+ http.Redirect(writer, request, "/?message="+message, 302)
return
}
- if cookie.Value == m.AccessToken {
- dash, err := templates.ReadFile("templates/home_authenticated.html")
- if err != nil {
- log.Fatalln(err)
+ if action == "edit" {
+ io.WriteString(writer, m.edit(name, destination))
+ }
+
+ if action == "update" {
+ if len(destination) == 0 {
+ message = "URL field is required"
+ http.Redirect(writer, request, "/?message="+message, 302)
+ return
+ }
+
+ if len(name) == 0 {
+ message = "Name field is required"
+ http.Redirect(writer, request, "/?message="+message, 302)
+ return
+ }
+
+ if len(oldName) == 0 {
+ message = "oldName field is required"
+ http.Redirect(writer, request, "/?message="+message, 302)
+ return
+ }
+
+ if len(name) != 0 && oldName != name {
+ if m.nameExists(name) {
+ message = "A shortened URL with this name already exists"
+ http.Redirect(writer, request, "/?message="+message, 302)
+ return
+ }
+ }
+
+ message := url.QueryEscape(m.update(name, destination, oldName))
+ http.Redirect(writer, request, "/?message="+message, 302)
+ }
+
+ if action == "delete" {
+ if len(name) == 0 {
+ message = "Name field is required"
+ http.Redirect(writer, request, "/?message="+message, 302)
}
- tmpl, err := template.New("authenticated").Parse(string(dash))
- tmpl.Execute(writer, m.genTable())
+ message := url.QueryEscape(m.delete(name))
+ log.Println(message)
+ http.Redirect(writer, request, "/?message="+message, 302)
}
}
@@ -67,8 +138,8 @@ func (m model) genTable() string {
table = table + fmt.Sprintf(`<tr>
<td><p>%s</p></td>
<td><p>%s</p></td>
- <td><button>Edit</button><button>Delete</button></td>
-</tr>`, k, v)
+ <td><a href="/?action=edit&name=%s&url=%s">Edit</a><a href="/?action=delete&name=%s">Delete</a></td>
+</tr>`, k, v, k, url.QueryEscape(string(v)), k)
return nil
})
if err != nil {
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<html lang="en-GB">
+ <head>
+ <title>umu</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <meta name="title" content="umu">
+ <meta name="description" content="umu">
+ <style>
+ html {
+ background: #1C1C1C;
+ color: #DCDCDC;
+ font-family: sans-serif;
+ text-align: center;
+ height: 100%;
+ margin: 0 auto;
+ }
+ body {
+ min-height: 100%;
+ padding: 0;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ max-width: 450px;
+ }
+ .title {
+ font-size: 50px;
+ }
+ form {
+ margin: 0 auto;
+ width: min(300px, calc(70% + 100px));
+ text-align: left;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ }
+ form > * {
+ margin-bottom: 0.3em;
+ }
+ label {
+ flex: 0 0 auto;
+ }
+ input {
+ font-family: inherit;
+ }
+ .input {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ details {
+ margin: 20px 0;
+ }
+ .link_info {
+ text-align: left;
+ }
+ table {
+ text-align: center;
+ }
+ tr:nth-child(even) {
+ background-color: #2B2B2B;
+ } td > p {
+ font-family: monospace;
+ }
+ td {
+ padding: 10px;
+ }
+ </style>
+ </head>
+ <body>
+ <p class="title">edit</p>
+ <form>
+ <div class="input">
+ <label for="url">URL:</label>
+ <input type="text" id="url" name="url" value="{{.URL}}">
+ </div>
+ <div class="input">
+ <label for="name">Name:</label>
+ <input type="text" id="name" name="name" value="{{.Name}}">
+ </div>
+ <input type="text" id="oldName" name="oldName" value="{{.Name}}" hidden="">
+ <input type="text" id="action" name="action" value="update" hidden="">
+ <input type="submit" formaction="/" value="Update shortened URL">
+ </form>
+ </body>
+</html>
@@ -78,7 +78,8 @@
<label for="name">Name (optional):</label>
<input type="text" id="name" name="name">
</div>
- <input type="submit" formaction="/create" value="Shorten URL">
+ <input type="text" id="action" name="action" value="create" hidden="">
+ <input type="submit" formaction="/?action=create" value="Shorten URL">
</form>
<details>
<summary>Shortened URLs</summary>
@@ -2,11 +2,8 @@ package main
import (
"fmt"
- "log"
"net/http"
"strings"
-
- "github.com/dgraph-io/badger/v3"
)
func (m *model) updateHandler(writer http.ResponseWriter, request *http.Request) {
@@ -20,53 +17,33 @@ func (m *model) updateHandler(writer http.ResponseWriter, request *http.Request)
return
}
+ oldName := query.Get("oldName")
name := query.Get("name")
- new_name := query.Get("new_name")
- new_url := query.Get("new_url")
+ destination := query.Get("url")
+
+ if len(oldName) == 0 {
+ http.Error(writer, "400 Bad Request: oldName parameter is required", 400)
+ return
+ }
if len(name) == 0 {
http.Error(writer, "400 Bad Request: name parameter is required", 400)
return
}
- if len(new_name) == 0 && len(new_url) == 0 {
- http.Error(writer, "400 Bad Request: You may update either the entry's URL or name but not both", 400)
+ if len(destination) == 0 {
+ http.Error(writer, "400 Bad Request: url parameter is required", 400)
return
}
- if len(new_name) != 0 {
- if m.nameExists(new_name) {
+ if len(name) != 0 && oldName != name {
+ if m.nameExists(name) {
http.Error(writer, "406 Not Acceptable: A shortened URL with this name already exists", 406)
return
}
- name = new_name
}
- var url string
- err := m.database.View(func(txn *badger.Txn) error {
- value, err := txn.Get([]byte(name))
- if err != nil {
- return err
- }
- url = value.String()
- return nil
- })
- if err != nil {
- log.Println("Error", err)
- }
-
- if len(new_url) != 0 {
- url = new_url
- }
-
- log.Println("Mapping \"" + name + "\" to \"" + url + "\"")
-
- err = m.database.Update(func(txn *badger.Txn) error {
- return txn.Set([]byte(name), []byte(url))
- })
- if err != nil {
- log.Println(err)
- }
+ m.update(name, destination, oldName)
- http.Error(writer, fmt.Sprint("\"", name, "\" mapped to \"", url, "\""), 200)
+ http.Error(writer, fmt.Sprint("\"", name, "\" mapped to \"", destination, "\""), 200)
}