Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- package data
- import (
- "context"
- "fmt"
- "sort"
- "strconv"
- "strings"
- "time"
- "trip/data/date"
- "trip/data/model"
- "github.com/go-pg/pg"
- "github.com/go-pg/pg/orm"
- "github.com/graph-gophers/graphql-go"
- "github.com/noken-travel/libauth"
- "github.com/noken-travel/libcurrency"
- "github.com/pkg/errors"
- log "github.com/sirupsen/logrus"
- )
- // An itinerary is a set of days for a destination.
- //
- // It can be generic (templatized) or customized to an individual traveler,
- // but it is distinct from a trip, which contains user specific information
- // around dates, number of people, and so on.
- //
- // When users are building their trip, they start with an itinerary. If the
- // itinerary changes, all of their selections will be reset.
- type Itinerary struct {
- ID int64
- CreatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
- UpdatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
- DeletedAt time.Time `sql:",type:'timestamp without time zone'" pg:",soft_delete"`
- AddDayDescription *string
- ArrivalEndTime date.Time `sql:",type:time,notnull"`
- ArrivalStartTime date.Time `sql:",type:time,notnull"`
- AvailableExperiences []*AvailableExperience `sql:"-"`
- AvailableEndDate date.Date `sql:",type:date,notnull"`
- AvailableStartDate date.Date `sql:",type:date,notnull"`
- Code string `sql:",notnull"`
- Country model.Country
- CountryID int64 `sql:",notnull"`
- Description *string
- DepartureEndTime date.Time `sql:",type:time,notnull"`
- DepartureStartTime date.Time `sql:",type:time,notnull"`
- DesktopMapImageID *int64
- DesktopMapImage *model.Image // Owned by Itinerary; deleted when last reference is deleted
- IsTemplate bool `sql:",notnull"`
- ItineraryDays []*ItineraryDay
- MobileMapImageID *int64
- MobileMapImage *model.Image // Owned by Itinerary; deleted when last reference is deleted
- Name *string
- PricingDescription *string
- StyleDescription *string
- SubtractDayDescription *string
- Summary *string
- Tags []*ItineraryTag
- TransportationSummary *string
- TransportationDescription *string
- tableName struct{} `sql:"trip_schema.itineraries" pg:",discard_unknown_columns"`
- }
- // ItineraryTag is an itinerary tag, a way of annotating an itinerary.
- //
- // Currently these tags are used in two ways:
- //
- // 1. To indicate pace, a tag of "fast" or "slow" might be applied to the trip.
- // A fast trip quickly moves from activity to activity, whereas a slow trip
- // takes a more leisurely approach.
- //
- // 2. To assign a custom itinerary to a user, a tag of "user:123" might be
- // applied, where 123 is the user ID.
- type ItineraryTag struct {
- ID int64
- CreatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
- UpdatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
- DeletedAt time.Time `sql:",type:'timestamp without time zone'" pg:",soft_delete"`
- ItineraryID int64 `sql:"type:'bigint',notnull"`
- Tag string `sql:"type:'text',notnull"`
- tableName struct{} `sql:"trip_schema.itinerary_tags" pg:",discard_unknown_columns"`
- }
- type itineraryInput struct {
- AddDayDescription *string
- ArrivalTimes timeRangeInput
- AvailableDates dateRangeInput
- Code string
- CountryCode graphql.ID
- DepartureTimes timeRangeInput
- Description *string
- DesktopMapImageID *graphql.ID
- DesktopMapImage *imageInput
- IsTemplate *bool
- MobileMapImageID *graphql.ID
- MobileMapImage *imageInput
- Name *string
- PricingDescription *string
- StyleDescription *string
- SubtractDayDescription *string
- Summary *string
- Tags *[]*string
- TransportationDescription *string
- TransportationSummary *string
- }
- type minMaxOrderIndex struct {
- ItineraryID int64
- MinIndex int64
- MaxIndex int64
- }
- type itinerariesResolver struct {
- total int32
- itineraries []Itinerary
- baseSVGURL string
- }
- type itineraryResolver struct {
- itinerary Itinerary
- baseSVGURL string
- }
- var itineraryColumns = []string{
- "itinerary.*",
- "Country",
- "DesktopMapImage",
- "ItineraryDays",
- "ItineraryDays.Alternatives",
- "ItineraryDays.Alternatives.Day",
- "ItineraryDays.Day.DayExperiences",
- "ItineraryDays.Day.DayExperiences.Experience",
- "ItineraryDays.Day.DayExperiences.Experience.Address",
- "ItineraryDays.Day.DayExperiences.Experience.Address.Country",
- "ItineraryDays.Day.DayExperiences.Experience.ExperienceImages",
- "ItineraryDays.Day.DayExperiences.Experience.ExperienceImages.Image",
- "ItineraryDays.Day.DayExperiences.Experience.Facts",
- "ItineraryDays.Day.DayExperiences.Experience.HoverImage",
- "ItineraryDays.Day.DayExperiences.Experience.OverviewImage",
- "ItineraryDays.Day.DayExperiences.Experience.Price",
- "ItineraryDays.Day.DayExperiences.Experience.Price.Taxes",
- // "ItineraryDays.Day.DayExperiences.Experience.Vendor",
- // "ItineraryDays.Day.DayExperiences.Experience.Vendor.Address",
- // "ItineraryDays.Day.DayExperiences.Experience.Vendor.Address.Country",
- "ItineraryDays.Day.DayExperiences.ExperiencePeriod",
- "ItineraryDays.Day.DayImages",
- "ItineraryDays.Day.DayImages.Image",
- "ItineraryDays.Day.DesktopMapImage",
- "ItineraryDays.Day.DesktopRouteMapImage",
- "ItineraryDays.Day.DesktopRouteMapImageAlternate",
- "ItineraryDays.Day.EndCity",
- "ItineraryDays.Day.EndCity.Country",
- "ItineraryDays.Day.MobileMapImage",
- "ItineraryDays.Day.MobileRouteMapImage",
- // "ItineraryDays.Day.DayRestaurants",
- // "ItineraryDays.Day.DayRestaurants.Restaurant",
- // "ItineraryDays.Day.DayRestaurants.Restaurant.CancelationFee",
- // "ItineraryDays.Day.DayRestaurants.Restaurant.RestaurantImages",
- // "ItineraryDays.Day.DayRestaurants.Restaurant.RestaurantImages.Image",
- // "ItineraryDays.Day.DayRestaurants.Restaurant.Vendor",
- // "ItineraryDays.Day.DayRestaurants.Restaurant.Vendor.Address",
- // "ItineraryDays.Day.DayRestaurants.Restaurant.Vendor.Address.Country",
- "ItineraryDays.Day.StartCity",
- "ItineraryDays.Day.StartCity.Country",
- "Tags",
- "MobileMapImage",
- }
- // intersectIDsSQL is used to further refine itineraries during a search.
- var intersectIDsSQL = `
- SELECT
- it.id itinerary_id
- FROM trip_schema.itineraries it
- JOIN (
- SELECT
- it.id itinerary_id,
- d.start_city_id city_id
- FROM trip_schema.itineraries it
- JOIN (
- SELECT
- it.id itinerary_id,
- MIN(itd.order_index) min_index
- FROM trip_schema.itineraries it
- JOIN trip_schema.itinerary_days itd
- ON it.id = itd.itinerary_id
- AND itd.deleted_at IS NULL
- WHERE it.id IN (?)
- AND it.deleted_at IS NULL
- GROUP BY it.id
- ORDER BY it.id
- ) min
- ON it.id = min.itinerary_id
- JOIN trip_schema.itinerary_days itd
- ON it.id = itd.itinerary_id
- AND itd.deleted_at IS NULL
- JOIN trip_schema.days AS d
- ON itd.day_id = d.id
- AND itd.order_index = min.min_index
- AND d.deleted_at IS NULL
- WHERE it.id IN (?)
- AND it.deleted_at IS NULL
- GROUP BY it.id, d.start_city_id
- ORDER BY it.id
- ) start_day
- ON it.id = start_day.itinerary_id
- JOIN (
- SELECT
- it.id itinerary_id,
- d.end_city_id city_id
- FROM trip_schema.itineraries it
- JOIN (
- SELECT
- it.id itinerary_id,
- MAX(itd.order_index) max_index
- FROM trip_schema.itineraries it
- JOIN trip_schema.itinerary_days itd
- ON it.id = itd.itinerary_id
- AND itd.deleted_at IS NULL
- WHERE it.id IN (?)
- AND it.deleted_at IS NULL
- GROUP BY it.id
- ORDER BY it.id
- ) max
- ON it.id = max.itinerary_id
- JOIN trip_schema.itinerary_days itd
- ON it.id = itd.itinerary_id
- AND itd.deleted_at IS NULL
- JOIN trip_schema.days AS d
- ON itd.day_id = d.id
- AND itd.order_index = max.max_index
- AND d.deleted_at IS NULL
- WHERE it.id IN (?)
- AND it.deleted_at IS NULL
- GROUP BY it.id, d.end_city_id
- ORDER BY it.id
- ) end_day
- ON it.id = end_day.itinerary_id
- LEFT JOIN trip_schema.airports a_start
- ON start_day.city_id = a_start.city_id
- LEFT JOIN trip_schema.airports a_end
- ON end_day.city_id = a_end.city_id
- WHERE it.id IN (?)
- AND it.deleted_at IS NULL
- %s
- GROUP BY it.id
- `
- // Equals checks that an itinerary is functionally equivalent to another one.
- // It does this by ensuring that the country and individual day IDs are
- // identical.
- func (a Itinerary) Equals(b Itinerary) bool {
- if a.CountryID != b.CountryID {
- return false
- }
- numDays := len(a.ItineraryDays)
- if numDays != len(b.ItineraryDays) {
- return false
- }
- aDays := a.ItineraryDays
- sort.Slice(aDays, func(i, j int) bool {
- return aDays[i].Index < aDays[j].Index
- })
- bDays := b.ItineraryDays
- sort.Slice(bDays, func(i, j int) bool {
- return bDays[i].Index < bDays[j].Index
- })
- for i := 0; i < numDays; i++ {
- if aDays[i].DayID != bDays[i].DayID {
- return false
- }
- }
- return true
- }
- // AddDaysToItinerary adds days to an itinerary.
- func (r *Resolver) AddDaysToItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- DayIDs []*graphql.ID
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- count, err := r.DB.
- WithContext(ctx).
- Model(&Itinerary{}).
- Where("id = ?", id).
- Count()
- if err != nil {
- log.Error(err.Error())
- return nil, errNotFound
- }
- if count == 0 {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, fmt.Errorf("unknown itinerary: %d", id)
- }
- err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- var index int32
- err = tx.
- Model(&ItineraryDay{}).
- ColumnExpr("MAX(order_index) + 1").
- Where("itinerary_id = ?", id).
- Select(&index)
- if err != nil {
- return err
- }
- var days []ItineraryDay
- for _, s := range args.DayIDs {
- if s == nil {
- continue
- }
- did, err := decodeID(*s)
- if err != nil {
- return err
- }
- days = append(days, ItineraryDay{
- ItineraryID: id,
- DayID: did,
- Index: index,
- })
- index++
- }
- _, err = r.DB.
- WithContext(ctx).
- Model(&days).
- OnConflict("DO NOTHING").
- Insert()
- if err != nil {
- return err
- }
- return nil
- })
- if err != nil {
- displayErr := fmt.Errorf("could not add days to itinerary %d", id)
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, err
- }
- // AddDayAlternativesToItinerary adds alternative days to an itinerary.
- func (r *Resolver) AddDayAlternativesToItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- DayID graphql.ID
- AlternativeIDs []*graphql.ID
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- did, err := decodeID(args.DayID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- var itineraryDay ItineraryDay
- err = r.DB.
- WithContext(ctx).
- Model(&itineraryDay).
- Where("itinerary_id = ?", id).
- Where("day_id = ?", did).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, fmt.Errorf("day %d not found for itinerary %d", did, id)
- }
- var alternatives []ItineraryDayAlternative
- for _, s := range args.AlternativeIDs {
- if s == nil {
- continue
- }
- aid, err := decodeID(*s)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- alternatives = append(alternatives, ItineraryDayAlternative{
- ItineraryDayID: itineraryDay.ID,
- DayID: aid,
- })
- }
- _, err = r.DB.
- WithContext(ctx).
- Model(&alternatives).
- OnConflict("DO NOTHING").
- Insert()
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, err
- }
- // AddTagsToItinerary adds tags to an itinerary.
- func (r *Resolver) AddTagsToItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- Tags []*string
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- var itineraryTags []ItineraryTag
- for _, t := range args.Tags {
- if t == nil {
- continue
- }
- itineraryTags = append(itineraryTags, ItineraryTag{
- ItineraryID: id,
- Tag: strings.ToLower(*t),
- })
- }
- _, err = r.DB.
- WithContext(ctx).
- Model(&itineraryTags).
- OnConflict("DO NOTHING").
- Insert()
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, errors.Wrap(err, "could not get itinerary")
- }
- // CopyItinerary copies the itinerary and creates a duplicate.
- func (r *Resolver) CopyItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- TripID *graphql.ID
- DeepCopy bool
- Suffix *string
- DayIDs []*graphql.ID
- },
- ) (*itineraryResolver, error) {
- claims := libauth.ClaimsFromContext(ctx)
- if claims == nil {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not decode itinerary id [%s]", args.ID)
- }
- if args.TripID == nil {
- if !claims.WriteAnyContent {
- return nil, libauth.ErrPermissionDenied
- }
- } else { // Verify trip ownership by calling user
- var trip Trip
- err = r.DB.
- WithContext(ctx).
- Model(&trip).
- Where("id = ?", id).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errors.Wrapf(err, "could not select trip [%d]", id)
- }
- userID, err := decodeID(graphql.ID(claims.Subject))
- if err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not decode user id for the itinerary [%s]", claims.Subject)
- }
- if !claims.WriteAnyContent && id != trip.ItineraryID && userID != trip.UserID {
- return nil, libauth.ErrPermissionDenied
- }
- }
- var itineraryID *graphql.ID
- err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- itineraryID, err = copyItinerary(
- tx,
- args.ID,
- args.TripID,
- args.DeepCopy,
- args.Suffix,
- args.DayIDs,
- )
- return errors.Wrapf(err, "could not copy itinerary [%d]", id)
- })
- if err != nil {
- log.Error(err)
- return nil, err
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- ID: *itineraryID,
- },
- )
- if err != nil {
- return nil, errors.Wrapf(err, "could not get itinerary [%d]", id)
- }
- return result, nil
- }
- // CreateItinerary creates an itinerary.
- func (r *Resolver) CreateItinerary(
- ctx context.Context,
- args struct{ Itinerary *itineraryInput },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- var itineraryID *graphql.ID
- err := r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- var err error
- itineraryID, err = createItinerary(tx, args.Itinerary)
- return err
- })
- if err != nil {
- displayErr := errors.New("could not create itinerary")
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- *itineraryID,
- },
- )
- return result, err
- }
- // DeleteItinerary deletes an itinerary.
- func (r *Resolver) DeleteItinerary(
- ctx context.Context,
- args struct{ ID graphql.ID },
- ) (*graphql.ID, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- var itinerary Itinerary
- err = r.DB.
- WithContext(ctx).
- Model(&itinerary).
- Where("id = ?", id).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errNotFound
- }
- err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- if itinerary.DesktopMapImageID != nil {
- numImages, err := tx.
- Model(&Itinerary{}).
- Where("desktop_map_image_id = ?", *itinerary.DesktopMapImageID).
- Count()
- if err != nil {
- return err
- }
- if numImages == 1 {
- err = deleteImage(tx, encodeID(*itinerary.DesktopMapImageID))
- if err != nil {
- return err
- }
- }
- }
- if itinerary.MobileMapImageID != nil {
- numImages, err := tx.
- Model(&Itinerary{}).
- Where("mobile_map_image_id = ?", *itinerary.MobileMapImageID).
- Count()
- if err != nil {
- return err
- }
- if numImages == 1 {
- err = deleteImage(tx, encodeID(*itinerary.MobileMapImageID))
- if err != nil {
- return err
- }
- }
- }
- var itineraryDays []*ItineraryDay
- err = tx.
- Model(&itineraryDays).
- Where("itinerary_id = ?", id).
- Select()
- if err != nil {
- return err
- }
- var itineraryDayIDs []int64
- for _, v := range itineraryDays {
- itineraryDayIDs = append(itineraryDayIDs, v.ID)
- }
- if len(itineraryDayIDs) > 0 {
- _, err = tx.
- Model(&ItineraryDayAlternative{}).
- Where("itinerary_day_id IN (?)", pg.In(itineraryDayIDs)).
- Delete()
- if err != nil {
- return err
- }
- }
- _, err = tx.
- Model(&ItineraryDay{}).
- Where("itinerary_id = ?", id).
- Delete()
- if err != nil {
- return err
- }
- _, err = tx.
- Model(&ItineraryTag{}).
- Where("itinerary_id = ?", id).
- Delete()
- if err != nil {
- return err
- }
- _, err = tx.
- Model(&Itinerary{}).
- Where("id = ?", id).
- Delete()
- if err != nil {
- return err
- }
- return nil
- })
- if err != nil {
- displayErr := fmt.Errorf("could not delete itinerary %d", id)
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- return &args.ID, nil
- }
- // Itinerary returns an itinerary.
- func (r *Resolver) Itinerary(
- ctx context.Context,
- args struct{ ID graphql.ID },
- ) (*itineraryResolver, error) {
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- var itinerary Itinerary
- err = r.DB.
- WithContext(ctx).
- Model(&itinerary).
- Column(itineraryColumns...).
- Where("?TableAlias.id = ?", id).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errNotFound
- }
- if !itinerary.IsTemplate {
- claims := libauth.ClaimsFromContext(ctx)
- if claims == nil {
- return nil, libauth.ErrPermissionDenied
- }
- }
- itinerary.AvailableExperiences, err = r.getAvailableExperiences(itinerary.ID)
- if err != nil {
- displayErr := errors.Wrap(err, "could not get available experiences")
- log.Error(displayErr)
- return nil, displayErr
- }
- return &itineraryResolver{
- itinerary: itinerary,
- baseSVGURL: r.BaseSVGURL,
- }, nil
- }
- // Itineraries returns itineraries based on a variety of parameters.
- //
- // If a search parameter is provided, it will search the itinerary code and
- // country name. You can also search by country, airport codes, dates and
- // times, tags, and whether the itinerary is a template or not.
- func (r *Resolver) Itineraries(
- ctx context.Context,
- args struct {
- Search *string
- CountryCode *graphql.ID
- ArrivalAirportCode *graphql.ID
- ArrivalDate *date.Date
- ArrivalTime *date.Time
- DepartureAirportCode *graphql.ID
- DepartureDate *date.Date
- DepartureTime *date.Time
- IsTemplate *bool
- Limit *int32
- Offset *int32
- Tags *[]*string
- },
- ) (*itinerariesResolver, error) {
- q := r.DB.
- WithContext(ctx).
- Model(&Itinerary{}).
- Column("itinerary.id", "Country._").
- Join("LEFT JOIN trip_schema.itinerary_days AS itinerary_day").
- JoinOn("?TableAlias.id = itinerary_day.itinerary_id").
- Join("LEFT JOIN trip_schema.days AS d").
- JoinOn("itinerary_day.day_id = d.id").
- Where("itinerary_day.deleted_at IS NULL").
- Group("itinerary.id")
- ct := r.DB.
- WithContext(ctx).
- Model(&Itinerary{}).
- Column("itinerary.*", "Country._")
- if args.Search != nil {
- if _, ok := libauth.Can(ctx, libauth.ClaimReadAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- search := fmt.Sprintf("%%%s%%", strings.ToLower(*args.Search))
- where := "LOWER(?TableAlias.code) LIKE ?"
- whereOr := `LOWER("country"."name") LIKE ?`
- q = q.WhereGroup(func(q *orm.Query) (*orm.Query, error) {
- q = q.
- Where(where, search).
- WhereOr(whereOr, search)
- return q, nil
- })
- ct = ct.WhereGroup(func(q *orm.Query) (*orm.Query, error) {
- q = q.
- Where(where, search).
- WhereOr(whereOr, search)
- return q, nil
- })
- }
- if args.CountryCode != nil {
- var country model.Country
- err := r.DB.
- WithContext(ctx).
- Model(&country).
- Where("code = ?", *args.CountryCode).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, fmt.Errorf("unknown country: %s", *args.CountryCode)
- }
- where := `"country"."id" = ?`
- q = q.Where(where, country.ID)
- ct = ct.Where(where, country.ID)
- }
- if args.ArrivalDate != nil {
- arrDate := *args.ArrivalDate
- var arrivalDate date.Date
- if err := arrivalDate.Scan(time.Date(1, arrDate.Time.Month(), arrDate.Time.Day(), 0, 0, 0, 0, time.UTC)); err != nil {
- return nil, errors.Wrap(err, "cannot compose arrival date")
- }
- var maxDate date.Date
- if err := maxDate.Scan(time.Date(1, time.December, 31, 23, 59, 59, 0, time.UTC)); err != nil {
- return nil, errors.Wrap(err, "cannot compose max date")
- }
- var minDate date.Date
- if err := minDate.Scan(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)); err != nil {
- return nil, errors.Wrap(err, "cannot compose min date")
- }
- where := `
- CASE
- WHEN ?TableAlias.available_start_date < ?TableAlias.available_end_date THEN
- ? BETWEEN ?TableAlias.available_start_date AND ?TableAlias.available_end_date
- WHEN ?TableAlias.available_start_date > ?TableAlias.available_end_date THEN
- ? BETWEEN ?TableAlias.available_start_date AND ?
- OR ? BETWEEN ? AND ?TableAlias.available_end_date
- END
- `
- params := []interface{}{
- arrivalDate,
- arrivalDate,
- maxDate,
- arrivalDate,
- minDate,
- arrivalDate,
- }
- q = q.Where(where, params...)
- ct = ct.Where(where, params...)
- }
- var maxTime date.Time
- if err := maxTime.Scan(time.Date(1, time.January, 1, 23, 59, 59, 0, time.UTC)); err != nil {
- return nil, errors.Wrap(err, "cannot compose max time")
- }
- var minTime date.Time
- if err := minTime.Scan(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)); err != nil {
- return nil, errors.Wrap(err, "cannot compose min time")
- }
- if args.ArrivalTime != nil {
- where := `
- CASE
- WHEN ?TableAlias.arrival_start_time < ?TableAlias.arrival_end_time THEN
- ? BETWEEN ?TableAlias.arrival_start_time AND ?TableAlias.arrival_end_time
- WHEN ?TableAlias.arrival_start_time > ?TableAlias.arrival_end_time THEN
- ? BETWEEN ?TableAlias.arrival_start_time AND ?
- OR ? BETWEEN ? AND ?TableAlias.arrival_end_time
- END
- `
- params := []interface{}{
- *args.ArrivalTime,
- *args.ArrivalTime,
- maxTime,
- *args.ArrivalTime,
- minTime,
- *args.ArrivalTime,
- }
- q = q.Where(where, params...)
- ct = ct.Where(where, params...)
- }
- if args.DepartureTime != nil {
- where := `
- CASE
- WHEN ?TableAlias.departure_start_time < ?TableAlias.departure_end_time THEN
- ? BETWEEN ?TableAlias.departure_start_time AND ?TableAlias.departure_end_time
- WHEN ?TableAlias.departure_start_time > ?TableAlias.departure_end_time THEN
- ? BETWEEN ?TableAlias.departure_start_time AND ?
- OR ? BETWEEN ? AND ?TableAlias.departure_end_time
- END
- `
- params := []interface{}{
- *args.DepartureTime,
- *args.DepartureTime,
- maxTime,
- *args.DepartureTime,
- minTime,
- }
- q = q.Where(where, params...)
- ct = ct.Where(where, params...)
- }
- if args.IsTemplate != nil {
- q = q.Where("?TableAlias.is_template = ?", *args.IsTemplate)
- ct = ct.Where("?TableAlias.is_template = ?", *args.IsTemplate)
- }
- total, err := ct.Count()
- if err != nil {
- log.Error(err.Error())
- return nil, errNotFound
- }
- if args.Offset != nil {
- q = q.Offset(int(*args.Offset))
- }
- if args.Limit != nil {
- q = q.Limit(int(*args.Limit))
- }
- var numDays int
- if args.ArrivalTime != nil && args.DepartureTime != nil {
- if args.ArrivalDate != nil && args.DepartureDate != nil {
- arrivalDate := args.ArrivalDate.Time
- departureDate := args.DepartureDate.Time
- numDays = int(departureDate.Sub(arrivalDate)/(24*time.Hour)) + 1
- }
- arrivalTime := args.ArrivalTime.Time
- departureTime := args.DepartureTime.Time
- // If arrival time is between 00:01:00 to 04:00:00...
- if (arrivalTime.Hour() == 0 && arrivalTime.Minute() > 0) ||
- (arrivalTime.Hour() > 0 && arrivalTime.Hour() <= 4) {
- numDays++
- }
- if (departureTime.Hour() == 0 && departureTime.Minute() > 0) ||
- (departureTime.Hour() > 0 && departureTime.Hour() <= 4) {
- numDays--
- }
- q = q.Having("COUNT(itinerary_day.*) = ?", numDays)
- }
- var itineraryIDs []int64
- if err := q.Select(&itineraryIDs); err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errNotFound
- }
- if args.Tags != nil {
- tags := *args.Tags
- if len(tags) > 0 {
- itineraryIDs, err = r.filterTags(ctx, itineraryIDs, tags)
- if err != nil {
- log.Error(err.Error())
- }
- }
- }
- intersectedIDs, err := r.intersectIDs(ctx, struct {
- itineraryIDs []int64
- arrivalAirportCode *graphql.ID
- departureAirportCode *graphql.ID
- }{
- itineraryIDs: itineraryIDs,
- arrivalAirportCode: args.ArrivalAirportCode,
- departureAirportCode: args.DepartureAirportCode,
- })
- if err != nil {
- return nil, errors.Wrap(err, "could not get intersectIDs")
- }
- var itineraries []Itinerary
- if intersectedIDs != nil {
- err = r.DB.
- WithContext(ctx).
- Model(&itineraries).
- Column(itineraryColumns...).
- Where("?TableAlias.id IN (?)", pg.In(intersectedIDs)).
- Order("code").
- Select()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errNotFound
- }
- resp, err := r.getAvailableExperiencesMap(itineraryIDs...)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- for i := range itineraries {
- for _, rs := range resp {
- if itineraries[i].ID == rs.ItineraryID {
- itineraries[i].AvailableExperiences =
- append(itineraries[i].AvailableExperiences, rs)
- }
- }
- }
- }
- // TODO: Future debug code
- // if args.ArrivalDate != nil && args.ArrivalTime != nil &&
- // args.DepartureDate != nil && args.DepartureTime != nil {
- // for _, itinerary := range itineraries {
- // fmt.Printf("<- %s (%d days)\n", itinerary.Code, numDays)
- // }
- // }
- result := &itinerariesResolver{
- total: int32(total),
- itineraries: itineraries,
- }
- return result, nil
- }
- // RemoveDaysFromItinerary removes days from an itinerary.
- func (r *Resolver) RemoveDaysFromItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- DayIDs []*graphql.ID
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- var itinerary Itinerary
- err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- err = tx.
- Model(&itinerary).
- Column("itinerary.*", "ItineraryDays").
- Where("id = ?", id).
- Select()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err)
- }
- return errNotFound
- }
- var dayIDs []int64
- for _, s := range args.DayIDs {
- if s == nil {
- continue
- }
- did, err := decodeID(*s)
- if err != nil {
- log.Error(err)
- return err
- }
- dayIDs = append(dayIDs, did)
- }
- if len(dayIDs) > 0 {
- var itineraryDayIDs []int64
- loop:
- for _, did := range dayIDs {
- for _, itd := range itinerary.ItineraryDays {
- if itd.DayID == did {
- itineraryDayIDs = append(itineraryDayIDs, itd.ID)
- continue loop
- }
- }
- }
- _, err = tx.
- Model(&ItineraryDay{}).
- Where("itinerary_id = ?", id).
- Where("id IN (?)", pg.In(itineraryDayIDs)).
- Delete()
- if err != nil {
- log.Error(err)
- return err
- }
- }
- _, err = tx.Exec(`UPDATE trip_schema.itinerary_days as I
- SET order_index = days.seqnum - 1
- FROM (
- SELECT itinerary_days.deleted_at,
- itinerary_days.itinerary_id,
- itinerary_days.id,
- row_number() over () as seqnum
- FROM trip_schema.itinerary_days as itinerary_days
- WHERE itinerary_days.itinerary_id = ?
- AND itinerary_days.deleted_at IS NULL
- ORDER BY order_index
- ) as days
- WHERE I.itinerary_id = ?
- AND I.id = days.id
- AND I.deleted_at IS NULL`, id, id)
- if err != nil {
- return errors.Wrapf(err, "could not set order_index in itinerary days of itinerary [%d]", id)
- }
- return nil
- })
- if err == errNotFound {
- return nil, errNotFound
- }
- if err != nil {
- return nil, errors.Wrap(err, "could not remove days from itinerary")
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, err
- }
- // RemoveDayAlternativesFromItinerary removes alternative days from an
- // itinerary.
- func (r *Resolver) RemoveDayAlternativesFromItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- DayID graphql.ID
- AlternativeIDs []*graphql.ID
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- did, err := decodeID(args.DayID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- var itineraryDay ItineraryDay
- err = r.DB.
- WithContext(ctx).
- Model(&itineraryDay).
- Where("itinerary_id = ?", id).
- Where("day_id = ?", did).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, fmt.Errorf("day %d not found for itinerary %d", did, id)
- }
- var alternativeIDs []int64
- for _, s := range args.AlternativeIDs {
- if s == nil {
- continue
- }
- aid, err := decodeID(*s)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- alternativeIDs = append(alternativeIDs, aid)
- }
- if len(alternativeIDs) > 0 {
- _, err = r.DB.
- WithContext(ctx).
- Model(&ItineraryDayAlternative{}).
- Where("itinerary_day_id = ?", itineraryDay.ID).
- Where("day_id IN (?)", pg.In(alternativeIDs)).
- Delete()
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, err
- }
- // RemoveTagsFromItinerary removes tags from an itinerary.
- func (r *Resolver) RemoveTagsFromItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- Tags []*string
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- var tags []string
- for _, t := range args.Tags {
- if t == nil {
- continue
- }
- tags = append(tags, strings.ToLower(*t))
- }
- _, err = r.DB.
- WithContext(ctx).
- Model(&ItineraryTag{}).
- Where("itinerary_id = ?", id).
- Where("LOWER(tag) IN (?)", pg.In(tags)).
- Delete()
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, errors.Wrap(err, "could no get itinerary")
- }
- // ReorderDaysForItinerary reorders days within an itinerary.
- func (r *Resolver) ReorderDaysForItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- DayIDs []*graphql.ID
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- count, err := r.DB.
- WithContext(ctx).
- Model(&Itinerary{}).
- Where("id = ?", id).
- Count()
- if err != nil {
- log.Error(err.Error())
- return nil, errNotFound
- }
- if count == 0 {
- err = fmt.Errorf("unknown itinerary: %d", id)
- log.Error(err.Error())
- return nil, err
- }
- var days []ItineraryDay
- for index, s := range args.DayIDs {
- if s == nil {
- continue
- }
- did, err := decodeID(*s)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- days = append(days, ItineraryDay{
- ItineraryID: id,
- DayID: did,
- Index: int32(index),
- })
- }
- err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- for _, day := range days {
- _, err = tx.
- Model(&ItineraryDay{}).
- Set("order_index = ?", day.Index).
- Where("itinerary_id = ?", day.ItineraryID).
- Where("day_id = ?", day.DayID).
- Update()
- if err != nil {
- return err
- }
- }
- return nil
- })
- if err != nil {
- displayErr := fmt.Errorf("could not reorder days for itinerary %d", id)
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, err
- }
- // SwapDayForItinerary swaps out an old day for a new day within an itinerary.
- // The new day must be an alternative of the old day.
- func (r *Resolver) SwapDayForItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- OldDayID graphql.ID
- NewDayID graphql.ID
- TripID *graphql.ID
- },
- ) (*itineraryResolver, error) {
- claims := libauth.ClaimsFromContext(ctx)
- if claims == nil {
- return nil, libauth.ErrPermissionDenied
- }
- id, err := decodeID(args.ID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- columns := []string{
- "itinerary.*",
- "ItineraryDays",
- "ItineraryDays.Alternatives",
- "ItineraryDays.Day",
- }
- var itinerary Itinerary
- err = r.DB.
- WithContext(ctx).
- Model(&itinerary).
- Column(columns...).
- Where("?TableAlias.id = ?", id).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, fmt.Errorf("unknown itinerary: %d", id)
- }
- var trip Trip
- if args.TripID == nil {
- if !claims.WriteAnyContent {
- return nil, libauth.ErrPermissionDenied
- }
- } else { // Verify trip ownership by calling user
- tripID, err := decodeID(*args.TripID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- err = r.DB.
- WithContext(ctx).
- Model(&trip).
- Column("trip.*", "Country", "Invoice", "Invoice.Price").
- Where("?TableAlias.id = ?", tripID).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, fmt.Errorf("unknown trip: %d", tripID)
- }
- userID, err := decodeID(graphql.ID(claims.Subject))
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- if !claims.WriteAnyContent && userID != trip.UserID {
- return nil, libauth.ErrPermissionDenied
- }
- }
- oldDayID, err := decodeID(args.OldDayID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- newDayID, err := decodeID(args.NewDayID)
- if err != nil {
- log.Error(err.Error())
- return nil, err
- }
- found := false
- loop:
- for _, itineraryDay := range itinerary.ItineraryDays {
- if itineraryDay.DayID == oldDayID {
- for _, alt := range itineraryDay.Alternatives {
- if alt.DayID == newDayID {
- found = true
- break loop
- }
- }
- if !found {
- err = fmt.Errorf("day %d is not an alternative for %d", newDayID, oldDayID)
- return nil, err
- }
- }
- }
- if !found {
- err = fmt.Errorf("itinerary %d does not include day %d", id, oldDayID)
- return nil, err
- }
- err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- if itinerary.IsTemplate {
- copyID, err := copyItinerary(tx, args.ID, args.TripID, false, nil, nil)
- if err != nil {
- return err
- }
- if copyID == nil {
- return errors.New("expected copied itinerary ID")
- }
- if id, err = decodeID(*copyID); err != nil {
- return err
- }
- }
- var itineraryDay ItineraryDay
- err = tx.
- Model(&itineraryDay).
- Where("itinerary_id = ?", id).
- Where("day_id = ?", oldDayID).
- First()
- if err != nil {
- return err
- }
- _, err = tx.
- Model(&ItineraryDayAlternative{}).
- Set("day_id = ?", oldDayID).
- Where("itinerary_day_id = ?", itineraryDay.ID).
- Where("day_id = ?", newDayID).
- Update()
- if err != nil {
- return err
- }
- _, err = tx.
- Model(&ItineraryDay{}).
- Set("day_id = ?", newDayID).
- Where("itinerary_id = ?", id).
- Where("day_id = ?", oldDayID).
- Update()
- if err != nil {
- return err
- }
- var driver1ID *graphql.ID
- if trip.Driver1ID != nil {
- d1id := encodeID(*trip.Driver1ID)
- driver1ID = &d1id
- }
- var driver2ID *graphql.ID
- if trip.Driver2ID != nil {
- d2id := encodeID(*trip.Driver2ID)
- driver2ID = &d2id
- }
- if trip.ID > 0 {
- err = updateUnbookedTrip(
- tx,
- encodeID(trip.ID),
- &tripInput{
- UserID: encodeID(trip.UserID),
- CountryCode: graphql.ID(trip.Country.Code),
- ItineraryID: encodeID(id),
- StartDateTime: trip.StartDateTime,
- EndDateTime: trip.EndDateTime,
- Currency: trip.Invoice.Price.Currency,
- NumAdults: trip.NumAdults,
- NumChildren: &trip.NumChildren,
- Occasion: trip.Occasion,
- Notes: trip.Notes,
- InternalNotes: trip.InternalNotes,
- Phone: trip.Phone,
- Driver1ID: driver1ID,
- Driver2ID: driver2ID,
- },
- r.Converter,
- )
- if err != nil {
- return err
- }
- }
- return nil
- })
- if err != nil {
- displayErr := fmt.Errorf("could not swap days for itinerary %d", id)
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- encodeID(id),
- },
- )
- return result, err
- }
- // UpdateItinerary updates an itinerary.
- func (r *Resolver) UpdateItinerary(
- ctx context.Context,
- args struct {
- ID graphql.ID
- Itinerary *itineraryInput
- },
- ) (*itineraryResolver, error) {
- if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
- return nil, libauth.ErrPermissionDenied
- }
- err := r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
- return updateItinerary(tx, args.ID, args.Itinerary, r.Converter)
- })
- if err != nil {
- displayErr := fmt.Errorf("could not update itinerary %s", string(args.ID))
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- result, err := r.Itinerary(
- ctx,
- struct{ ID graphql.ID }{
- args.ID,
- },
- )
- return result, err
- }
- // intersectIDs fetches a set of possible itinerary matches and filters them
- // further within code.
- func (r *Resolver) intersectIDs(
- ctx context.Context,
- args struct {
- itineraryIDs []int64
- arrivalAirportCode *graphql.ID
- departureAirportCode *graphql.ID
- }) ([]int64, error) {
- if len(args.itineraryIDs) == 0 {
- return args.itineraryIDs, nil
- }
- if args.arrivalAirportCode == nil && args.departureAirportCode == nil {
- return args.itineraryIDs, nil
- }
- idList := pg.In(args.itineraryIDs)
- params := []interface{}{
- idList,
- idList,
- idList,
- idList,
- idList,
- }
- var where []string
- var arrivalAirport Airport
- if args.arrivalAirportCode != nil {
- code := formatCode(string(*args.arrivalAirportCode))
- err := r.DB.
- WithContext(ctx).
- Model(&arrivalAirport).
- Where("UPPER(?TableAlias.code) = ?", code).
- Select()
- if err != nil {
- displayErr := fmt.Errorf(`could not find arrival airport "%s"`, code)
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- where = append(where, "AND a_start.code = ?")
- params = append(params, arrivalAirport.Code)
- }
- var departureAirport Airport
- if args.departureAirportCode != nil {
- code := formatCode(string(*args.departureAirportCode))
- err := r.DB.
- WithContext(ctx).
- Model(&departureAirport).
- Where("UPPER(?TableAlias.code) = ?", code).
- Select()
- if err != nil {
- displayErr := fmt.Errorf(`could not find departure airport "%s"`, code)
- log.Errorf("%s: %s", displayErr.Error(), err.Error())
- return nil, displayErr
- }
- where = append(where, "AND a_end.code = ?")
- params = append(params, departureAirport.Code)
- }
- sql := fmt.Sprintf(intersectIDsSQL, strings.Join(where, " "))
- var ids []int64
- if _, err := r.DB.WithContext(ctx).Query(&ids, sql, params...); err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, err
- }
- return ids, nil
- }
- // filterTags filters a set of itinerary IDs by matching tags.
- func (r *Resolver) filterTags(
- ctx context.Context,
- itineraryIDs []int64,
- tags []*string) ([]int64, error) {
- var tagList []string
- m := make(map[string]bool)
- for _, t := range tags {
- if t != nil {
- m[strings.ToLower(*t)] = true
- }
- }
- for t := range m {
- tagList = append(tagList, t)
- }
- if len(tagList) == 0 {
- return itineraryIDs, nil
- }
- var t []ItineraryTag
- err := r.DB.
- WithContext(ctx).
- Model(&t).
- Column("itinerary_tag.itinerary_id").
- Where("itinerary_id IN (?)", pg.In(itineraryIDs)).
- Where("LOWER(tag) IN (?)", pg.In(tagList)).
- Group("itinerary_tag.itinerary_id").
- Having("COUNT(tag) = ?", len(tagList)).
- Select()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, err
- }
- var result []int64
- for _, tag := range t {
- result = append(result, tag.ItineraryID)
- }
- return result, nil
- }
- // Total returns the total number of itineraries returned.
- func (r *itinerariesResolver) Total() int32 {
- return r.total
- }
- // Items returns the itineraries in the itineraries response.
- func (r *itinerariesResolver) Items() []*itineraryResolver {
- var result []*itineraryResolver
- for _, i := range r.itineraries {
- result = append(result, &itineraryResolver{
- itinerary: i,
- baseSVGURL: r.baseSVGURL,
- })
- }
- return result
- }
- // ID returns the itinerary ID.
- func (r *itineraryResolver) ID() graphql.ID {
- return encodeID(r.itinerary.ID)
- }
- // AddDayDescription returns the description for what users would see if they
- // extended their itinerary by one day. It's a kind of up sell. It's not used.
- func (r *itineraryResolver) AddDayDescription() *string {
- return r.itinerary.AddDayDescription
- }
- // ArrivalTimes returns the itinerary's arrival times, the set of times within
- // which the itinerary applies (for example, if an itinerary applies to all
- // arrivals between 9am and 1pm).
- func (r *itineraryResolver) ArrivalTimes() *timeRangeResolver {
- timeRange := TimeRange{
- StartTime: r.itinerary.ArrivalStartTime,
- EndTime: r.itinerary.ArrivalEndTime,
- }
- return &timeRangeResolver{&timeRange}
- }
- // AvailableDates returns the itinerary's available dates, the dates within
- // which an itinerary applies. These are almost always Jan 1 to Dec 31, but
- // there may be a need for an itinerary that's available for a short time.
- func (r *itineraryResolver) AvailableDates() *dateRangeResolver {
- dateRange := DateRange{
- StartDate: r.itinerary.AvailableStartDate,
- EndDate: r.itinerary.AvailableEndDate,
- }
- return &dateRangeResolver{&dateRange}
- }
- // AvailableExperiences returns the available experiences. See the Available
- // Experiences type for more information.
- func (r *itineraryResolver) AvailableExperiences() []*availableExperienceResolver {
- var result []*availableExperienceResolver
- for _, e := range r.itinerary.AvailableExperiences {
- result = append(result, &availableExperienceResolver{*e})
- }
- return result
- }
- // Code returns the itinerary code.
- func (r *itineraryResolver) Code() string {
- return r.itinerary.Code
- }
- // Country returns the itinerary's country.
- func (r *itineraryResolver) Country() *countryResolver {
- return &countryResolver{r.itinerary.Country}
- }
- // Days returns the itinerary's days.
- func (r *itineraryResolver) Days() []*itineraryDayResolver {
- days := r.itinerary.ItineraryDays
- sort.Slice(days, func(i, j int) bool {
- return days[i].Index < days[j].Index
- })
- var result []*itineraryDayResolver
- for _, d := range days {
- result = append(result, &itineraryDayResolver{
- itineraryDay: *d,
- baseSVGURL: r.baseSVGURL,
- })
- }
- return result
- }
- // DepartureTimes returns the itinerary's departure times. See ArrivalTimes for
- // an explanation of a similar type.
- func (r *itineraryResolver) DepartureTimes() *timeRangeResolver {
- timeRange := TimeRange{
- StartTime: r.itinerary.DepartureStartTime,
- EndTime: r.itinerary.DepartureEndTime,
- }
- return &timeRangeResolver{&timeRange}
- }
- // DesktopMapImage returns the itinerary's desktop map image.
- func (r *itineraryResolver) DesktopMapImage() *imageResolver {
- if r.itinerary.DesktopMapImage == nil {
- return nil
- }
- return &imageResolver{*r.itinerary.DesktopMapImage}
- }
- // Description returns the itinerary's description text.
- func (r *itineraryResolver) Description() *string {
- return r.itinerary.Description
- }
- // IsTemplate returns whether or not an itinerary is a template.
- func (r *itineraryResolver) IsTemplate() bool {
- return r.itinerary.IsTemplate
- }
- // SubtractDayDescription returns the description for what users would see if
- // they reduce their itinerary by one day. It's not used.
- func (r *itineraryResolver) SubtractDayDescription() *string {
- return r.itinerary.SubtractDayDescription
- }
- // MobileMapImage returns the itinerary's mobile map image.
- func (r *itineraryResolver) MobileMapImage() *imageResolver {
- if r.itinerary.MobileMapImage == nil {
- return nil
- }
- return &imageResolver{*r.itinerary.MobileMapImage}
- }
- // Name returns the itinerary's name.
- func (r *itineraryResolver) Name() *string {
- return r.itinerary.Name
- }
- // PricingDescription returns the description of the itinerary's pricing.
- func (r *itineraryResolver) PricingDescription() *string {
- return r.itinerary.PricingDescription
- }
- // StyleDescription returns the description of the itinerary's stays. This
- // was originally for describing the style (pace), but has been co-opted for
- // this new use.
- func (r *itineraryResolver) StyleDescription() *string {
- return r.itinerary.StyleDescription
- }
- // Summary returns the itinerary's summary text.
- func (r *itineraryResolver) Summary() *string {
- return r.itinerary.Summary
- }
- // Tags returns the itinerary's tags.
- func (r *itineraryResolver) Tags() *[]*string {
- var result []*string
- for _, v := range r.itinerary.Tags {
- s := v.Tag
- result = append(result, &s)
- }
- return &result
- }
- // TransportationDescription returns the description of the itinerary's
- // transportation. This isn't used.
- func (r *itineraryResolver) TransportationDescription() *string {
- return r.itinerary.TransportationDescription
- }
- // TransportationSummary returns the summary of the itinerary's transportation.
- // This isn't used.
- func (r *itineraryResolver) TransportationSummary() *string {
- return r.itinerary.TransportationSummary
- }
- // createItinerary creates an itinerary.
- func createItinerary(tx *pg.Tx, itinerary *itineraryInput) (*graphql.ID, error) {
- countryCode := formatCode(string(itinerary.CountryCode))
- var country model.Country
- err := tx.
- Model(&country).
- Where("code = ?", countryCode).
- First()
- if err != nil {
- return nil, fmt.Errorf("unknown country: %s", countryCode)
- }
- if itinerary.DesktopMapImage != nil {
- id, err := createImage(tx, itinerary.DesktopMapImage)
- if err != nil {
- return nil, err
- }
- itinerary.DesktopMapImageID = id
- }
- var desktopMapImageID *int64
- if itinerary.DesktopMapImageID != nil {
- id, err := decodeID(*itinerary.DesktopMapImageID)
- if err != nil {
- return nil, err
- }
- desktopMapImageID = &id
- }
- if itinerary.MobileMapImage != nil {
- id, err := createImage(tx, itinerary.MobileMapImage)
- if err != nil {
- return nil, err
- }
- itinerary.MobileMapImageID = id
- }
- var mobileMapImageID *int64
- if itinerary.MobileMapImageID != nil {
- id, err := decodeID(*itinerary.MobileMapImageID)
- if err != nil {
- return nil, err
- }
- mobileMapImageID = &id
- }
- var (
- addDayDescription *string
- isTemplate bool
- SubtractDayDescription *string
- name *string
- pricingDetails *string
- styleDescription *string
- summary *string
- transportationDescription *string
- transportationSummary *string
- )
- if itinerary.AddDayDescription != nil && len(*itinerary.AddDayDescription) > 0 {
- addDayDescription = itinerary.AddDayDescription
- }
- if itinerary.IsTemplate != nil {
- isTemplate = *itinerary.IsTemplate
- }
- if itinerary.SubtractDayDescription != nil && len(*itinerary.SubtractDayDescription) > 0 {
- SubtractDayDescription = itinerary.SubtractDayDescription
- }
- if itinerary.Name != nil && len(*itinerary.Name) > 0 {
- name = itinerary.Name
- }
- if itinerary.PricingDescription != nil && len(*itinerary.PricingDescription) > 0 {
- pricingDetails = itinerary.PricingDescription
- }
- if itinerary.Summary != nil && len(*itinerary.Summary) > 0 {
- summary = itinerary.Summary
- }
- if itinerary.TransportationDescription != nil && len(*itinerary.TransportationDescription) > 0 {
- transportationDescription = itinerary.TransportationDescription
- }
- if itinerary.TransportationSummary != nil && len(*itinerary.TransportationSummary) > 0 {
- transportationSummary = itinerary.TransportationSummary
- }
- if itinerary.StyleDescription != nil && len(*itinerary.StyleDescription) > 0 {
- styleDescription = itinerary.StyleDescription
- }
- i := Itinerary{
- AddDayDescription: addDayDescription,
- ArrivalEndTime: itinerary.ArrivalTimes.EndTime,
- ArrivalStartTime: itinerary.ArrivalTimes.StartTime,
- AvailableEndDate: itinerary.AvailableDates.EndDate,
- AvailableStartDate: itinerary.AvailableDates.StartDate,
- Code: itinerary.Code,
- CountryID: country.ID,
- DepartureEndTime: itinerary.DepartureTimes.EndTime,
- DepartureStartTime: itinerary.DepartureTimes.StartTime,
- Description: itinerary.Description,
- DesktopMapImageID: desktopMapImageID,
- IsTemplate: isTemplate,
- MobileMapImageID: mobileMapImageID,
- Name: name,
- PricingDescription: pricingDetails,
- StyleDescription: styleDescription,
- SubtractDayDescription: SubtractDayDescription,
- Summary: summary,
- TransportationDescription: transportationDescription,
- TransportationSummary: transportationSummary,
- CreatedAt: time.Now(),
- UpdatedAt: time.Now(),
- }
- if err := tx.Insert(&i); err != nil {
- return nil, err
- }
- id := encodeID(i.ID)
- return &id, nil
- }
- // copyItinerary copies an itinerary, its days, and all its assets.
- func copyItinerary(
- tx *pg.Tx,
- id graphql.ID,
- tripID *graphql.ID,
- deepCopy bool,
- suffix *string,
- dayIDs []*graphql.ID,
- ) (*graphql.ID, error) {
- var itinerary Itinerary
- err := tx.
- Model(&itinerary).
- Where("id = ?", id).
- First()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err)
- }
- return nil, errNotFound
- }
- if itinerary.DesktopMapImageID != nil {
- var image model.Image
- err = tx.
- Model(&image).
- Where("id = ?", itinerary.DesktopMapImageID).
- Select()
- if err != nil {
- return nil, errors.Wrapf(err, "could not select itinerary desktop map image [%d]",
- itinerary.DesktopMapImageID)
- }
- image.ID = 0
- image.CreatedAt = time.Now()
- image.UpdatedAt = time.Now()
- if err := tx.Insert(&image); err != nil {
- return nil, errors.Wrap(err, "could not insert copy of desktop map image")
- }
- itinerary.DesktopMapImageID = &image.ID
- }
- if itinerary.MobileMapImageID != nil {
- var image model.Image
- err = tx.
- Model(&image).
- Where("id = ?", itinerary.MobileMapImageID).
- Select()
- if err != nil {
- return nil, errors.Wrapf(err, "could not select mobile map image [%d]", itinerary.MobileMapImageID)
- }
- image.ID = 0
- image.CreatedAt = time.Now()
- image.UpdatedAt = time.Now()
- if err := tx.Insert(&image); err != nil {
- return nil, errors.Wrap(err, "could not insert copy of mobile map image")
- }
- itinerary.MobileMapImageID = &image.ID
- }
- itinerary.ID = 0
- itinerary.IsTemplate = false
- itinerary.CreatedAt = time.Now()
- itinerary.UpdatedAt = time.Now()
- if suffix != nil && len(*suffix) > 0 {
- itinerary.Code += *suffix
- } else if tripID != nil {
- itinerary.Code += "_" + string(*tripID)
- } else {
- itinerary.Code += "_COPY"
- }
- if err := tx.Insert(&itinerary); err != nil {
- return nil, errors.Wrap(err, "could not insert copy of itinerary into db")
- }
- var itineraryDays []ItineraryDay
- err = tx.
- Model(&itineraryDays).
- Where("itinerary_id = ?", id).
- Select()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errNotFound
- }
- idMap := make(map[int64]int64) // Old => New
- didMap := make(map[int64]int64)
- var itineraryDayIDs []int64
- var deepCopyDayIDs []int64
- for _, itineraryDay := range itineraryDays {
- idid := itineraryDay.ID
- itineraryDayIDs = append(itineraryDayIDs, idid)
- did := itineraryDay.DayID
- // create new itineraryDay
- itineraryDay.ID = 0
- itineraryDay.ItineraryID = itinerary.ID
- itineraryDay.CreatedAt = time.Now()
- itineraryDay.UpdatedAt = time.Now()
- var deepCopyDay bool
- for _, adid := range dayIDs {
- if adid == nil {
- continue
- }
- deepCopyDayID, err := decodeID(*adid)
- if err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not decode id [%s]", *adid)
- }
- if deepCopyDayID == did {
- deepCopyDay = true
- break
- }
- }
- if deepCopy && deepCopyDay {
- // take duplicate days into account
- updatedSuffix := new(string)
- if suffix != nil && didMap[did] > 0 {
- *updatedSuffix = fmt.Sprintf("%s%s", *suffix, strconv.FormatInt(didMap[did], 10))
- } else {
- updatedSuffix = suffix
- }
- dayID, err := copyDay(tx, encodeID(did), updatedSuffix)
- if err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not copy day [%d]", did)
- }
- id, err := decodeID(*dayID)
- if err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not decode id [%s]", *dayID)
- }
- itineraryDay.DayID = id
- }
- if err := tx.Insert(&itineraryDay); err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not insert copy of itinerary day [%d]", itineraryDay.ID)
- }
- if deepCopy && deepCopyDay {
- deepCopyDayIDs = append(deepCopyDayIDs, itineraryDay.ID)
- }
- idMap[idid] = itineraryDay.ID
- didMap[did]++
- }
- var itineraryTags []ItineraryTag
- err = tx.
- Model(&itineraryTags).
- Where("itinerary_id = ?", id).
- Select()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errNotFound
- }
- for _, itineraryTag := range itineraryTags {
- itineraryTag.ID = 0
- itineraryTag.ItineraryID = itinerary.ID
- itineraryTag.CreatedAt = time.Now()
- itineraryTag.UpdatedAt = time.Now()
- iTag := itineraryTag
- if err := tx.Insert(&iTag); err != nil {
- log.Error(err.Error())
- return nil, errors.Wrap(err, "could not insert itinerary tag")
- }
- }
- var alternatives []ItineraryDayAlternative
- if len(itineraryDayIDs) > 0 {
- err = tx.
- Model(&alternatives).
- Where("itinerary_day_id IN (?)", pg.In(itineraryDayIDs)).
- Select()
- if err != nil {
- if err != pg.ErrNoRows {
- log.Error(err.Error())
- }
- return nil, errors.Wrap(err, "could not select alternatives day")
- }
- }
- altDayIDsNum := make(map[int64]int64)
- for _, alternative := range alternatives {
- alternative.ID = 0
- alternative.ItineraryDayID = idMap[alternative.ItineraryDayID]
- alternative.CreatedAt = time.Now()
- alternative.UpdatedAt = time.Now()
- var deepCopyDay bool
- for _, day := range deepCopyDayIDs {
- if day == alternative.ItineraryDayID {
- deepCopyDay = true
- break
- }
- }
- if deepCopy && deepCopyDay {
- updatedSuffix := new(string)
- if suffix != nil && altDayIDsNum[alternative.DayID] > 0 {
- *updatedSuffix = fmt.Sprintf("%s%s", *suffix,
- strconv.FormatInt(didMap[alternative.DayID], 10))
- } else {
- updatedSuffix = suffix
- }
- dayID, err := copyDay(tx, encodeID(alternative.DayID), updatedSuffix)
- if err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not copy day [%d]", alternative.DayID)
- }
- id, err := decodeID(*dayID)
- if err != nil {
- log.Error(err.Error())
- return nil, errors.Wrapf(err, "could not decode id [%s]", *dayID)
- }
- alternative.DayID = id
- }
- if err := tx.Insert(&alternative); err != nil {
- log.Error(err.Error())
- return nil, errors.Wrap(err, "could not insert copy of alternative day")
- }
- altDayIDsNum[alternative.DayID]++
- }
- itineraryID := encodeID(itinerary.ID)
- return &itineraryID, nil
- }
- // updateItinerary updates an itinerary.
- //
- // This cascades to all non-booked trips, as well.
- func updateItinerary(
- tx *pg.Tx,
- id graphql.ID,
- itinerary *itineraryInput,
- conv *libcurrency.Converter,
- ) error {
- intID, err := decodeID(id)
- if err != nil {
- return err
- }
- countryCode := formatCode(string(itinerary.CountryCode))
- var country model.Country
- err = tx.
- Model(&country).
- Where("code = ?", countryCode).
- First()
- if err != nil {
- return fmt.Errorf("unknown country: %s", countryCode)
- }
- var i Itinerary
- err = tx.
- Model(&i).
- Where("id = ?", intID).
- First()
- if err != nil {
- return err
- }
- if itinerary.DesktopMapImage != nil {
- var (
- iid *graphql.ID
- err error
- )
- if i.DesktopMapImageID == nil {
- iid, err = createImage(tx, itinerary.DesktopMapImage)
- } else {
- gqlID := encodeID(*i.DesktopMapImageID)
- iid = &gqlID
- err = updateImage(tx, *iid, itinerary.DesktopMapImage)
- }
- if err != nil {
- return err
- }
- itinerary.DesktopMapImageID = iid
- }
- var desktopMapImageID *int64
- if itinerary.DesktopMapImageID != nil {
- iid, err := decodeID(*itinerary.DesktopMapImageID)
- if err != nil {
- return err
- }
- desktopMapImageID = &iid
- }
- if itinerary.MobileMapImage != nil {
- var (
- iid *graphql.ID
- err error
- )
- if i.MobileMapImageID == nil {
- iid, err = createImage(tx, itinerary.MobileMapImage)
- } else {
- gqlID := encodeID(*i.MobileMapImageID)
- iid = &gqlID
- err = updateImage(tx, *iid, itinerary.MobileMapImage)
- }
- if err != nil {
- return err
- }
- itinerary.MobileMapImageID = iid
- }
- var mobileMapImageID *int64
- if itinerary.MobileMapImageID != nil {
- iid, err := decodeID(*itinerary.MobileMapImageID)
- if err != nil {
- return err
- }
- mobileMapImageID = &iid
- }
- var (
- addDayDescription *string
- isTemplate bool
- subtractDayDescription *string
- name *string
- pricingDescription *string
- styleDescription *string
- summary *string
- transportationDescription *string
- transportationSummary *string
- )
- if itinerary.AddDayDescription != nil {
- if len(*itinerary.AddDayDescription) > 0 {
- addDayDescription = itinerary.AddDayDescription
- }
- }
- if itinerary.IsTemplate != nil {
- isTemplate = *itinerary.IsTemplate
- }
- if itinerary.SubtractDayDescription != nil {
- if len(*itinerary.SubtractDayDescription) > 0 {
- subtractDayDescription = itinerary.SubtractDayDescription
- }
- }
- if itinerary.Name != nil {
- if len(*itinerary.Name) > 0 {
- name = itinerary.Name
- }
- }
- if itinerary.PricingDescription != nil {
- if len(*itinerary.PricingDescription) > 0 {
- pricingDescription = itinerary.PricingDescription
- }
- }
- if itinerary.StyleDescription != nil {
- if len(*itinerary.StyleDescription) > 0 {
- styleDescription = itinerary.StyleDescription
- }
- }
- if itinerary.Summary != nil {
- if len(*itinerary.Summary) > 0 {
- summary = itinerary.Summary
- }
- }
- if itinerary.TransportationDescription != nil {
- if len(*itinerary.TransportationDescription) > 0 {
- transportationDescription = itinerary.TransportationDescription
- }
- }
- if itinerary.TransportationSummary != nil {
- if len(*itinerary.TransportationSummary) > 0 {
- transportationSummary = itinerary.TransportationSummary
- }
- }
- _, err = tx.
- Model(&Itinerary{}).
- Set("add_day_description = ?", addDayDescription).
- Set("arrival_end_time = ?", itinerary.ArrivalTimes.EndTime).
- Set("arrival_start_time = ?", itinerary.ArrivalTimes.StartTime).
- Set("available_end_date = ?", itinerary.AvailableDates.EndDate).
- Set("available_start_date = ?", itinerary.AvailableDates.StartDate).
- Set("code = ?", itinerary.Code).
- Set("country_id = ?", country.ID).
- Set("departure_end_time = ?", itinerary.DepartureTimes.EndTime).
- Set("departure_start_time = ?", itinerary.DepartureTimes.StartTime).
- Set("description = ?", itinerary.Description).
- Set("desktop_map_image_id = ?", desktopMapImageID).
- Set("is_template = ?", isTemplate).
- Set("mobile_map_image_id = ?", mobileMapImageID).
- Set("name = ?", name).
- Set("pricing_description = ?", pricingDescription).
- Set("style_description = ?", styleDescription).
- Set("subtract_day_description = ?", subtractDayDescription).
- Set("summary = ?", summary).
- Set("transportation_description = ?", transportationDescription).
- Set("transportation_summary = ?", transportationSummary).
- Where("id = ?", intID).
- Update()
- if err != nil {
- return err
- }
- columns := []string{
- "trip.*",
- "ArrivalAirport",
- "Country",
- "DepartureAirport",
- "Invoice",
- "Invoice.Price",
- }
- var trips []Trip
- err = tx.
- Model(&trips).
- Column(columns...).
- Where("itinerary_id = ?", intID).
- Where("booked = ?", false).
- Select()
- if err != nil {
- return err
- }
- for _, trip := range trips {
- trip := trip
- err = updateUnbookedTrip(
- tx,
- encodeID(trip.ID),
- buildTripInputFromTrip(&trip),
- conv,
- )
- if err != nil {
- return err
- }
- }
- return nil
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement