Skip to content

Commit

Permalink
add airport search
Browse files Browse the repository at this point in the history
  • Loading branch information
its-felix committed May 8, 2024
1 parent 06592b2 commit e3d48fa
Show file tree
Hide file tree
Showing 15 changed files with 432 additions and 37 deletions.
2 changes: 2 additions & 0 deletions cdk/lib/constructs/api-lambda-construct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export class ApiLambdaConstruct extends Construct {

props.dataBucket.grantRead(lambda, 'processed/flights/*');
props.dataBucket.grantRead(lambda, 'raw/LH_Public_Data/airports.json');
props.dataBucket.grantRead(lambda, 'raw/LH_Public_Data/cities.json');
props.dataBucket.grantRead(lambda, 'raw/LH_Public_Data/countries.json');

this.functionURL = new FunctionUrl(this, 'ApiLambdaFunctionUrl', {
function: lambda,
Expand Down
33 changes: 27 additions & 6 deletions cdk/lib/constructs/cloudfront-construct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ export class CloudfrontConstruct extends Construct {
],
},
});

const cacheOverridableResponseHeadersPolicy = new ResponseHeadersPolicy(this, 'CacheOverridableResponseHeadersPolicy', {
customHeadersBehavior: {
customHeaders: [
{ header: 'Cache-Control', value: 'public, max-age=604800, stale-while-revalidate=86400', override: false, }
],
},
});
// endregion

// region OriginRequestPolicy - which headers, cookies and query params should be forwarded to the origin
Expand All @@ -79,6 +87,15 @@ export class CloudfrontConstruct extends Construct {
});
// endregion

// region origins
const apiLambdaOrigin = new HttpOrigin(Fn.select(2, Fn.split('/', props.apiLambdaFunctionURL.url)), {
protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
customHeaders: { Forwarded: `host=${props.domain};proto=https` },
originShieldEnabled: true,
originShieldRegion: Stack.of(this).region,
});
// endregion

const uiResourcesOAC = new CfnOriginAccessControl(this, 'UIResourcesOAC', {
originAccessControlConfig: {
// these names must be unique
Expand Down Expand Up @@ -137,19 +154,23 @@ export class CloudfrontConstruct extends Construct {
},
additionalBehaviors: {
'/api/*': {
origin: new HttpOrigin(Fn.select(2, Fn.split('/', props.apiLambdaFunctionURL.url)), {
protocolPolicy: OriginProtocolPolicy.HTTPS_ONLY,
customHeaders: { Forwarded: `host=${props.domain};proto=https` },
originShieldEnabled: true,
originShieldRegion: Stack.of(this).region,
}),
origin: apiLambdaOrigin,
compress: true,
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: AllowedMethods.ALLOW_ALL,
cachePolicy: CachePolicy.CACHING_DISABLED,
originRequestPolicy: allExceptHostOriginRequestPolicy,
responseHeadersPolicy: noCacheResponseHeadersPolicy,
},
'/data/*': {
origin: apiLambdaOrigin,
compress: true,
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
cachePolicy: CachePolicy.CACHING_OPTIMIZED,
originRequestPolicy: allExceptHostOriginRequestPolicy,
responseHeadersPolicy: cacheOverridableResponseHeadersPolicy,
},
},
enableLogging: false,
enabled: true,
Expand Down
24 changes: 19 additions & 5 deletions go/api/config_lambda.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"errors"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/explore-flights/monorepo/go/api/data"
"github.com/explore-flights/monorepo/go/api/search"
"os"
"strconv"
Expand All @@ -18,16 +19,29 @@ func echoPort() int {
return cmp.Or(port, 8080)
}

func flightRepo(ctx context.Context) (*search.FlightRepo, error) {
func s3Client(ctx context.Context) (*s3.Client, error) {
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}

return s3.NewFromConfig(cfg), nil
}

func dataRepo(ctx context.Context, s3c data.MinimalS3Client) (*data.Repo, error) {
dataBucket := os.Getenv("FLIGHTS_DATA_BUCKET")
if dataBucket == "" {
return nil, errors.New("env variable FLIGHTS_DATA_BUCKET required")
}

cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
return data.NewRepo(s3c, dataBucket), nil
}

func flightRepo(ctx context.Context, s3c search.MinimalS3Client) (*search.FlightRepo, error) {
dataBucket := os.Getenv("FLIGHTS_DATA_BUCKET")
if dataBucket == "" {
return nil, errors.New("env variable FLIGHTS_DATA_BUCKET required")
}

return search.NewFlightRepo(s3.NewFromConfig(cfg), dataBucket), nil
return search.NewFlightRepo(s3c, dataBucket), nil
}
16 changes: 11 additions & 5 deletions go/api/config_local.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main

import (
"context"
"github.com/explore-flights/monorepo/go/api/data"
"github.com/explore-flights/monorepo/go/api/local"
"github.com/explore-flights/monorepo/go/api/search"
"os"
Expand All @@ -14,14 +15,19 @@ func echoPort() int {
return 8080
}

func flightRepo(ctx context.Context) (*search.FlightRepo, error) {
func s3Client(ctx context.Context) (*local.S3Client, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, err
}

return search.NewFlightRepo(
local.NewS3Client(filepath.Join(home, "Downloads", "local_s3")),
"flights_data_bucket",
), nil
return local.NewS3Client(filepath.Join(home, "Downloads", "local_s3")), nil
}

func dataRepo(ctx context.Context, s3c data.MinimalS3Client) (*data.Repo, error) {
return data.NewRepo(s3c, "flights_data_bucket"), nil
}

func flightRepo(ctx context.Context, s3c search.MinimalS3Client) (*search.FlightRepo, error) {
return search.NewFlightRepo(s3c, "flights_data_bucket"), nil
}
160 changes: 160 additions & 0 deletions go/api/data/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package data

import (
"context"
"github.com/explore-flights/monorepo/go/common/lufthansa"
"slices"
)

type Country struct {
Code string `json:"code"`
Name string `json:"name"`
Cities []*City `json:"cities"`
}

type City struct {
Code string `json:"code"`
Name string `json:"name"`
Airports []*Airport `json:"airports"`
}

type Airport struct {
Code string `json:"code"`
Name string `json:"name"`
}

type Handler struct {
r *Repo
}

func NewHandler(r *Repo) *Handler {
return &Handler{r}
}

func (h *Handler) Locations(ctx context.Context, lang string) ([]*Country, error) {
airports, err := h.r.Airports(ctx)
if err != nil {
return nil, err
}

airports = addMissingAirports(airports)

citiesByCode, err := h.cities(ctx, lang)
if err != nil {
return nil, err
}

countriesByCode, err := h.countries(ctx, lang)
if err != nil {
return nil, err
}

r := make([]*Country, 0, len(countriesByCode))
for _, airport := range airports {
country, ok := countriesByCode[airport.CountryCode]
if !ok {
country = &Country{
Code: airport.CountryCode,
Name: airport.CountryCode,
Cities: make([]*City, 0),
}

countriesByCode[airport.CountryCode] = country
}

if !slices.Contains(r, country) {
r = append(r, country)
}

city, ok := citiesByCode[airport.CityCode]
if !ok {
city = &City{
Code: airport.CityCode,
Name: airport.CityCode,
Airports: make([]*Airport, 0),
}

citiesByCode[airport.CityCode] = city
}

if !slices.Contains(country.Cities, city) {
country.Cities = append(country.Cities, city)
}

city.Airports = append(city.Airports, &Airport{
Code: airport.Code,
Name: findName(airport.Names.Name, lang),
})
}

return r, nil
}

func (h *Handler) countries(ctx context.Context, lang string) (map[string]*Country, error) {
countries, err := h.r.Countries(ctx)
if err != nil {
return nil, err
}

r := make(map[string]*Country, len(countries))
for _, v := range countries {
r[v.CountryCode] = &Country{
Code: v.CountryCode,
Name: findName(v.Names.Name, lang),
Cities: make([]*City, 0),
}
}

return r, nil
}

func (h *Handler) cities(ctx context.Context, lang string) (map[string]*City, error) {
cities, err := h.r.Cities(ctx)
if err != nil {
return nil, err
}

r := make(map[string]*City, len(cities))
for _, v := range cities {
r[v.CityCode] = &City{
Code: v.CityCode,
Name: findName(v.Names.Name, lang),
Airports: make([]*Airport, 0),
}
}

return r, nil
}

func findName(n []lufthansa.Name, lang string) string {
if len(n) < 1 {
return ""
}

r := n[0].Name
for _, v := range n {
if v.LanguageCode == lang {
return v.Name
} else if v.LanguageCode == "EN" {
r = v.Name
}
}

return r
}

func addMissingAirports(airports []lufthansa.Airport) []lufthansa.Airport {
return append(
airports,
lufthansa.Airport{
Code: "BER",
CityCode: "BER",
CountryCode: "DE",
Names: lufthansa.Names{
Name: lufthansa.Array[lufthansa.Name]{
{LanguageCode: "EN", Name: "Berlin/Brandenburg"},
},
},
},
)
}
54 changes: 54 additions & 0 deletions go/api/data/repo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package data

import (
"context"
"encoding/json"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/explore-flights/monorepo/go/common/lufthansa"
)

type MinimalS3Client interface {
GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error)
}

type Repo struct {
s3c MinimalS3Client
bucket string
}

func NewRepo(s3c MinimalS3Client, bucket string) *Repo {
return &Repo{
s3c: s3c,
bucket: bucket,
}
}

func (r *Repo) Airports(ctx context.Context) ([]lufthansa.Airport, error) {
return loadJson[[]lufthansa.Airport](ctx, r.s3c, r.bucket, "raw/LH_Public_Data/airports.json")
}

func (r *Repo) Cities(ctx context.Context) ([]lufthansa.City, error) {
return loadJson[[]lufthansa.City](ctx, r.s3c, r.bucket, "raw/LH_Public_Data/cities.json")
}

func (r *Repo) Countries(ctx context.Context) ([]lufthansa.Country, error) {
return loadJson[[]lufthansa.Country](ctx, r.s3c, r.bucket, "raw/LH_Public_Data/countries.json")
}

func loadJson[T any](ctx context.Context, s3c MinimalS3Client, bucket, key string) (T, error) {
resp, err := s3c.GetObject(ctx, &s3.GetObjectInput{
Bucket: aws.String(bucket),
Key: aws.String(key),
})

if err != nil {
var r T
return r, err
}

defer resp.Body.Close()

var r T
return r, json.NewDecoder(resp.Body).Decode(&r)
}
1 change: 1 addition & 0 deletions go/api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ require (
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
)

replace github.com/explore-flights/monorepo/go/common v0.0.0 => ../common
2 changes: 2 additions & 0 deletions go/api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,7 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading

0 comments on commit e3d48fa

Please sign in to comment.