You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

163 lines
4.5 KiB

package handlers
import (
"encoding/base64"
"fmt"
"net/http"
"net/url"
"runtime"
"time"
"github.com/gin-gonic/gin"
"github.com/maxibanki/golang-url-shortener/handlers/auth"
"github.com/maxibanki/golang-url-shortener/store"
"github.com/maxibanki/golang-url-shortener/util"
)
// urlUtil is used to help in- and outgoing requests for json
// un- and marshalling
type urlUtil struct {
URL string `binding:"required"`
ID, DeletionURL string
Expiration *time.Time
}
// handleLookup is the http handler for getting the infos
func (h *Handler) handleLookup(c *gin.Context) {
var data struct {
ID string `binding:"required"`
}
if err := c.ShouldBind(&data); err != nil {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
entry, err := h.store.GetEntryByID(data.ID)
if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": err.Error()})
return
}
if !h.oAuthPropertiesEquals(c, entry.OAuthID, entry.OAuthProvider) {
c.JSON(http.StatusOK, store.Entry{
Public: store.EntryPublicData{
URL: entry.Public.URL,
},
})
return
}
c.JSON(http.StatusOK, entry.Public)
}
// handleAccess handles the access for incoming requests
func (h *Handler) handleAccess(c *gin.Context) {
id := c.Request.URL.Path[1:]
url, err := h.store.GetURLAndIncrease(id)
if err == store.ErrNoEntryFound {
return
} else if err != nil {
http.Error(c.Writer, fmt.Sprintf("could not get and crease visitor counter: %v, ", err), http.StatusInternalServerError)
return
}
go h.store.RegisterVisit(id, store.Visitor{
IP: c.ClientIP(),
Timestamp: time.Now(),
Referer: c.GetHeader("Referer"),
UTMSource: c.Query("utm_source"),
UTMMedium: c.Query("utm_medium"),
UTMCampaign: c.Query("utm_campaign"),
UTMContent: c.Query("utm_content"),
UTMTerm: c.Query("utm_term"),
})
c.Redirect(http.StatusTemporaryRedirect, url)
// There is a need to Abort in the current middleware to prevent
// that the status code will be overridden by the default NoRoute handler
c.Abort()
}
// handleCreate handles requests to create an entry
func (h *Handler) handleCreate(c *gin.Context) {
var data urlUtil
if err := c.ShouldBind(&data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
user := c.MustGet("user").(*auth.JWTClaims)
id, delID, err := h.store.CreateEntry(store.Entry{
Public: store.EntryPublicData{
URL: data.URL,
Expiration: data.Expiration,
},
RemoteAddr: c.ClientIP(),
OAuthProvider: user.OAuthProvider,
OAuthID: user.OAuthID,
}, data.ID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
originURL := h.getURLOrigin(c)
c.JSON(http.StatusOK, urlUtil{
URL: fmt.Sprintf("%s/%s", originURL, id),
DeletionURL: fmt.Sprintf("%s/d/%s/%s", originURL, id, url.QueryEscape(base64.RawURLEncoding.EncodeToString(delID))),
})
}
// handleGetVisitors handles requests to create an entry
func (h *Handler) handleGetVisitors(c *gin.Context) {
var data struct {
ID string `binding:"required"`
}
if err := c.ShouldBind(&data); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
dataSets, err := h.store.GetVisitors(data.ID)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, dataSets)
}
func (h *Handler) handleInfo(c *gin.Context) {
info := gin.H{
"providers": h.providers,
"go": runtime.Version(),
}
for k, v := range util.VersionInfo {
info[k] = v
}
c.JSON(http.StatusOK, info)
}
func (h *Handler) handleRecent(c *gin.Context) {
user := c.MustGet("user").(*auth.JWTClaims)
entries, err := h.store.GetUserEntries(user.OAuthProvider, user.OAuthID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, entries)
}
func (h *Handler) handleDelete(c *gin.Context) {
givenHmac, err := base64.RawURLEncoding.DecodeString(c.Param("hash"))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("could not decode base64: %v", err)})
return
}
if err := h.store.DeleteEntry(c.Param("id"), givenHmac); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
})
}
func (h *Handler) getURLOrigin(c *gin.Context) string {
protocol := "http"
if c.Request.TLS != nil {
protocol = "https"
}
return fmt.Sprintf("%s://%s", protocol, c.Request.Host)
}