Skip to content

Commit b1e6a0d

Browse files
committed
fix flight schedules conversion handling of legs on the next day
previously, flights conversion saved the result file using the same date used to query from the LH API. When there is a flight with multiple legs, where one log falls into the next day (e.g. 4Y136), the flight was saved to the wrong flights file. This fix handles this by always taking the UTC departure date after conversion of legs to flights, then upserting the existing file if it exists
1 parent f54eecc commit b1e6a0d

23 files changed

+432
-99
lines changed

.github/workflows/deploy.yml

+2-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ jobs:
6868
GOOS: 'linux'
6969
GOARCH: 'arm64' # keep this in sync with the arch configured in CDK!
7070
CGO_ENABLED: '0'
71-
run: 'go build -o bootstrap -tags "lambda.norpc"'
71+
run: |
72+
go build -o bootstrap -tags "lambda,lambda.norpc"
7273
- name: 'Store cron artifact'
7374
uses: actions/upload-artifact@v4
7475
with:

go/api/auth/repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
"github.com/explore-flights/monorepo/go/api/adapt"
7+
"github.com/explore-flights/monorepo/go/common/adapt"
88
"github.com/gofrs/uuid/v5"
99
"io"
1010
"net/url"

go/api/data/handler.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
"fmt"
99
"github.com/aws/aws-sdk-go-v2/aws"
1010
"github.com/aws/aws-sdk-go-v2/service/s3"
11-
"github.com/explore-flights/monorepo/go/api/adapt"
1211
"github.com/explore-flights/monorepo/go/common"
12+
"github.com/explore-flights/monorepo/go/common/adapt"
1313
"github.com/explore-flights/monorepo/go/common/lufthansa"
1414
"io"
1515
"slices"

go/api/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.23
55
require (
66
github.com/aws/aws-sdk-go-v2 v1.30.4
77
github.com/aws/aws-sdk-go-v2/config v1.27.28
8-
github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0
8+
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1
99
github.com/aws/aws-sdk-go-v2/service/ssm v1.52.5
1010
github.com/explore-flights/monorepo/go/common v0.0.0
1111
github.com/goccy/go-graphviz v0.1.3

go/api/go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHC
2424
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c=
2525
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I=
2626
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16/go.mod h1:Uyk1zE1VVdsHSU7096h/rwnXDzOzYQVl+FNPhPw7ShY=
27-
github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0 h1:Cso4Ev/XauMVsbwdhYEoxg8rxZWw43CFqqaPB5w3W2c=
28-
github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI=
27+
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1 h1:mx2ucgtv+MWzJesJY9Ig/8AFHgoE5FwLXwUVgW/FGdI=
28+
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI=
2929
github.com/aws/aws-sdk-go-v2/service/ssm v1.52.5 h1:eY1n+pyBbgqRBRnpVUg0QguAGMWVLQp2n+SfjjOJuQI=
3030
github.com/aws/aws-sdk-go-v2/service/ssm v1.52.5/go.mod h1:Bw2YSeqq/I4VyVs9JSfdT9ArqyAbQkJEwj13AVm0heg=
3131
github.com/aws/aws-sdk-go-v2/service/sso v1.22.5 h1:zCsFCKvbj25i7p1u94imVoO447I/sFv8qq+lGJhRN0c=

go/api/local/s3.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package local
55
import (
66
"context"
77
"github.com/aws/aws-sdk-go-v2/service/s3"
8-
"github.com/explore-flights/monorepo/go/api/adapt"
8+
"github.com/explore-flights/monorepo/go/common/adapt"
99
"io"
1010
"os"
1111
"path/filepath"

go/api/search/repo.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"encoding/json"
66
"github.com/aws/aws-sdk-go-v2/aws"
77
"github.com/aws/aws-sdk-go-v2/service/s3"
8-
"github.com/explore-flights/monorepo/go/api/adapt"
98
"github.com/explore-flights/monorepo/go/common"
9+
"github.com/explore-flights/monorepo/go/common/adapt"
1010
"github.com/explore-flights/monorepo/go/common/concurrent"
1111
"golang.org/x/sync/errgroup"
1212
"sync"

go/api/web/data.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ package web
33
import (
44
"context"
55
"errors"
6-
"github.com/explore-flights/monorepo/go/api/adapt"
76
"github.com/explore-flights/monorepo/go/api/data"
87
"github.com/explore-flights/monorepo/go/common"
8+
"github.com/explore-flights/monorepo/go/common/adapt"
99
"github.com/labstack/echo/v4"
1010
"net/http"
1111
)
File renamed without changes.
File renamed without changes.
File renamed without changes.

go/common/flight.go

+15-14
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,21 @@ type FlightId struct {
5555
}
5656

5757
type Flight struct {
58-
Airline AirlineIdentifier `json:"airline"`
59-
FlightNumber int `json:"flightNumber"`
60-
Suffix string `json:"suffix"`
61-
DepartureTime time.Time `json:"departureTime"`
62-
DepartureAirport string `json:"departureAirport"`
63-
ArrivalTime time.Time `json:"arrivalTime"`
64-
ArrivalAirport string `json:"arrivalAirport"`
65-
ServiceType string `json:"serviceType"`
66-
AircraftOwner AirlineIdentifier `json:"aircraftOwner"`
67-
AircraftType string `json:"aircraftType"`
68-
AircraftConfigurationVersion string `json:"aircraftConfigurationVersion"`
69-
Registration string `json:"registration"`
70-
DataElements map[int]string `json:"dataElements"`
71-
CodeShares []FlightNumber `json:"codeShares"`
58+
QueryDate LocalDate `json:"queryDate"`
59+
Airline AirlineIdentifier `json:"airline"`
60+
FlightNumber int `json:"flightNumber"`
61+
Suffix string `json:"suffix"`
62+
DepartureTime time.Time `json:"departureTime"`
63+
DepartureAirport string `json:"departureAirport"`
64+
ArrivalTime time.Time `json:"arrivalTime"`
65+
ArrivalAirport string `json:"arrivalAirport"`
66+
ServiceType string `json:"serviceType"`
67+
AircraftOwner AirlineIdentifier `json:"aircraftOwner"`
68+
AircraftType string `json:"aircraftType"`
69+
AircraftConfigurationVersion string `json:"aircraftConfigurationVersion"`
70+
Registration string `json:"registration"`
71+
DataElements map[int]string `json:"dataElements"`
72+
CodeShares map[FlightNumber]map[int]string `json:"codeShares"`
7273
}
7374

7475
func (f *Flight) DepartureDate() LocalDate {

go/common/flight_schedule.go

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package common
2+
3+
import (
4+
"maps"
5+
)
6+
7+
type FlightScheduleData struct {
8+
DepartureTime OffsetTime `json:"departureTime"`
9+
DepartureAirport string `json:"departureAirport"`
10+
ArrivalTime OffsetTime `json:"arrivalTime"`
11+
ArrivalAirport string `json:"arrivalAirport"`
12+
ServiceType string `json:"serviceType"`
13+
AircraftOwner AirlineIdentifier `json:"aircraftOwner"`
14+
AircraftType string `json:"aircraftType"`
15+
AircraftConfigurationVersion string `json:"aircraftConfigurationVersion"`
16+
Registration string `json:"registration"`
17+
DataElements map[int]string `json:"dataElements"`
18+
CodeShares []FlightNumber `json:"codeShares"`
19+
}
20+
21+
func (fsd FlightScheduleData) Equal(other FlightScheduleData) bool {
22+
return fsd.DepartureTime == other.DepartureTime &&
23+
fsd.DepartureAirport == other.DepartureAirport &&
24+
fsd.ArrivalTime == other.ArrivalTime &&
25+
fsd.ArrivalAirport == other.ArrivalAirport &&
26+
fsd.ServiceType == other.ServiceType &&
27+
fsd.AircraftOwner == other.AircraftOwner &&
28+
fsd.AircraftType == other.AircraftType &&
29+
fsd.AircraftConfigurationVersion == other.AircraftConfigurationVersion &&
30+
fsd.Registration == other.Registration &&
31+
maps.Equal(fsd.DataElements, other.DataElements) &&
32+
SliceEqualContent(fsd.CodeShares, other.CodeShares)
33+
}
34+
35+
type FlightScheduleVariant struct {
36+
Ranges []LocalDateRange `json:"ranges"`
37+
Data FlightScheduleData `json:"data"`
38+
}
39+
40+
func (fsv *FlightScheduleVariant) Expand(d LocalDate) {
41+
42+
}
43+
44+
type FlightSchedule struct {
45+
Airline AirlineIdentifier `json:"airline"`
46+
FlightNumber int `json:"flightNumber"`
47+
Suffix string `json:"suffix"`
48+
Variants []*FlightScheduleVariant `json:"variants"`
49+
}
50+
51+
func (fs *FlightSchedule) DataVariant(fsd FlightScheduleData) *FlightScheduleVariant {
52+
for _, variant := range fs.Variants {
53+
if variant.Data.Equal(fsd) {
54+
return variant
55+
}
56+
}
57+
58+
return nil
59+
}

go/common/go.mod

+14-1
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,22 @@ module github.com/explore-flights/monorepo/go/common
33
go 1.23
44

55
require (
6+
github.com/aws/aws-sdk-go-v2 v1.30.4
7+
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1
68
github.com/go-jose/go-jose/v4 v4.0.4
79
github.com/golang-jwt/jwt/v5 v5.2.1
810
golang.org/x/time v0.6.0
911
)
1012

11-
require golang.org/x/crypto v0.26.0 // indirect
13+
require (
14+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 // indirect
15+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 // indirect
16+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 // indirect
17+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 // indirect
18+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 // indirect
19+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 // indirect
20+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 // indirect
21+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 // indirect
22+
github.com/aws/smithy-go v1.20.4 // indirect
23+
golang.org/x/crypto v0.26.0 // indirect
24+
)

go/common/go.sum

+22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
github.com/aws/aws-sdk-go-v2 v1.30.4 h1:frhcagrVNrzmT95RJImMHgabt99vkXGslubDaDagTk8=
2+
github.com/aws/aws-sdk-go-v2 v1.30.4/go.mod h1:CT+ZPWXbYrci8chcARI3OmI/qgd+f6WtuLOoaIA8PR0=
3+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4 h1:70PVAiL15/aBMh5LThwgXdSQorVr91L127ttckI9QQU=
4+
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.4/go.mod h1:/MQxMqci8tlqDH+pjmoLu1i0tbWCUP1hhyMRuFxpQCw=
5+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16 h1:TNyt/+X43KJ9IJJMjKfa3bNTiZbUP7DeCxfbTROESwY=
6+
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.16/go.mod h1:2DwJF39FlNAUiX5pAc0UNeiz16lK2t7IaFcm0LFHEgc=
7+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16 h1:jYfy8UPmd+6kJW5YhY0L1/KftReOGxI/4NtVSTh9O/I=
8+
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.16/go.mod h1:7ZfEPZxkW42Afq4uQB8H2E2e6ebh6mXTueEpYzjCzcs=
9+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16 h1:mimdLQkIX1zr8GIPY1ZtALdBQGxcASiBd2MOp8m/dMc=
10+
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.16/go.mod h1:YHk6owoSwrIsok+cAH9PENCOGoH5PU2EllX4vLtSrsY=
11+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4 h1:KypMCbLPPHEmf9DgMGw51jMj77VfGPAN2Kv4cfhlfgI=
12+
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.4/go.mod h1:Vz1JQXliGcQktFTN/LN6uGppAIRoLBR2bMvIMP0gOjc=
13+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18 h1:GckUnpm4EJOAio1c8o25a+b3lVfwVzC9gnSBqiiNmZM=
14+
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.18/go.mod h1:Br6+bxfG33Dk3ynmkhsW2Z/t9D4+lRqdLDNCKi85w0U=
15+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18 h1:tJ5RnkHCiSH0jyd6gROjlJtNwov0eGYNz8s8nFcR0jQ=
16+
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.18/go.mod h1:++NHzT+nAF7ZPrHPsA+ENvsXkOO8wEu+C6RXltAG4/c=
17+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16 h1:jg16PhLPUiHIj8zYIW6bqzeQSuHVEiWnGA0Brz5Xv2I=
18+
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.16/go.mod h1:Uyk1zE1VVdsHSU7096h/rwnXDzOzYQVl+FNPhPw7ShY=
19+
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1 h1:mx2ucgtv+MWzJesJY9Ig/8AFHgoE5FwLXwUVgW/FGdI=
20+
github.com/aws/aws-sdk-go-v2/service/s3 v1.60.1/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI=
21+
github.com/aws/smithy-go v1.20.4 h1:2HK1zBdPgRbjFOHlfeQZfpC4r72MOb9bZkiFwggKO+4=
22+
github.com/aws/smithy-go v1.20.4/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
123
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
224
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
325
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=

go/common/localdate.go

+22
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,25 @@ func (ld *LocalDate) UnmarshalJSON(data []byte) error {
7979
func (ld LocalDate) MarshalJSON() ([]byte, error) {
8080
return json.Marshal(ld.String())
8181
}
82+
83+
type LocalDateRange [2]LocalDate
84+
85+
func (ldr LocalDateRange) Iter() iter.Seq[LocalDate] {
86+
return ldr[0].Until(ldr[1])
87+
}
88+
89+
func (ldr LocalDateRange) Contains(d LocalDate) bool {
90+
return ldr[0].Compare(d) <= 0 && ldr[1].Compare(d) >= 0
91+
}
92+
93+
type LocalDateRanges []LocalDateRange
94+
95+
func (ldrs LocalDateRanges) Contains(d LocalDate) bool {
96+
for _, ldr := range ldrs {
97+
if ldr.Contains(d) {
98+
return true
99+
}
100+
}
101+
102+
return false
103+
}

go/common/time.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package common
2+
3+
import (
4+
"encoding/json"
5+
"time"
6+
)
7+
8+
const offsetTimeFormat = "15:04:05Z07:00"
9+
10+
type OffsetTime struct {
11+
Hour int
12+
Min int
13+
Sec int
14+
Loc *time.Location
15+
}
16+
17+
func NewOffsetTime(t time.Time) OffsetTime {
18+
hour, minute, sec := t.Clock()
19+
return OffsetTime{
20+
Hour: hour,
21+
Min: minute,
22+
Sec: sec,
23+
Loc: t.Location(),
24+
}
25+
}
26+
27+
func ParseOffsetTime(v string) (OffsetTime, error) {
28+
t, err := time.Parse(offsetTimeFormat, v)
29+
if err != nil {
30+
return OffsetTime{}, err
31+
}
32+
33+
return OffsetTime{
34+
Hour: t.Hour(),
35+
Min: t.Minute(),
36+
Sec: t.Second(),
37+
Loc: t.Location(),
38+
}, nil
39+
}
40+
41+
func MustParseOffsetTime(v string) OffsetTime {
42+
t, err := ParseOffsetTime(v)
43+
if err != nil {
44+
panic(err)
45+
}
46+
47+
return t
48+
}
49+
50+
func (t OffsetTime) Time(d LocalDate) time.Time {
51+
return time.Date(d.Year, d.Month, d.Day, t.Hour, t.Min, t.Sec, 0, t.Loc)
52+
}
53+
54+
func (t OffsetTime) String() string {
55+
return t.Time(LocalDate{}).Format(offsetTimeFormat)
56+
}
57+
58+
func (t *OffsetTime) UnmarshalJSON(data []byte) error {
59+
var v string
60+
if err := json.Unmarshal(data, &v); err != nil {
61+
return err
62+
}
63+
64+
r, err := time.Parse(offsetTimeFormat, v)
65+
if err != nil {
66+
return err
67+
}
68+
69+
*t = NewOffsetTime(r)
70+
return nil
71+
}
72+
73+
func (t OffsetTime) MarshalJSON() ([]byte, error) {
74+
return json.Marshal(t.String())
75+
}
76+
77+
func SplitTime(t time.Time) (LocalDate, OffsetTime) {
78+
return NewLocalDate(t), NewOffsetTime(t)
79+
}

go/common/time_test.go

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package common
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestSplitTime(t *testing.T) {
9+
now := time.Now().Truncate(time.Second)
10+
d, ot := SplitTime(now)
11+
restored := ot.Time(d)
12+
13+
if now != restored {
14+
t.Fatalf("Restored time does not match the original time: %v != %v", now, restored)
15+
return
16+
}
17+
}

go/common/util.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package common
2+
3+
import "slices"
4+
5+
func SliceEqualContent[S ~[]E, E comparable](s1, s2 S) bool {
6+
if len(s1) != len(s2) {
7+
return false
8+
}
9+
10+
s1 = slices.Clone(s1)
11+
s2 = slices.Clone(s2)
12+
13+
for len(s1) > 0 && len(s2) > 0 {
14+
idx := slices.Index(s2, s1[0])
15+
if idx == -1 {
16+
return false
17+
}
18+
19+
s1 = s1[1:]
20+
s2 = slices.Delete(s2, idx, idx+1)
21+
}
22+
23+
return len(s1) == 0 && len(s2) == 0
24+
}

go/common/util_test.go

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package common
2+
3+
import "testing"
4+
5+
func TestSliceEqualContent(t *testing.T) {
6+
s1 := []string{"a", "b", "c", "d"}
7+
s2 := []string{"a", "d", "c", "b"}
8+
9+
if !SliceEqualContent(s1, s2) {
10+
t.Fatal("slices should be equal")
11+
}
12+
}

0 commit comments

Comments
 (0)