Skip to content

Commit

Permalink
add s3 cache layer for seatmaps
Browse files Browse the repository at this point in the history
  • Loading branch information
its-felix committed Nov 1, 2024
1 parent 34d965c commit 79cd263
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 12 deletions.
1 change: 1 addition & 0 deletions cdk/lib/constructs/api-lambda-construct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class ApiLambdaConstruct extends Construct {
props.dataBucket.grantRead(this.lambda, 'raw/ourairports_data/regions.csv');
props.dataBucket.grantRead(this.lambda, 'raw/LH_Public_Data/airports.json');
props.dataBucket.grantRead(this.lambda, 'raw/LH_Public_Data/aircraft.json');
props.dataBucket.grantReadWrite(this.lambda, 'tmp/seatmap/*');

props.authBucket.grantReadWrite(this.lambda, 'authreq/*');
props.authBucket.grantReadWrite(this.lambda, 'federation/*');
Expand Down
6 changes: 6 additions & 0 deletions cdk/lib/stacks/data-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ export class DataStack extends cdk.Stack {
noncurrentVersionExpiration: Duration.days(1),
noncurrentVersionsToRetain: 2,
},
{
enabled: true,
expiration: Duration.days(3),
noncurrentVersionExpiration: Duration.days(1),
prefix: 'tmp/',
},
],
});
}
Expand Down
87 changes: 86 additions & 1 deletion go/api/data/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ import (
"github.com/explore-flights/monorepo/go/common"
"github.com/explore-flights/monorepo/go/common/adapt"
"github.com/explore-flights/monorepo/go/common/lufthansa"
"github.com/explore-flights/monorepo/go/common/xtime"
"io"
"log/slog"
"net/http"
"slices"
"strconv"
"strings"
Expand Down Expand Up @@ -159,16 +161,19 @@ type Aircraft struct {
type MinimalS3Client interface {
adapt.S3Getter
adapt.S3Lister
adapt.S3Putter
}

type Handler struct {
s3c MinimalS3Client
lhc *lufthansa.Client
bucket string
}

func NewHandler(s3c MinimalS3Client, bucket string) *Handler {
func NewHandler(s3c MinimalS3Client, lhc *lufthansa.Client, bucket string) *Handler {
return &Handler{
s3c: s3c,
lhc: lhc,
bucket: bucket,
}
}
Expand Down Expand Up @@ -415,6 +420,86 @@ func (h *Handler) Airlines(ctx context.Context, prefix string) ([]common.Airline
}), nil
}

func (h *Handler) SeatMap(ctx context.Context, fn common.FlightNumber, departureAirport, arrivalAirport string, departureDate xtime.LocalDate, cabinClass lufthansa.CabinClass) (*lufthansa.SeatAvailability, error) {
sm, found, err := h.loadSeatMapFromS3(ctx, fn, departureAirport, arrivalAirport, departureDate, cabinClass)
if err != nil {
return nil, err
}

if found {
slog.InfoContext(
ctx,
"loaded seatmap from s3 cache",
slog.String("fn", fn.String()),
slog.String("departureAirport", departureAirport),
slog.String("arrivalAirport", arrivalAirport),
slog.String("departureDate", departureDate.String()),
slog.String("cabinClass", string(cabinClass)),
slog.Bool("isNull", sm == nil),
)

return sm, nil
}

sm, err = h.loadSeatMapFromLH(ctx, fn, departureAirport, arrivalAirport, departureDate, cabinClass)
if err != nil {
return nil, err
}

s3Key := h.seatMapS3Key(fn, departureAirport, arrivalAirport, departureDate, cabinClass)
return sm, adapt.S3PutJson(ctx, h.s3c, h.bucket, s3Key, sm)
}

func (h *Handler) loadSeatMapFromLH(ctx context.Context, fn common.FlightNumber, departureAirport, arrivalAirport string, departureDate xtime.LocalDate, cabinClass lufthansa.CabinClass) (*lufthansa.SeatAvailability, error) {
sm, err := h.lhc.SeatMap(
ctx,
fn.String(),
departureAirport,
arrivalAirport,
departureDate,
cabinClass,
)

if err != nil {
var rse lufthansa.ResponseStatusErr
if errors.As(err, &rse) && rse.StatusCode == http.StatusNotFound {
return nil, nil
} else {
return nil, err
}
}

return &sm, nil
}

func (h *Handler) loadSeatMapFromS3(ctx context.Context, fn common.FlightNumber, departureAirport, arrivalAirport string, departureDate xtime.LocalDate, cabinClass lufthansa.CabinClass) (*lufthansa.SeatAvailability, bool, error) {
resp, err := h.s3c.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(h.bucket),
Key: aws.String(h.seatMapS3Key(fn, departureAirport, arrivalAirport, departureDate, cabinClass)),
})

if err != nil {
if adapt.IsS3NotFound(err) {
return nil, false, nil
} else {
return nil, false, err
}
}

defer resp.Body.Close()

var sm *lufthansa.SeatAvailability
if err := json.NewDecoder(resp.Body).Decode(&sm); err != nil {
return nil, false, err
}

return sm, true, nil
}

func (h *Handler) seatMapS3Key(fn common.FlightNumber, departureAirport, arrivalAirport string, departureDate xtime.LocalDate, cabinClass lufthansa.CabinClass) string {
return fmt.Sprintf("tmp/seatmap/%s/%s/%s/%s/%s.json", fn.String(), departureAirport, arrivalAirport, departureDate.String(), cabinClass)
}

func (h *Handler) loadCsv(ctx context.Context, name string) (*csvReader, error) {
resp, err := h.s3c.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(h.bucket),
Expand Down
4 changes: 2 additions & 2 deletions go/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func main() {
}

connHandler := search.NewConnectionsHandler(fr)
dataHandler := data.NewHandler(s3c, bucket)
dataHandler := data.NewHandler(s3c, lhc, bucket)

e := echo.New()
e.Use(authHandler.Middleware)
Expand All @@ -71,7 +71,7 @@ func main() {
e.GET("/data/airports.json", web.NewAirportsEndpoint(dataHandler))
e.GET("/data/aircraft.json", web.NewAircraftEndpoint(dataHandler))
e.GET("/data/flight/:fn", web.NewFlightNumberEndpoint(dataHandler))
e.GET("/data/flight/:fn/seatmap/:departure/:arrival/:date/:aircraft", web.NewSeatMapEndpoint(dataHandler, lhc))
e.GET("/data/flight/:fn/seatmap/:departure/:arrival/:date/:aircraft", web.NewSeatMapEndpoint(dataHandler))

if err := run(ctx, e); err != nil {
panic(err)
Expand Down
17 changes: 8 additions & 9 deletions go/api/web/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func NewFlightNumberEndpoint(dh *data.Handler) echo.HandlerFunc {
}
}

func NewSeatMapEndpoint(dh *data.Handler, lhc *lufthansa.Client) echo.HandlerFunc {
func NewSeatMapEndpoint(dh *data.Handler) echo.HandlerFunc {
return func(c echo.Context) error {
fnRaw := c.Param("fn")
departureAirport := strings.ToUpper(c.Param("departure"))
Expand Down Expand Up @@ -86,22 +86,21 @@ func NewSeatMapEndpoint(dh *data.Handler, lhc *lufthansa.Client) echo.HandlerFun
rawSeatMaps := make(map[lufthansa.CabinClass]lufthansa.SeatAvailability)

for _, cabinClass := range cabinClasses {
sm, err := lhc.SeatMap(
sm, err := dh.SeatMap(
c.Request().Context(),
fn.String(),
fn,
departureAirport,
arrivalAirport,
departureDate,
cabinClass,
)

if err != nil {
var rse lufthansa.ResponseStatusErr
if !errors.As(err, &rse) || rse.StatusCode != http.StatusNotFound {
return echo.NewHTTPError(http.StatusBadGateway, err)
}
} else {
rawSeatMaps[cabinClass] = sm
return echo.NewHTTPError(http.StatusInternalServerError)
}

if sm != nil {
rawSeatMaps[cabinClass] = *sm
}
}

Expand Down

0 comments on commit 79cd263

Please sign in to comment.