add csv reading, routing

This commit is contained in:
2026-03-30 21:42:52 +01:00
parent 153dc64f60
commit a98ef28055
5 changed files with 141 additions and 22 deletions
+7
View File
@@ -0,0 +1,7 @@
package internal
type CLI struct {
File string `arg:"" name:"file" short:"f" help:"Path to CSV redirects file." type:"path"`
Host string `name:"host" help:"HTTP listen host." default:"${host}"`
Port string `name:"port" short:"p" help:"HTTP listen port." default:"${port}"`
}
+72
View File
@@ -0,0 +1,72 @@
package internal
import (
"encoding/csv"
"errors"
"io"
"os"
"strconv"
)
type Headings []string
func (h Headings) IndexOf(heading string) int {
for i, value := range h {
if value == heading {
return i
}
}
return -1
}
func ReadCsvRedirects(file string) (Redirects, error) {
reader, err := os.Open(file)
if err != nil {
return nil, err
}
csvReader := csv.NewReader(reader)
// Process headings
textHeadings, err := csvReader.Read()
if err != nil {
return nil, err
}
headings := Headings(textHeadings)
fromCol := headings.IndexOf("From")
toCol := headings.IndexOf("To")
statusCol := headings.IndexOf("Status Code")
if fromCol == -1 || toCol == -1 || statusCol == -1 {
return nil, errors.New("CSV must contain From, To, and Status Code headings")
}
redirects := Redirects{}
for {
row, err := csvReader.Read()
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
if row == nil {
break
}
statusCode, err := strconv.Atoi(row[statusCol])
if err != nil {
return nil, err
}
redirect := &Redirect{
From: row[fromCol],
To: row[toCol],
StatusCode: statusCode,
}
redirects = append(redirects, redirect)
}
return redirects, nil
}
+26
View File
@@ -0,0 +1,26 @@
package internal
import (
"net/http"
"github.com/rs/zerolog"
)
type HttpServer struct {
CLI *CLI
Log zerolog.Logger
Redirects Redirects
}
func (srv *HttpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
redirect := srv.Redirects.Find(req.URL.Path)
if redirect != nil {
srv.Log.Info().Int("result", redirect.StatusCode).Msg(req.URL.Path)
w.WriteHeader(redirect.StatusCode)
w.Header().Add("location", redirect.To)
} else {
statusCode := 404
srv.Log.Error().Int("result", statusCode).Msg(req.URL.Path)
w.WriteHeader(statusCode)
}
}
+24
View File
@@ -0,0 +1,24 @@
package internal
type Redirect struct {
From string
To string
StatusCode int
}
func (r *Redirect) Match(from string) bool {
return from == r.From
}
type Redirects []*Redirect
func (rs Redirects) Find(from string) *Redirect {
for i := 0; i < len(rs); i++ {
redirect := rs[i]
if redirect.Match(from) {
return redirect
}
}
return nil
}
+12 -22
View File
@@ -6,37 +6,27 @@ import (
"github.com/alecthomas/kong" "github.com/alecthomas/kong"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"gogs.home.aneur.in/go/ultrashorty/internal"
) )
type CLI struct {
File string `arg:"" name:"file" short:"f" help:"Path to CSV redirects file." type:"path"`
Host string `name:"host" help:"HTTP listen host." default:"${host}"`
Port string `name:"port" short:"p" help:"HTTP listen port." default:"${port}"`
}
type HttpServer struct {
CLI *CLI
Log zerolog.Logger
}
func (srv *HttpServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
statusCode := 404
srv.Log.Trace().Int("result", statusCode).Msg(req.URL.Path)
w.WriteHeader(statusCode)
}
func main() { func main() {
vars := kong.Vars{ vars := kong.Vars{
"host": "localhost", "host": "localhost",
"port": "4000", "port": "4000",
} }
cli := &CLI{} cli := &internal.CLI{}
kong.Parse(cli, vars) kong.Parse(cli, vars)
srv := &HttpServer{ redirects, err := internal.ReadCsvRedirects(cli.File)
CLI: cli, if err != nil {
Log: zerolog.New(zerolog.NewConsoleWriter()).With().Timestamp().Logger(), panic(err)
}
srv := &internal.HttpServer{
CLI: cli,
Log: zerolog.New(zerolog.NewConsoleWriter()).With().Timestamp().Logger(),
Redirects: redirects,
} }
addr := net.JoinHostPort(cli.Host, cli.Port) addr := net.JoinHostPort(cli.Host, cli.Port)
@@ -46,6 +36,6 @@ func main() {
}() }()
srv.Log.Info().Msgf("Listening at %s", addr) srv.Log.Info().Msgf("Listening at %s", addr)
err := <-errc err = <-errc
panic(err) panic(err)
} }