From 368bdcc95f34fddff86e6c4befc6df4eebb51985 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Thu, 14 May 2020 12:20:05 +0200 Subject: [PATCH] Add option to save text --- database.go | 32 ++++++++++++ main.go | 145 ++++++++++++++++++++++++++++++++++++++------------- templates.go | 71 +++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 36 deletions(-) create mode 100644 database.go create mode 100644 templates.go 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}}

{{range .Fields}}

{{end}}
") +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 URLs

Short URLs

{{range .}}{{end}}
slugurlhits
{{.Slug}}{{.Url}}{{.Hits}}
") - 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

" + + "" + + "" + + "{{range .}}" + + "" + + "{{end}}" + + "
slugurlhits
{{.Slug}}{{.Url}}{{.Hits}}
" + + "") + return +} + +func initUrlFormTemplate() (err error) { + urlFormTemplate, err = template.New("UrlForm").Parse( + "" + + "" + + "" + + "{{.Title}}" + + "

{{.Title}}

" + + "
" + + "{{range .Fields}}" + + "

" + + "{{end}}" + + "" + + "
" + + "") + return +} + +func initTextFormTemplate() (err error) { + textFormTemplate, err = template.New("TextForm").Parse( + "" + + "" + + "" + + "{{.Title}}" + + "

{{.Title}}

" + + "
" + + "{{range .Fields}}" + + "

" + + "{{end}}" + + "{{range .TextAreas}}" + + "

" + + "{{end}}" + + "" + + "
" + + "") + return +}