commit 840cd1b471f038e6aa9c13ae976eff9ec695fc3d Author: Andreas Mieke Date: Sat Jun 9 17:22:25 2018 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0269c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,85 @@ +# Ignore folders in /vendor/ but allow vendor.json +/vendor/*/ +!/vendor/vendor.json + +# Created by https://www.gitignore.io/api/go,linux,macos,windows + +### Go ### +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +# End of https://www.gitignore.io/api/go,linux,macos,windows diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..d84a02e --- /dev/null +++ b/config.yml @@ -0,0 +1,7 @@ +DBType: "postgres" +DBConnection: "host=localhost user=shortdragon dbname=shortdragon sslmode=disable" +UseSocket: false +BindAddress: ":8080" +BindSocket: "/var/run/shortdragon.sock" +DefaultURL: "https://1750studios.com" +ShortURL: "https://1750.st" diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..fb56364 --- /dev/null +++ b/database/database.go @@ -0,0 +1,41 @@ +package database + +import ( + "database/sql" + "log" + "os" + "time" + + "github.com/jinzhu/gorm" +) + +// Postgres database driver +import _ "github.com/jinzhu/gorm/dialects/postgres" + +// Db is the open Database +var Db *gorm.DB + +// URL defines the structure of a shortened URL +type URL struct { + ID uint `gorm:"primary_key:auto_increment"` + CreatedAt time.Time + UpdatedAt time.Time + + Short sql.NullString `gorm:"unique_index:short_url;not null"` + Long sql.NullString `gorm:"unique;not null"` + Hits sql.NullInt64 +} + +// InitDb opens a database connection and runs the auto migration +func InitDb(DBType, DBConnection string) { + var err error + Db, err = gorm.Open(DBType, DBConnection) + if err != nil { + log.Fatal("Could not open database:", err) + } + if os.Getenv("GIN_MODE") != "release" { + Db.LogMode(true) + } + + Db.AutoMigrate(&URL{}) +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..0bd3a70 --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +package main + +import ( + "log" + + "git.1750studios.com/ToddShepard/ShortDragon/database" + "git.1750studios.com/ToddShepard/ShortDragon/routes" + "github.com/gin-gonic/gin" + "github.com/spf13/viper" +) + +func main() { + // Configuration + viper.SetDefault("DBType", "postgres") + viper.SetDefault("DBConnection", "host=localhost user=shortdragon dbname=shortdragon sslmode=disable") + viper.SetDefault("UseSocket", false) + viper.SetDefault("BindAddress", ":8080") + viper.SetDefault("BindSocket", "/var/run/shortdragon.sock") + viper.SetDefault("DefaultURL", "https://1750studios.com") + viper.SetDefault("ShortURL", "https://1750.st") + + viper.SetConfigName("config") + viper.AddConfigPath("/etc/shortdragon/") + viper.AddConfigPath("$HOME/.shortdragon/") + viper.AddConfigPath(".") + + err := viper.ReadInConfig() + if err != nil { + log.Fatal("Could not read config:", err) + } + + database.InitDb(viper.GetString("DBType"), viper.GetString("DBConnection")) + + router := gin.Default() + router.POST("/e", routes.Encode) + router.GET("/r/:short", routes.Redirect) + router.GET("/d/:short", routes.Decode) + router.GET("/i/:short", routes.Info) + + if viper.GetBool("UseSocket") { + router.RunUnix(viper.GetString("BindSocket")) + } else { + router.Run(viper.GetString("BindAddress")) + } + +} diff --git a/routes/routes.go b/routes/routes.go new file mode 100644 index 0000000..65f88f2 --- /dev/null +++ b/routes/routes.go @@ -0,0 +1,109 @@ +package routes + +import ( + "crypto/sha512" + "encoding/base64" + "net/http" + + "git.1750studios.com/ToddShepard/ShortDragon/database" + "github.com/gin-gonic/gin" + "github.com/jinzhu/gorm" + "github.com/spf13/viper" +) + +// Redirect redirects a user to the long URL specified by the short URL +func Redirect(c *gin.Context) { + var URL database.URL + short := c.Param("short") + err := database.Db.First(&URL, "short = ?", short).Error + if err != nil { + c.Redirect(http.StatusFound, viper.GetString("DefaultURL")) + return + } + if !URL.Long.Valid { + c.Redirect(http.StatusFound, viper.GetString("DefaultURL")) + return + } + if URL.Hits.Valid && !(c.Request.Header.Get("DNT") == "1") { + URL.Hits.Int64 = URL.Hits.Int64 + 1 + database.Db.Save(&URL) + } + c.Redirect(http.StatusFound, URL.Long.String) + return +} + +// Encode encodes a long URL and returns the short one +func Encode(c *gin.Context) { + var URL database.URL + var count uint + URL.Long.String = c.PostForm("LongURL") + URL.Long.Valid = true + database.Db.Where("long = ?", URL.Long.String).FirstOrInit(&URL) + if URL.Short.Valid { + c.Data(http.StatusOK, "text/plain", []byte(viper.GetString("ShortURL")+"/"+URL.Short.String)) + return + } + URL.Short.String = c.DefaultPostForm("ShortURL", "") + if URL.Short.String == "" { + hasher := sha512.New() + hasher.Write([]byte(URL.Long.String)) + base := base64.StdEncoding.EncodeToString(hasher.Sum(nil)) + i := 1 + for { + database.Db.Model(&database.URL{}).Where("short = ?", base[0:i]).Count(&count) + if count > 0 && i < len(base) { + i = i + 1 + } else if count > 0 { + c.AbortWithStatus(http.StatusConflict) + return + } else { + URL.Short.String = base[0:i] + break + } + } + } + URL.Short.Valid = true + URL.Hits.Int64 = 0 + if c.DefaultPostForm("track", "true") == "true" { + URL.Hits.Valid = true + } else { + URL.Hits.Valid = false + } + err := database.Db.Create(&URL).Error + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + c.Data(http.StatusCreated, "text/plain", []byte(viper.GetString("ShortURL")+"/"+URL.Short.String)) + return +} + +// Decode decodes a short URL and returns the long one +func Decode(c *gin.Context) { + var URL database.URL + short := c.Param("short") + err := database.Db.Where("short = ?", short).Find(&URL).Error + if err != nil && err == gorm.ErrRecordNotFound { + c.Data(http.StatusNotFound, "text/plain", []byte("Record not found")) + return + } else if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + c.Data(http.StatusOK, "text/plain", []byte(URL.Long.String)) +} + +// Info decodes a short URL and returns the database content +func Info(c *gin.Context) { + var URL database.URL + short := c.Param("short") + err := database.Db.Where("short = ?", short).Find(&URL).Error + if err != nil && err == gorm.ErrRecordNotFound { + c.Data(http.StatusNotFound, "text/plain", []byte("Record not found")) + return + } else if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + c.JSON(http.StatusOK, URL) +} diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 0000000..60835b1 --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,259 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "checksumSHA1": "7NP1qUMF8Kx1y0zANxx0e+oq9Oo=", + "path": "github.com/fsnotify/fsnotify", + "revision": "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9", + "revisionTime": "2018-01-10T05:33:47Z" + }, + { + "checksumSHA1": "QeKwBtN2df+j+4stw3bQJ6yO4EY=", + "path": "github.com/gin-contrib/sse", + "revision": "22d885f9ecc78bf4ee5d72b937e4bbcdc58e8cae", + "revisionTime": "2017-01-09T09:34:21Z" + }, + { + "checksumSHA1": "J9Wy/bT7FckPvRzC0uSW6Tpg6Yc=", + "path": "github.com/gin-gonic/gin", + "revision": "caf3e350a548af1add9def68087ac53d1d000caa", + "revisionTime": "2018-05-31T06:13:40Z" + }, + { + "checksumSHA1": "O2rrqYI7QQjJYVx8+E8DC8rlIh0=", + "path": "github.com/gin-gonic/gin/binding", + "revision": "caf3e350a548af1add9def68087ac53d1d000caa", + "revisionTime": "2018-05-31T06:13:40Z" + }, + { + "checksumSHA1": "woO1qIxIeQ1bcbPSiMfAKk3r4xg=", + "path": "github.com/gin-gonic/gin/json", + "revision": "caf3e350a548af1add9def68087ac53d1d000caa", + "revisionTime": "2018-05-31T06:13:40Z" + }, + { + "checksumSHA1": "DGs8sdsCkNHsU9+fQMM9XHb+ZCE=", + "path": "github.com/gin-gonic/gin/render", + "revision": "caf3e350a548af1add9def68087ac53d1d000caa", + "revisionTime": "2018-05-31T06:13:40Z" + }, + { + "checksumSHA1": "JZV+pLo8Z/kIYExrZr1Zu9KSKyU=", + "path": "github.com/golang/protobuf/proto", + "revision": "05f48f4eaf0e05663b562bab533cdd472238ce29", + "revisionTime": "2018-06-06T20:26:30Z" + }, + { + "checksumSHA1": "HtpYAWHvd9mq+mHkpo7z8PGzMik=", + "path": "github.com/hashicorp/hcl", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", + "path": "github.com/hashicorp/hcl/hcl/ast", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "1GmX7G0Pgf5XprOh+T3zXMXX0dc=", + "path": "github.com/hashicorp/hcl/hcl/parser", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "encY+ZtDf4nJaMvsVL2c+EJ2r3Q=", + "path": "github.com/hashicorp/hcl/hcl/printer", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "+qJTCxhkwC7r+VZlPlZz8S74KmU=", + "path": "github.com/hashicorp/hcl/hcl/scanner", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "oS3SCN9Wd6D8/LG0Yx1fu84a7gI=", + "path": "github.com/hashicorp/hcl/hcl/strconv", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", + "path": "github.com/hashicorp/hcl/hcl/token", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "PwlfXt7mFS8UYzWxOK5DOq0yxS0=", + "path": "github.com/hashicorp/hcl/json/parser", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "afrZ8VmAwfTdDAYVgNSXbxa4GsA=", + "path": "github.com/hashicorp/hcl/json/scanner", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", + "path": "github.com/hashicorp/hcl/json/token", + "revision": "ef8a98b0bbce4a65b5aa4c368430a80ddc533168", + "revisionTime": "2018-04-04T17:41:02Z" + }, + { + "checksumSHA1": "nlEiwqdbwY1eclAGG1xcC+XPV9M=", + "path": "github.com/jinzhu/gorm", + "revision": "82eb9f8a5bbb5e6b929d2f0ae5b934e6a253f94e", + "revisionTime": "2018-05-12T06:29:00Z" + }, + { + "checksumSHA1": "Sf/CFFl00e8nN8DDKaT2dKLUaFI=", + "path": "github.com/jinzhu/gorm/dialects/postgres", + "revision": "82eb9f8a5bbb5e6b929d2f0ae5b934e6a253f94e", + "revisionTime": "2018-05-12T06:29:00Z" + }, + { + "checksumSHA1": "4eelJOSLZoDzLuaJvgilOBVNovU=", + "path": "github.com/jinzhu/inflection", + "revision": "04140366298a54a039076d798123ffa108fff46c", + "revisionTime": "2018-03-08T03:36:59Z" + }, + { + "checksumSHA1": "gydhX7ik17tDsfT5t97UDRseQ7g=", + "path": "github.com/json-iterator/go", + "revision": "8744d7c5c7b40a53e018f78d8c508b3315260b96", + "revisionTime": "2018-05-26T01:43:29Z" + }, + { + "checksumSHA1": "s6eyIXpJ7VAU06FM27GcEBM/5lo=", + "path": "github.com/lib/pq", + "revision": "90697d60dd844d5ef6ff15135d0203f65d2f53b8", + "revisionTime": "2018-05-23T17:54:26Z" + }, + { + "checksumSHA1": "EnMmNGgpDa5sNw0Zs5RhvNDcP2g=", + "path": "github.com/lib/pq/hstore", + "revision": "90697d60dd844d5ef6ff15135d0203f65d2f53b8", + "revisionTime": "2018-05-23T17:54:26Z" + }, + { + "checksumSHA1": "AU3fA8Sm33Vj9PBoRPSeYfxLRuE=", + "path": "github.com/lib/pq/oid", + "revision": "90697d60dd844d5ef6ff15135d0203f65d2f53b8", + "revisionTime": "2018-05-23T17:54:26Z" + }, + { + "checksumSHA1": "AYZGozgchqe4WcKH/jCZI1flnP8=", + "path": "github.com/magiconair/properties", + "revision": "c2353362d570a7bfa228149c62842019201cfb71", + "revisionTime": "2018-05-15T20:40:05Z" + }, + { + "checksumSHA1": "w5RcOnfv5YDr3j2bd1YydkPiZx4=", + "path": "github.com/mattn/go-isatty", + "revision": "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c", + "revisionTime": "2017-11-07T05:05:31Z" + }, + { + "checksumSHA1": "ewGq4nGalpCQOHcmBTdAEQx1wW0=", + "path": "github.com/mitchellh/mapstructure", + "revision": "bb74f1db0675b241733089d5a1faa5dd8b0ef57b", + "revisionTime": "2018-05-11T14:21:26Z" + }, + { + "checksumSHA1": "ZTcgWKWHsrX0RXYVXn5Xeb8Q0go=", + "path": "github.com/modern-go/concurrent", + "revision": "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94", + "revisionTime": "2018-03-06T01:26:44Z" + }, + { + "checksumSHA1": "ntSr6NgBxGzQG1fgkl5IFgdjCLo=", + "path": "github.com/modern-go/reflect2", + "revision": "58118c1ea9161250907268a484af4dd6ed314280", + "revisionTime": "2018-05-11T05:30:14Z" + }, + { + "checksumSHA1": "hlXUEyUn6u1ZhZbunDqTTEz1gyw=", + "path": "github.com/pelletier/go-toml", + "revision": "c01d1270ff3e442a8a57cddc1c92dc1138598194", + "revisionTime": "2018-03-06T02:43:58Z" + }, + { + "checksumSHA1": "wsfbX7YxbkeBp+xm9M9nBFzrCnA=", + "path": "github.com/spf13/afero", + "revision": "787d034dfe70e44075ccc060d346146ef53270ad", + "revisionTime": "2018-05-31T09:43:57Z" + }, + { + "checksumSHA1": "HcOjO9+cwl+xYnkiGuIeesqwBs8=", + "path": "github.com/spf13/afero/mem", + "revision": "787d034dfe70e44075ccc060d346146ef53270ad", + "revisionTime": "2018-05-31T09:43:57Z" + }, + { + "checksumSHA1": "Hc2i9OOK34PAImuNftTaHdbdLgs=", + "path": "github.com/spf13/cast", + "revision": "8965335b8c7107321228e3e3702cab9832751bac", + "revisionTime": "2018-02-14T17:35:30Z" + }, + { + "checksumSHA1": "+JFKK0z5Eutk29rUz1lEhLxHMfk=", + "path": "github.com/spf13/jwalterweatherman", + "revision": "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394", + "revisionTime": "2018-01-09T13:55:06Z" + }, + { + "checksumSHA1": "OJI0OgC5V8gZtfS1e0CDYMhkDNc=", + "path": "github.com/spf13/pflag", + "revision": "3ebe029320b2676d667ae88da602a5f854788a8a", + "revisionTime": "2018-06-01T13:25:42Z" + }, + { + "checksumSHA1": "Kd8R7CLqI5pceYpTircWmxZzbvE=", + "path": "github.com/spf13/viper", + "revision": "15738813a09db5c8e5b60a19d67d3f9bd38da3a4", + "revisionTime": "2018-05-06T01:38:06Z" + }, + { + "checksumSHA1": "kdas0zQNtQqfUMEMFGkUF1OcgPc=", + "path": "github.com/ugorji/go/codec", + "revision": "f3cacc17c85ecb7f1b6a9e373ee85d1480919868", + "revisionTime": "2018-04-07T10:30:00Z" + }, + { + "checksumSHA1": "VUb+SqeoloAw1A2Jz9IzkDNs06c=", + "path": "golang.org/x/sys/unix", + "revision": "9527bec2660bd847c050fda93a0f0c6dee0800bb", + "revisionTime": "2018-06-06T19:45:53Z" + }, + { + "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", + "path": "golang.org/x/text/transform", + "revision": "5c1cf69b5978e5a34c5f9ba09a83e56acc4b7877", + "revisionTime": "2018-05-10T04:57:31Z" + }, + { + "checksumSHA1": "BCNYmf4Ek93G4lk5x3ucNi/lTwA=", + "path": "golang.org/x/text/unicode/norm", + "revision": "5c1cf69b5978e5a34c5f9ba09a83e56acc4b7877", + "revisionTime": "2018-05-10T04:57:31Z" + }, + { + "checksumSHA1": "P/k5ZGf0lEBgpKgkwy++F7K1PSg=", + "path": "gopkg.in/go-playground/validator.v8", + "revision": "5f1438d3fca68893a817e4a66806cea46a9e4ebf", + "revisionTime": "2017-07-30T05:02:35Z" + }, + { + "checksumSHA1": "ZSWoOPUNRr5+3dhkLK3C4cZAQPk=", + "path": "gopkg.in/yaml.v2", + "revision": "5420a8b6744d3b0345ab293f6fcba19c978f1183", + "revisionTime": "2018-03-28T19:50:20Z" + } + ], + "rootPath": "git.1750studios.com/ToddShepard/ShortDragon" +}