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) }