All checks were successful
Build and Push Docker Image / build (push) Successful in 5m55s
84 lines
2.0 KiB
Go
84 lines
2.0 KiB
Go
package service
|
|
|
|
import (
|
|
"database/sql"
|
|
"log/slog"
|
|
"net/http"
|
|
"slices"
|
|
"strings"
|
|
|
|
"git.kocoder.xyz/vt/shortener/internal/config"
|
|
"git.kocoder.xyz/vt/shortener/internal/database"
|
|
)
|
|
|
|
type Server struct {
|
|
conf *config.Config
|
|
db *database.Queries
|
|
}
|
|
|
|
func NewServer(conf *config.Config, db *database.Queries) *Server {
|
|
return &Server{
|
|
conf: conf,
|
|
db: db,
|
|
}
|
|
}
|
|
|
|
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
ctx := req.Context()
|
|
path := req.URL.Path
|
|
|
|
path = strings.Trim(path, "/")
|
|
|
|
slog.InfoContext(ctx, "processing request", slog.String("path", path))
|
|
|
|
res, err := s.db.GetURLByShortCode(ctx, path)
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "failed to get url by short code", slog.String("path", path), slog.String("err", err.Error()))
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
forwardedFor := req.Header.Get("X-Forwarded-For")
|
|
remoteAddr := req.RemoteAddr
|
|
|
|
if slices.Contains(s.conf.TRUSTED_PROXIES, strings.Split(remoteAddr, ":")[0]) {
|
|
if strings.Contains(forwardedFor, ", ") {
|
|
remoteAddr = strings.Split(forwardedFor, ", ")[0]
|
|
} else {
|
|
remoteAddr = forwardedFor
|
|
}
|
|
}
|
|
|
|
slog.InfoContext(ctx, "tracking click",
|
|
slog.String("ip", remoteAddr),
|
|
slog.String("user_agent", req.UserAgent()),
|
|
slog.String("referrer", req.Referer()),
|
|
)
|
|
|
|
_, err = s.db.TrackClick(ctx, database.TrackClickParams{
|
|
UrlID: res.UrlID,
|
|
UserAgent: sql.NullString{
|
|
String: req.UserAgent(),
|
|
Valid: true,
|
|
},
|
|
Referrer: sql.NullString{
|
|
String: req.Referer(),
|
|
Valid: true,
|
|
},
|
|
IpAddress: sql.NullString{
|
|
String: remoteAddr,
|
|
Valid: true,
|
|
},
|
|
})
|
|
|
|
if err != nil {
|
|
slog.ErrorContext(ctx, "failed to track click", slog.String("err", err.Error()))
|
|
// Still continuing to redirect even if tracking fails, or we can return.
|
|
// Usually we still want to redirect the user so they reach their destination.
|
|
}
|
|
|
|
slog.InfoContext(ctx, "redirecting to destination", slog.String("long_url", res.LongUrl))
|
|
|
|
http.Redirect(w, req, res.LongUrl, http.StatusFound)
|
|
}
|