mirror of
https://github.com/jesperh1/forkort.dk.git
synced 2025-05-16 17:28:09 +01:00
232 lines
5.7 KiB
Go
232 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"database/sql"
|
|
"fmt"
|
|
"html/template"
|
|
"io"
|
|
"log"
|
|
"math/big"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
|
|
"github.com/BurntSushi/toml"
|
|
_ "github.com/go-sql-driver/mysql"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
var tmpl = template.Must(template.ParseFiles("./sites/index.html"))
|
|
var statsTmpl = template.Must(template.ParseFiles("./sites/stats.html"))
|
|
var successTmpl = template.Must(template.ParseFiles("./sites/success.html"))
|
|
|
|
type SuccessShortend struct {
|
|
LongLink string
|
|
ShortLink string
|
|
Success bool
|
|
Error bool
|
|
}
|
|
|
|
// GenerateRandomString returns a securely generated random string.
|
|
// It will return an error if the system's secure random
|
|
// number generator fails to function correctly, in which
|
|
// case the caller should not continue.
|
|
func GenerateRandomString(n int) (string, error) {
|
|
const letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-"
|
|
ret := make([]byte, n)
|
|
for i := 0; i < n; i++ {
|
|
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
ret[i] = letters[num.Int64()]
|
|
}
|
|
|
|
return string(ret), nil
|
|
}
|
|
|
|
func HandleStats(w http.ResponseWriter, r *http.Request) {
|
|
var TotalSaved string
|
|
query := `SELECT * FROM TotalSaved`
|
|
err := db.QueryRow(query).Scan(&TotalSaved)
|
|
if err != nil {
|
|
statsTmpl.Execute(w, TotalSaved)
|
|
return
|
|
}
|
|
statsTmpl.Execute(w, TotalSaved)
|
|
}
|
|
|
|
func AboutPage(w http.ResponseWriter, r *http.Request) {
|
|
http.ServeFile(w, r, "./sites/about.html")
|
|
}
|
|
func UnshortenHandler(w http.ResponseWriter, r *http.Request) {
|
|
token := mux.Vars(r)["token"]
|
|
var unshortenedLink string
|
|
query := `SELECT oldLink FROM redirects where newLink = BINARY ?`
|
|
err := db.QueryRow(query, token).Scan(&unshortenedLink)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
fmt.Fprintf(w, "404 not found")
|
|
return
|
|
}
|
|
http.Redirect(w, r, unshortenedLink, http.StatusSeeOther)
|
|
}
|
|
|
|
func IndexHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
tmpl.Execute(w, nil)
|
|
return
|
|
}
|
|
|
|
link := r.FormValue("userLink")
|
|
println(link)
|
|
u, err := url.ParseRequestURI(string(link))
|
|
if err != nil {
|
|
tmpl.Execute(w, struct{ Error bool }{Error: true})
|
|
return
|
|
}
|
|
|
|
// If no errors Occured
|
|
s := SuccessShortend{
|
|
LongLink: u.String(),
|
|
ShortLink: "",
|
|
Success: false,
|
|
Error: false,
|
|
}
|
|
|
|
var token string
|
|
query := `SELECT newLink FROM redirects where oldLink = ?`
|
|
err = db.QueryRow(query, string(link)).Scan(&token)
|
|
if err == nil {
|
|
s.ShortLink = "forkort.dk/" + token
|
|
successTmpl.Execute(w, s)
|
|
return
|
|
}
|
|
|
|
CREATE_TOKEN:
|
|
token, err = GenerateRandomString(4)
|
|
if err != nil {
|
|
log.Println(err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprintf(w, "Internal Server Error")
|
|
return
|
|
}
|
|
query = `SELECT newLink FROM redirects where newLink = BINARY ?`
|
|
err = db.QueryRow(query, token).Scan(&token)
|
|
if err == nil {
|
|
goto CREATE_TOKEN
|
|
}
|
|
s.ShortLink = "forkort.dk/" + token
|
|
_, err = db.Exec(`INSERT INTO redirects(oldLink, newLink) VALUES (?, ?)`, string(link), token)
|
|
if err != nil {
|
|
log.Println(err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
tmpl.Execute(w, struct{ Error bool }{Error: true})
|
|
return
|
|
}
|
|
successTmpl.Execute(w, s)
|
|
}
|
|
|
|
func UnshortenApi(w http.ResponseWriter, r *http.Request) {
|
|
token := mux.Vars(r)["token"]
|
|
var unshortenedLink string
|
|
query := `SELECT oldLink FROM redirects where newLink = BINARY ?`
|
|
err := db.QueryRow(query, token).Scan(&unshortenedLink)
|
|
if err != nil {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
fmt.Fprintf(w, "404 not found")
|
|
return
|
|
}
|
|
w.Write([]byte(unshortenedLink))
|
|
}
|
|
|
|
func ShortenApi(w http.ResponseWriter, r *http.Request) {
|
|
link, err := io.ReadAll(r.Body)
|
|
defer r.Body.Close()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), 500)
|
|
return
|
|
}
|
|
u, err := url.ParseRequestURI(string(link))
|
|
if err != nil {
|
|
log.Println(err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
fmt.Fprintf(w, "Invalid URL")
|
|
return
|
|
}
|
|
log.Println(u)
|
|
log.Println(string(link))
|
|
var token string
|
|
query := `SELECT newLink FROM redirects where oldLink = ?`
|
|
err = db.QueryRow(query, string(link)).Scan(&token)
|
|
if err == nil {
|
|
fmt.Fprintf(w, "forkort.dk/"+token)
|
|
return
|
|
}
|
|
|
|
CREATE_TOKEN:
|
|
token, err = GenerateRandomString(4)
|
|
if err != nil {
|
|
log.Println(err)
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
fmt.Fprintf(w, "Internal Server Error")
|
|
}
|
|
query = `SELECT newLink FROM redirects where newLink = BINARY ?`
|
|
err = db.QueryRow(query, token).Scan(&token)
|
|
if err == nil {
|
|
goto CREATE_TOKEN
|
|
}
|
|
_, err = db.Exec(`INSERT INTO redirects(oldLink, newLink) VALUES (?, ?)`, string(link), token)
|
|
if err != nil {
|
|
log.Println(err)
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
fmt.Fprintf(w, "Bad request")
|
|
return
|
|
}
|
|
fmt.Fprintf(w, "forkort.dk/"+token)
|
|
}
|
|
|
|
var db *sql.DB
|
|
|
|
type Config struct {
|
|
MYSQL_DB string
|
|
MYSQL_USER string
|
|
MYSQL_PASS string
|
|
MYSQL_HOST string
|
|
}
|
|
|
|
func main() {
|
|
var cfg Config
|
|
file, err := os.ReadFile("./config.toml")
|
|
if err != nil {
|
|
log.Fatal("you need a config.toml file")
|
|
}
|
|
err = toml.Unmarshal(file, &cfg)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
db, err = sql.Open("mysql", cfg.MYSQL_USER+":@("+cfg.MYSQL_HOST+"/"+cfg.MYSQL_DB+"?parseTime=true")
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
err = db.Ping()
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer db.Close()
|
|
log.Println("Database connection established")
|
|
|
|
router := mux.NewRouter()
|
|
router.HandleFunc("/", IndexHandler)
|
|
router.HandleFunc("/stats", HandleStats)
|
|
router.HandleFunc("/about", AboutPage)
|
|
router.HandleFunc("/api/shorten", ShortenApi)
|
|
router.HandleFunc("/api/unshorten/{token}", UnshortenApi)
|
|
router.HandleFunc(`/{token}`, UnshortenHandler)
|
|
router.PathPrefix("/static/").Handler(http.StripPrefix("/static", http.FileServer(http.Dir("./assets"))))
|
|
|
|
http.ListenAndServe(":8080", router)
|
|
}
|