Skip to content

Commit

Permalink
add atom/rss feed for allegris
Browse files Browse the repository at this point in the history
  • Loading branch information
its-felix committed Nov 6, 2024
1 parent bbfa61f commit 1c12c8d
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 33 deletions.
28 changes: 27 additions & 1 deletion go/api/data/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ func (h *Handler) QuerySchedules(ctx context.Context, airline common.AirlineIden
fn := fs.Number()

if variant.Data.ServiceType == "J" && variant.Data.AircraftType == aircraftType && variant.Data.AircraftConfigurationVersion == aircraftConfigurationVersion && variant.Data.OperatedAs == fn {
if span, ok := variant.Ranges.Span(); ok {
if cnt, span := variant.Ranges.Span(); cnt > 0 {
idx := slices.IndexFunc(result[fn], func(rr RouteAndRange) bool {
return rr.DepartureAirport == variant.Data.DepartureAirport && rr.ArrivalAirport == variant.Data.ArrivalAirport
})
Expand Down Expand Up @@ -558,6 +558,32 @@ func (h *Handler) QuerySchedules(ctx context.Context, airline common.AirlineIden
})
}

func (h *Handler) FlightSchedules(ctx context.Context, airline common.AirlineIdentifier, fn func(seq iter.Seq[*common.FlightSchedule]) error) error {
return h.flightSchedules(ctx, airline, func(seq iter.Seq[jstream.KV]) error {
var internalErr error
err := fn(func(yield func(*common.FlightSchedule) bool) {
for kv := range seq {
var b []byte
b, internalErr = json.Marshal(kv.Value)
if internalErr != nil {
return
}

var fs *common.FlightSchedule
if internalErr = json.Unmarshal(b, &fs); internalErr != nil {
return
}

if !yield(fs) {
return
}
}
})

return errors.Join(internalErr, err)
})
}

func (h *Handler) flightSchedules(ctx context.Context, airline common.AirlineIdentifier, fn func(seq iter.Seq[jstream.KV]) error) error {
resp, err := h.s3c.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(h.bucket),
Expand Down
2 changes: 2 additions & 0 deletions go/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ func main() {
e.GET("/data/:airline/schedule/:aircraftType/:aircraftConfigurationVersion/v2", web.NewQueryFlightSchedulesEndpoint(dataHandler))
e.GET("/data/:fn/:departureDate/:departureAirport/feed.rss", web.NewFlightUpdateFeedEndpoint(dataHandler, "application/rss+xml", (*feeds.Feed).WriteRss))
e.GET("/data/:fn/:departureDate/:departureAirport/feed.atom", web.NewFlightUpdateFeedEndpoint(dataHandler, "application/atom+xml", (*feeds.Feed).WriteAtom))
e.GET("/data/allegris/feed.rss", web.NewAllegrisUpdateFeedEndpoint(dataHandler, "application/rss+xml", (*feeds.Feed).WriteRss))
e.GET("/data/allegris/feed.atom", web.NewAllegrisUpdateFeedEndpoint(dataHandler, "application/atom+xml", (*feeds.Feed).WriteAtom))

if err := run(ctx, e); err != nil {
panic(err)
Expand Down
141 changes: 141 additions & 0 deletions go/api/web/feed.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package web

import (
"cmp"
"fmt"
"github.com/explore-flights/monorepo/go/api/data"
"github.com/explore-flights/monorepo/go/common"
Expand All @@ -9,6 +10,7 @@ import (
"github.com/gorilla/feeds"
"github.com/labstack/echo/v4"
"io"
"iter"
"maps"
"net/http"
"net/url"
Expand Down Expand Up @@ -120,3 +122,142 @@ Codeshares: %+v
return nil
}
}

func NewAllegrisUpdateFeedEndpoint(dh *data.Handler, contentType string, writer func(*feeds.Feed, io.Writer) error) echo.HandlerFunc {
const (
feedId = "https://explore.flights/allegris"
allegrisAircraftType = "359"
allegrisAircraftConfigurationNoFirst = "C38E24M201"
allegrisAircraftConfigurationWithFirst = "F4C38E24M201"
)

buildItemLink := func(fn common.FlightNumber, aircraftConfigurationVersion string) string {
q := make(url.Values)
q.Set("aircraft_type", allegrisAircraftType)
q.Set("aircraft_configuration_version", aircraftConfigurationVersion)

return fmt.Sprintf("https://explore.flights/flight/%s?%s", fn.String(), q.Encode())
}

buildItemContent := func(fn common.FlightNumber, variants []*common.FlightScheduleVariant) string {
rangeByRoute := make(map[[2]string]xtime.LocalDateRanges)

for _, variant := range variants {
route := [2]string{variant.Data.DepartureAirport, variant.Data.ArrivalAirport}

if ldrs, ok := rangeByRoute[route]; ok {
rangeByRoute[route] = ldrs.ExpandAll(variant.Ranges)
} else {
rangeByRoute[route] = variant.Ranges
}
}

routesSorted := slices.SortedFunc(maps.Keys(rangeByRoute), func(a [2]string, b [2]string) int {
return cmp.Or(
cmp.Compare(a[0], b[0]),
cmp.Compare(a[1], b[1]),
)
})

result := fmt.Sprintf("Flight %s operates Allegris on:\n", fn.String())
for _, route := range routesSorted {
ldrs := rangeByRoute[route]
if cnt, span := ldrs.Span(); cnt > 0 {
result += fmt.Sprintf("%s - %s from %s until %s (%d days)\n", route[0], route[1], span[0].String(), span[1].String(), cnt)
}
}

return strings.TrimSpace(result)
}

return func(c echo.Context) error {
results := make(map[string]map[common.FlightNumber][]*common.FlightScheduleVariant)

err := dh.FlightSchedules(c.Request().Context(), common.Lufthansa, func(seq iter.Seq[*common.FlightSchedule]) error {
for fs := range seq {
fn := fs.Number()

for _, variant := range fs.Variants {
if variant.Data.ServiceType == "J" && variant.Data.AircraftType == allegrisAircraftType && variant.Data.OperatedAs == fn {
if variant.Data.AircraftConfigurationVersion == allegrisAircraftConfigurationNoFirst || variant.Data.AircraftConfigurationVersion == allegrisAircraftConfigurationWithFirst {
byFn, ok := results[variant.Data.AircraftConfigurationVersion]
if !ok {
byFn = make(map[common.FlightNumber][]*common.FlightScheduleVariant)
results[variant.Data.AircraftConfigurationVersion] = byFn
}

byFn[fn] = append(byFn[fn], variant)
}
}
}
}

return nil
})

if err != nil {
noCache(c)
return echo.NewHTTPError(http.StatusInternalServerError)
}

feed := &feeds.Feed{
Id: feedId,
Title: "Lufthansa Allegris Flights",
Link: &feeds.Link{
Href: feedId,
Rel: "self",
Type: "text/html",
},
}

for aircraftConfigurationVersion, byFn := range results {
for fn, variants := range byFn {
if len(variants) < 0 {
continue
}

itemLink := buildItemLink(fn, aircraftConfigurationVersion)
var created time.Time
var updated time.Time

for _, variant := range variants {
if created.IsZero() || created.After(variant.Metadata.CreationTime) {
created = variant.Metadata.CreationTime
}

updateTime := common.Max(variant.Metadata.RangesUpdateTime, variant.Metadata.DataUpdateTime)
if updated.IsZero() || updated.Before(updateTime) {
updated = updateTime
}
}

var suffix string
if aircraftConfigurationVersion == allegrisAircraftConfigurationNoFirst {
suffix = "without first"
} else {
suffix = "with first"
}

content := buildItemContent(fn, variants)
feed.Items = append(feed.Items, &feeds.Item{
Id: itemLink,
IsPermaLink: "false",
Title: fmt.Sprintf("Flight %s operates on Allegris %s (%s)", fn.String(), suffix, aircraftConfigurationVersion),
Link: &feeds.Link{
Href: itemLink,
Rel: "self",
Type: "text/html",
},
Created: created,
Updated: updated,
Content: content,
Description: content,
})
}
}

c.Response().Header().Add(echo.HeaderContentType, contentType)
addExpirationHeaders(c, time.Now(), time.Hour)
return writer(feed, c.Response())
}
}
21 changes: 21 additions & 0 deletions go/common/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package common

type SelfComparator[T any] interface {
Compare(other T) int
}

func Min[T SelfComparator[T]](a, b T) T {
if a.Compare(b) < 0 {
return a
}

return b
}

func Max[T SelfComparator[T]](a, b T) T {
if a.Compare(b) > 0 {
return a
}

return b
}
6 changes: 3 additions & 3 deletions go/common/xtime/range.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,13 @@ func (ldrs LocalDateRanges) Iter() iter.Seq[LocalDate] {
}
}

func (ldrs LocalDateRanges) Span() (LocalDateRange, bool) {
func (ldrs LocalDateRanges) Span() (int, LocalDateRange) {
sorted := slices.SortedFunc(ldrs.Iter(), LocalDate.Compare)
if len(sorted) < 1 {
return LocalDateRange{}, false
return 0, LocalDateRange{}
}

return LocalDateRange{sorted[0], sorted[len(sorted)-1]}, true
return len(sorted), LocalDateRange{sorted[0], sorted[len(sorted)-1]}
}

func (ldrs LocalDateRanges) ExpandAll(other LocalDateRanges) LocalDateRanges {
Expand Down
19 changes: 0 additions & 19 deletions go/common/xtime/util.go

This file was deleted.

8 changes: 4 additions & 4 deletions go/cron/action/convert_flight_schedules.go
Original file line number Diff line number Diff line change
Expand Up @@ -348,10 +348,10 @@ func convertFlightSchedulesToFlights(queryDate xtime.LocalDate, lastModified tim
}

func combineFlights(f, other *common.Flight) {
f.Metadata.CreationTime = xtime.Min(f.Metadata.CreationTime, other.Metadata.CreationTime)
f.Metadata.CreationTime = common.Min(f.Metadata.CreationTime, other.Metadata.CreationTime)

if f.DataEqual(other) {
f.Metadata.UpdateTime = xtime.Min(f.Metadata.UpdateTime, other.Metadata.UpdateTime)
f.Metadata.UpdateTime = common.Min(f.Metadata.UpdateTime, other.Metadata.UpdateTime)

for codeShareFn, codeShare := range f.CodeShares {
f.CodeShares[codeShareFn] = combineCodeShares(codeShare, other.CodeShares[codeShareFn])
Expand Down Expand Up @@ -392,10 +392,10 @@ func combineFlights(f, other *common.Flight) {
}

func combineCodeShares(a, b common.CodeShare) common.CodeShare {
a.Metadata.CreationTime = xtime.Min(a.Metadata.CreationTime, b.Metadata.CreationTime)
a.Metadata.CreationTime = common.Min(a.Metadata.CreationTime, b.Metadata.CreationTime)

if a.DataEqual(b) {
a.Metadata.UpdateTime = xtime.Min(a.Metadata.UpdateTime, b.Metadata.UpdateTime)
a.Metadata.UpdateTime = common.Min(a.Metadata.UpdateTime, b.Metadata.UpdateTime)
} else {
bIsNewer := a.Metadata.UpdateTime.Before(b.Metadata.UpdateTime)
if bIsNewer {
Expand Down
12 changes: 6 additions & 6 deletions go/cron/action/convert_flights.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ func (a *cfAction) convertAll(ctx context.Context, bucket, prefix string, dateRa
if fs, ok := byFlightNumber[fn]; ok {
if variant, ok := fs.Variant(fsd); ok {
variant.Ranges = variant.Ranges.Add(d)
variant.Metadata.CreationTime = xtime.Min(variant.Metadata.CreationTime, md.CreationTime)
variant.Metadata.RangesUpdateTime = xtime.Max(variant.Metadata.RangesUpdateTime, md.RangesUpdateTime)
variant.Metadata.DataUpdateTime = xtime.Min(variant.Metadata.DataUpdateTime, md.DataUpdateTime)
variant.Metadata.CreationTime = common.Min(variant.Metadata.CreationTime, md.CreationTime)
variant.Metadata.RangesUpdateTime = common.Max(variant.Metadata.RangesUpdateTime, md.RangesUpdateTime)
variant.Metadata.DataUpdateTime = common.Min(variant.Metadata.DataUpdateTime, md.DataUpdateTime)
} else {
fs.Variants = append(fs.Variants, &common.FlightScheduleVariant{
Ranges: xtime.NewLocalDateRanges(xiter.Single(d)),
Expand Down Expand Up @@ -283,11 +283,11 @@ func combineSchedules(fs *common.FlightSchedule, existing *common.FlightSchedule
if variant, ok := fs.Variant(existingVariant.Data); ok {
if len(existingVariant.Ranges) > 0 {
variant.Ranges = variant.Ranges.ExpandAll(existingVariant.Ranges)
variant.Metadata.RangesUpdateTime = xtime.Max(variant.Metadata.RangesUpdateTime, existingVariant.Metadata.RangesUpdateTime)
variant.Metadata.RangesUpdateTime = common.Max(variant.Metadata.RangesUpdateTime, existingVariant.Metadata.RangesUpdateTime)
}

variant.Metadata.CreationTime = xtime.Min(variant.Metadata.CreationTime, existingVariant.Metadata.CreationTime)
variant.Metadata.DataUpdateTime = xtime.Min(variant.Metadata.DataUpdateTime, existingVariant.Metadata.DataUpdateTime)
variant.Metadata.CreationTime = common.Min(variant.Metadata.CreationTime, existingVariant.Metadata.CreationTime)
variant.Metadata.DataUpdateTime = common.Min(variant.Metadata.DataUpdateTime, existingVariant.Metadata.DataUpdateTime)
} else if len(variant.Ranges) > 0 {
fs.Variants = append(fs.Variants, existingVariant)
}
Expand Down

0 comments on commit 1c12c8d

Please sign in to comment.