diff --git a/database.go b/database.go
new file mode 100644
index 0000000..1ff579a
--- /dev/null
+++ b/database.go
@@ -0,0 +1,32 @@
+package main
+
+import (
+ migrate "github.com/rubenv/sql-migrate"
+ "log"
+)
+
+func migrateDatabase() {
+ migrations := &migrate.MemoryMigrationSource{
+ Migrations: []*migrate.Migration{
+ {
+ Id: "001",
+ Up: []string{"create table redirect(slug text not null primary key,url text not null,hits integer default 0 not null);insert into redirect (slug, url) values ('source', 'https://git.jlel.se/jlelse/GoShort');"},
+ Down: []string{"drop table redirect;"},
+ },
+ {
+ Id: "002",
+ Up: []string{"update redirect set url = 'https://git.jlel.se/jlelse/GoShort' where slug = 'source';"},
+ Down: []string{},
+ },
+ {
+ Id: "003",
+ Up: []string{"alter table redirect add column type text not null default 'url';"},
+ Down: []string{},
+ },
+ },
+ }
+ _, err := migrate.Exec(db, "sqlite3", migrations, migrate.Up)
+ if err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/main.go b/main.go
index 3ab627d..3fe1903 100644
--- a/main.go
+++ b/main.go
@@ -2,14 +2,14 @@ package main
import (
"database/sql"
+ "fmt"
"github.com/gorilla/mux"
_ "github.com/mattn/go-sqlite3"
- "github.com/rubenv/sql-migrate"
"github.com/spf13/viper"
- "html/template"
"log"
"math/rand"
"net/http"
+ "strconv"
"time"
)
@@ -19,6 +19,7 @@ func main() {
rand.Seed(time.Now().UTC().UnixNano())
viper.SetDefault("dbPath", "data/goshort.db")
+ viper.SetDefault("port", 8080)
viper.SetConfigName("config")
viper.AddConfigPath("./config")
@@ -54,7 +55,10 @@ func main() {
admin := r.NewRoute().Subrouter()
admin.HandleFunc("/s", ShortenFormHandler).Methods(http.MethodGet)
admin.HandleFunc("/s", ShortenHandler).Methods(http.MethodPost)
+ admin.HandleFunc("/t", ShortenTextFormHandler).Methods(http.MethodGet)
+ admin.HandleFunc("/t", ShortenTextHandler).Methods(http.MethodPost)
admin.HandleFunc("/u", UpdateFormHandler).Methods(http.MethodGet)
+ admin.HandleFunc("/ut", UpdateTextFormHandler).Methods(http.MethodGet)
admin.HandleFunc("/u", UpdateHandler).Methods(http.MethodPost)
admin.HandleFunc("/d", DeleteFormHandler).Methods(http.MethodGet)
admin.HandleFunc("/d", DeleteHandler).Methods(http.MethodPost)
@@ -63,8 +67,9 @@ func main() {
r.HandleFunc("/{slug}", ShortenedUrlHandler)
r.HandleFunc("/", CatchAllHandler)
- http.Handle("/", r)
- log.Fatal(http.ListenAndServe(":8080", nil))
+ addr := ":" + strconv.Itoa(viper.GetInt("port"))
+ fmt.Println("Listening to " + addr)
+ log.Fatal(http.ListenAndServe(addr, r))
}
func loginMiddleware(next http.Handler) http.Handler {
@@ -77,49 +82,43 @@ func loginMiddleware(next http.Handler) http.Handler {
})
}
-func migrateDatabase() {
- migrations := &migrate.MemoryMigrationSource{
- Migrations: []*migrate.Migration{
- {
- Id: "001",
- Up: []string{"create table redirect(slug text not null primary key,url text not null,hits integer default 0 not null);insert into redirect (slug, url) values ('source', 'https://git.jlel.se/jlelse/GoShort');"},
- Down: []string{"drop table redirect;"},
- },
- },
- }
- _, err := migrate.Exec(db, "sqlite3", migrations, migrate.Up)
- if err != nil {
- log.Fatal(err)
- }
-}
-
func ShortenFormHandler(w http.ResponseWriter, r *http.Request) {
- err := generateForm(w, "Shorten URL", "s", [][]string{{"url", r.FormValue("url")}, {"slug", r.FormValue("slug")}})
+ err := generateURLForm(w, "Shorten URL", "s", [][]string{{"url", r.FormValue("url")}, {"slug", r.FormValue("slug")}})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func UpdateFormHandler(w http.ResponseWriter, r *http.Request) {
- err := generateForm(w, "Update short link", "u", [][]string{{"slug", r.FormValue("slug")}, {"new", r.FormValue("new")}})
+ err := generateURLForm(w, "Update short link", "u", [][]string{{"slug", r.FormValue("slug")}, {"type", "url"}, {"new", r.FormValue("new")}})
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ }
+}
+
+func UpdateTextFormHandler(w http.ResponseWriter, r *http.Request) {
+ err := generateTextForm(w, "Update text", "u", [][]string{{"slug", r.FormValue("slug")}, {"type", "text"}}, [][]string{{"new", r.FormValue("new")}})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func DeleteFormHandler(w http.ResponseWriter, r *http.Request) {
- err := generateForm(w, "Delete short link", "d", [][]string{{"slug", r.FormValue("slug")}})
+ err := generateURLForm(w, "Delete short link", "d", [][]string{{"slug", r.FormValue("slug")}})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
-func generateForm(w http.ResponseWriter, title string, url string, fields [][]string) error {
- tmpl, err := template.New("Form").Parse("
{{.Title}}{{.Title}}
")
+func ShortenTextFormHandler(w http.ResponseWriter, r *http.Request) {
+ err := generateTextForm(w, "Save text", "t", [][]string{{"slug", r.FormValue("slug")}}, [][]string{{"text", r.FormValue("text")}})
if err != nil {
- return err
+ http.Error(w, err.Error(), http.StatusInternalServerError)
}
- err = tmpl.Execute(w, &struct {
+}
+
+func generateURLForm(w http.ResponseWriter, title string, url string, fields [][]string) error {
+ err := urlFormTemplate.Execute(w, &struct {
Title string
Url string
Fields [][]string
@@ -134,6 +133,24 @@ func generateForm(w http.ResponseWriter, title string, url string, fields [][]st
return nil
}
+func generateTextForm(w http.ResponseWriter, title string, url string, fields [][]string, textAreas [][]string) error {
+ err := textFormTemplate.Execute(w, &struct {
+ Title string
+ Url string
+ Fields [][]string
+ TextAreas [][]string
+ }{
+ Title: title,
+ Url: url,
+ Fields: fields,
+ TextAreas: textAreas,
+ })
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
func ShortenHandler(w http.ResponseWriter, r *http.Request) {
writeShortenedUrl := func(w http.ResponseWriter, slug string) {
_, _ = w.Write([]byte(viper.GetString("shortUrl") + "/" + slug))
@@ -185,6 +202,57 @@ func ShortenHandler(w http.ResponseWriter, r *http.Request) {
writeShortenedUrl(w, slug)
}
+func ShortenTextHandler(w http.ResponseWriter, r *http.Request) {
+ writeShortenedUrl := func(w http.ResponseWriter, slug string) {
+ _, _ = w.Write([]byte(viper.GetString("shortUrl") + "/" + slug))
+ }
+
+ requestText := r.FormValue("text")
+ if requestText == "" {
+ http.Error(w, "text parameter not set", http.StatusBadRequest)
+ return
+ }
+
+ slug := r.FormValue("slug")
+ manualSlug := false
+ if slug == "" {
+ _ = db.QueryRow("SELECT slug FROM redirect WHERE url = ? and type = 'text'", requestText).Scan(&slug)
+ } else {
+ manualSlug = true
+ }
+
+ if slug != "" {
+ if _, e := slugExists(slug); e {
+ if manualSlug {
+ http.Error(w, "slug already in use", http.StatusBadRequest)
+ return
+ }
+ writeShortenedUrl(w, slug)
+ return
+ }
+ } else {
+ var exists = true
+ for exists == true {
+ slug = generateSlug()
+ var err error
+ err, exists = slugExists(slug)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ }
+
+ _, err := db.Exec("INSERT INTO redirect (slug, url, type) VALUES (?, ?, 'text')", slug, requestText)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ w.WriteHeader(http.StatusCreated)
+ writeShortenedUrl(w, slug)
+}
+
func UpdateHandler(w http.ResponseWriter, r *http.Request) {
slug := r.FormValue("slug")
if slug == "" {
@@ -198,12 +266,17 @@ func UpdateHandler(w http.ResponseWriter, r *http.Request) {
return
}
+ typeString := r.FormValue("type")
+ if typeString == "" {
+ typeString = "url"
+ }
+
if err, e := slugExists(slug); !e || err != nil {
http.Error(w, "Slug not found", http.StatusNotFound)
return
}
- _, err := db.Exec("UPDATE redirect SET url = ? WHERE slug = ?", newUrl, slug)
+ _, err := db.Exec("UPDATE redirect SET url = ?, type = ? WHERE slug = ?", newUrl, typeString, slug)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -256,12 +329,7 @@ func ListHandler(w http.ResponseWriter, r *http.Request) {
}
list = append(list, r)
}
- tmpl, err := template.New("List").Parse("Short URLsShort URLs
slug | url | hits |
{{range .}}{{.Slug}} | {{.Url}} | {{.Hits}} |
{{end}}
")
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- err = tmpl.Execute(w, &list)
+ err = listTemplate.Execute(w, &list)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -307,7 +375,8 @@ func ShortenedUrlHandler(w http.ResponseWriter, r *http.Request) {
}
var redirectUrl string
- err := db.QueryRow("SELECT url FROM redirect WHERE slug = ?", slug).Scan(&redirectUrl)
+ var typeString string
+ err := db.QueryRow("SELECT url, type FROM redirect WHERE slug = ?", slug).Scan(&redirectUrl, &typeString)
if err != nil {
http.NotFound(w, r)
return
@@ -317,7 +386,11 @@ func ShortenedUrlHandler(w http.ResponseWriter, r *http.Request) {
_, _ = db.Exec("UPDATE redirect SET hits = hits + 1 WHERE slug = ?", slug)
}()
- http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect)
+ if typeString == "text" {
+ _, _ = w.Write([]byte(redirectUrl))
+ } else {
+ http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect)
+ }
}
func CatchAllHandler(w http.ResponseWriter, r *http.Request) {
diff --git a/templates.go b/templates.go
new file mode 100644
index 0000000..f9df3db
--- /dev/null
+++ b/templates.go
@@ -0,0 +1,71 @@
+package main
+
+import (
+ "html/template"
+ "log"
+)
+
+var listTemplate *template.Template
+var urlFormTemplate *template.Template
+var textFormTemplate *template.Template
+
+func init() {
+ if initListTemplate() != nil || initUrlFormTemplate() != nil || initTextFormTemplate() != nil {
+ log.Fatal("Failed to initialize templates")
+ return
+ }
+}
+
+func initListTemplate() (err error) {
+ listTemplate, err = template.New("List").Parse(
+ "" +
+ "" +
+ "" +
+ "Short URLs" +
+ "Short URLs
" +
+ "" +
+ "slug | url | hits |
" +
+ "{{range .}}" +
+ "{{.Slug}} | {{.Url}} | {{.Hits}} |
" +
+ "{{end}}" +
+ "
" +
+ "")
+ return
+}
+
+func initUrlFormTemplate() (err error) {
+ urlFormTemplate, err = template.New("UrlForm").Parse(
+ "" +
+ "" +
+ "" +
+ "{{.Title}}" +
+ "{{.Title}}
" +
+ "" +
+ "")
+ return
+}
+
+func initTextFormTemplate() (err error) {
+ textFormTemplate, err = template.New("TextForm").Parse(
+ "" +
+ "" +
+ "" +
+ "{{.Title}}" +
+ "{{.Title}}
" +
+ "" +
+ "")
+ return
+}