Advertisement
illfate

Untitled

Dec 18th, 2019
150
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 63.76 KB | None | 0 0
  1. package data
  2.  
  3. import (
  4. "context"
  5. "fmt"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. "time"
  10.  
  11. "trip/data/date"
  12. "trip/data/model"
  13.  
  14. "github.com/go-pg/pg"
  15. "github.com/go-pg/pg/orm"
  16. "github.com/graph-gophers/graphql-go"
  17. "github.com/noken-travel/libauth"
  18. "github.com/noken-travel/libcurrency"
  19. "github.com/pkg/errors"
  20. log "github.com/sirupsen/logrus"
  21. )
  22.  
  23. // An itinerary is a set of days for a destination.
  24. //
  25. // It can be generic (templatized) or customized to an individual traveler,
  26. // but it is distinct from a trip, which contains user specific information
  27. // around dates, number of people, and so on.
  28. //
  29. // When users are building their trip, they start with an itinerary. If the
  30. // itinerary changes, all of their selections will be reset.
  31. type Itinerary struct {
  32. ID int64
  33. CreatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
  34. UpdatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
  35. DeletedAt time.Time `sql:",type:'timestamp without time zone'" pg:",soft_delete"`
  36. AddDayDescription *string
  37. ArrivalEndTime date.Time `sql:",type:time,notnull"`
  38. ArrivalStartTime date.Time `sql:",type:time,notnull"`
  39. AvailableExperiences []*AvailableExperience `sql:"-"`
  40. AvailableEndDate date.Date `sql:",type:date,notnull"`
  41. AvailableStartDate date.Date `sql:",type:date,notnull"`
  42. Code string `sql:",notnull"`
  43. Country model.Country
  44. CountryID int64 `sql:",notnull"`
  45. Description *string
  46. DepartureEndTime date.Time `sql:",type:time,notnull"`
  47. DepartureStartTime date.Time `sql:",type:time,notnull"`
  48. DesktopMapImageID *int64
  49. DesktopMapImage *model.Image // Owned by Itinerary; deleted when last reference is deleted
  50. IsTemplate bool `sql:",notnull"`
  51. ItineraryDays []*ItineraryDay
  52. MobileMapImageID *int64
  53. MobileMapImage *model.Image // Owned by Itinerary; deleted when last reference is deleted
  54. Name *string
  55. PricingDescription *string
  56. StyleDescription *string
  57. SubtractDayDescription *string
  58. Summary *string
  59. Tags []*ItineraryTag
  60. TransportationSummary *string
  61. TransportationDescription *string
  62. tableName struct{} `sql:"trip_schema.itineraries" pg:",discard_unknown_columns"`
  63. }
  64.  
  65. // ItineraryTag is an itinerary tag, a way of annotating an itinerary.
  66. //
  67. // Currently these tags are used in two ways:
  68. //
  69. // 1. To indicate pace, a tag of "fast" or "slow" might be applied to the trip.
  70. // A fast trip quickly moves from activity to activity, whereas a slow trip
  71. // takes a more leisurely approach.
  72. //
  73. // 2. To assign a custom itinerary to a user, a tag of "user:123" might be
  74. // applied, where 123 is the user ID.
  75. type ItineraryTag struct {
  76. ID int64
  77. CreatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
  78. UpdatedAt time.Time `sql:",type:'timestamp without time zone',default:now(),notnull"`
  79. DeletedAt time.Time `sql:",type:'timestamp without time zone'" pg:",soft_delete"`
  80. ItineraryID int64 `sql:"type:'bigint',notnull"`
  81. Tag string `sql:"type:'text',notnull"`
  82. tableName struct{} `sql:"trip_schema.itinerary_tags" pg:",discard_unknown_columns"`
  83. }
  84.  
  85. type itineraryInput struct {
  86. AddDayDescription *string
  87. ArrivalTimes timeRangeInput
  88. AvailableDates dateRangeInput
  89. Code string
  90. CountryCode graphql.ID
  91. DepartureTimes timeRangeInput
  92. Description *string
  93. DesktopMapImageID *graphql.ID
  94. DesktopMapImage *imageInput
  95. IsTemplate *bool
  96. MobileMapImageID *graphql.ID
  97. MobileMapImage *imageInput
  98. Name *string
  99. PricingDescription *string
  100. StyleDescription *string
  101. SubtractDayDescription *string
  102. Summary *string
  103. Tags *[]*string
  104. TransportationDescription *string
  105. TransportationSummary *string
  106. }
  107.  
  108. type minMaxOrderIndex struct {
  109. ItineraryID int64
  110. MinIndex int64
  111. MaxIndex int64
  112. }
  113.  
  114. type itinerariesResolver struct {
  115. total int32
  116. itineraries []Itinerary
  117. baseSVGURL string
  118. }
  119.  
  120. type itineraryResolver struct {
  121. itinerary Itinerary
  122. baseSVGURL string
  123. }
  124.  
  125. var itineraryColumns = []string{
  126. "itinerary.*",
  127. "Country",
  128. "DesktopMapImage",
  129. "ItineraryDays",
  130. "ItineraryDays.Alternatives",
  131. "ItineraryDays.Alternatives.Day",
  132. "ItineraryDays.Day.DayExperiences",
  133. "ItineraryDays.Day.DayExperiences.Experience",
  134. "ItineraryDays.Day.DayExperiences.Experience.Address",
  135. "ItineraryDays.Day.DayExperiences.Experience.Address.Country",
  136. "ItineraryDays.Day.DayExperiences.Experience.ExperienceImages",
  137. "ItineraryDays.Day.DayExperiences.Experience.ExperienceImages.Image",
  138. "ItineraryDays.Day.DayExperiences.Experience.Facts",
  139. "ItineraryDays.Day.DayExperiences.Experience.HoverImage",
  140. "ItineraryDays.Day.DayExperiences.Experience.OverviewImage",
  141. "ItineraryDays.Day.DayExperiences.Experience.Price",
  142. "ItineraryDays.Day.DayExperiences.Experience.Price.Taxes",
  143. // "ItineraryDays.Day.DayExperiences.Experience.Vendor",
  144. // "ItineraryDays.Day.DayExperiences.Experience.Vendor.Address",
  145. // "ItineraryDays.Day.DayExperiences.Experience.Vendor.Address.Country",
  146. "ItineraryDays.Day.DayExperiences.ExperiencePeriod",
  147. "ItineraryDays.Day.DayImages",
  148. "ItineraryDays.Day.DayImages.Image",
  149. "ItineraryDays.Day.DesktopMapImage",
  150. "ItineraryDays.Day.DesktopRouteMapImage",
  151. "ItineraryDays.Day.DesktopRouteMapImageAlternate",
  152. "ItineraryDays.Day.EndCity",
  153. "ItineraryDays.Day.EndCity.Country",
  154. "ItineraryDays.Day.MobileMapImage",
  155. "ItineraryDays.Day.MobileRouteMapImage",
  156. // "ItineraryDays.Day.DayRestaurants",
  157. // "ItineraryDays.Day.DayRestaurants.Restaurant",
  158. // "ItineraryDays.Day.DayRestaurants.Restaurant.CancelationFee",
  159. // "ItineraryDays.Day.DayRestaurants.Restaurant.RestaurantImages",
  160. // "ItineraryDays.Day.DayRestaurants.Restaurant.RestaurantImages.Image",
  161. // "ItineraryDays.Day.DayRestaurants.Restaurant.Vendor",
  162. // "ItineraryDays.Day.DayRestaurants.Restaurant.Vendor.Address",
  163. // "ItineraryDays.Day.DayRestaurants.Restaurant.Vendor.Address.Country",
  164. "ItineraryDays.Day.StartCity",
  165. "ItineraryDays.Day.StartCity.Country",
  166. "Tags",
  167. "MobileMapImage",
  168. }
  169.  
  170. // intersectIDsSQL is used to further refine itineraries during a search.
  171. var intersectIDsSQL = `
  172. SELECT
  173. it.id itinerary_id
  174. FROM trip_schema.itineraries it
  175. JOIN (
  176. SELECT
  177. it.id itinerary_id,
  178. d.start_city_id city_id
  179. FROM trip_schema.itineraries it
  180. JOIN (
  181. SELECT
  182. it.id itinerary_id,
  183. MIN(itd.order_index) min_index
  184. FROM trip_schema.itineraries it
  185. JOIN trip_schema.itinerary_days itd
  186. ON it.id = itd.itinerary_id
  187. AND itd.deleted_at IS NULL
  188. WHERE it.id IN (?)
  189. AND it.deleted_at IS NULL
  190. GROUP BY it.id
  191. ORDER BY it.id
  192. ) min
  193. ON it.id = min.itinerary_id
  194. JOIN trip_schema.itinerary_days itd
  195. ON it.id = itd.itinerary_id
  196. AND itd.deleted_at IS NULL
  197. JOIN trip_schema.days AS d
  198. ON itd.day_id = d.id
  199. AND itd.order_index = min.min_index
  200. AND d.deleted_at IS NULL
  201. WHERE it.id IN (?)
  202. AND it.deleted_at IS NULL
  203. GROUP BY it.id, d.start_city_id
  204. ORDER BY it.id
  205. ) start_day
  206. ON it.id = start_day.itinerary_id
  207. JOIN (
  208. SELECT
  209. it.id itinerary_id,
  210. d.end_city_id city_id
  211. FROM trip_schema.itineraries it
  212. JOIN (
  213. SELECT
  214. it.id itinerary_id,
  215. MAX(itd.order_index) max_index
  216. FROM trip_schema.itineraries it
  217. JOIN trip_schema.itinerary_days itd
  218. ON it.id = itd.itinerary_id
  219. AND itd.deleted_at IS NULL
  220. WHERE it.id IN (?)
  221. AND it.deleted_at IS NULL
  222. GROUP BY it.id
  223. ORDER BY it.id
  224. ) max
  225. ON it.id = max.itinerary_id
  226. JOIN trip_schema.itinerary_days itd
  227. ON it.id = itd.itinerary_id
  228. AND itd.deleted_at IS NULL
  229. JOIN trip_schema.days AS d
  230. ON itd.day_id = d.id
  231. AND itd.order_index = max.max_index
  232. AND d.deleted_at IS NULL
  233. WHERE it.id IN (?)
  234. AND it.deleted_at IS NULL
  235. GROUP BY it.id, d.end_city_id
  236. ORDER BY it.id
  237. ) end_day
  238. ON it.id = end_day.itinerary_id
  239. LEFT JOIN trip_schema.airports a_start
  240. ON start_day.city_id = a_start.city_id
  241. LEFT JOIN trip_schema.airports a_end
  242. ON end_day.city_id = a_end.city_id
  243. WHERE it.id IN (?)
  244. AND it.deleted_at IS NULL
  245. %s
  246. GROUP BY it.id
  247. `
  248.  
  249. // Equals checks that an itinerary is functionally equivalent to another one.
  250. // It does this by ensuring that the country and individual day IDs are
  251. // identical.
  252. func (a Itinerary) Equals(b Itinerary) bool {
  253. if a.CountryID != b.CountryID {
  254. return false
  255. }
  256.  
  257. numDays := len(a.ItineraryDays)
  258. if numDays != len(b.ItineraryDays) {
  259. return false
  260. }
  261.  
  262. aDays := a.ItineraryDays
  263. sort.Slice(aDays, func(i, j int) bool {
  264. return aDays[i].Index < aDays[j].Index
  265. })
  266.  
  267. bDays := b.ItineraryDays
  268. sort.Slice(bDays, func(i, j int) bool {
  269. return bDays[i].Index < bDays[j].Index
  270. })
  271.  
  272. for i := 0; i < numDays; i++ {
  273. if aDays[i].DayID != bDays[i].DayID {
  274. return false
  275. }
  276. }
  277.  
  278. return true
  279. }
  280.  
  281. // AddDaysToItinerary adds days to an itinerary.
  282. func (r *Resolver) AddDaysToItinerary(
  283. ctx context.Context,
  284. args struct {
  285. ID graphql.ID
  286. DayIDs []*graphql.ID
  287. },
  288. ) (*itineraryResolver, error) {
  289.  
  290. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  291. return nil, libauth.ErrPermissionDenied
  292. }
  293.  
  294. id, err := decodeID(args.ID)
  295. if err != nil {
  296. log.Error(err.Error())
  297. return nil, err
  298. }
  299.  
  300. count, err := r.DB.
  301. WithContext(ctx).
  302. Model(&Itinerary{}).
  303. Where("id = ?", id).
  304. Count()
  305. if err != nil {
  306. log.Error(err.Error())
  307. return nil, errNotFound
  308. }
  309. if count == 0 {
  310. if err != pg.ErrNoRows {
  311. log.Error(err.Error())
  312. }
  313. return nil, fmt.Errorf("unknown itinerary: %d", id)
  314. }
  315.  
  316. err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  317. var index int32
  318. err = tx.
  319. Model(&ItineraryDay{}).
  320. ColumnExpr("MAX(order_index) + 1").
  321. Where("itinerary_id = ?", id).
  322. Select(&index)
  323. if err != nil {
  324. return err
  325. }
  326.  
  327. var days []ItineraryDay
  328. for _, s := range args.DayIDs {
  329. if s == nil {
  330. continue
  331. }
  332.  
  333. did, err := decodeID(*s)
  334. if err != nil {
  335. return err
  336. }
  337.  
  338. days = append(days, ItineraryDay{
  339. ItineraryID: id,
  340. DayID: did,
  341. Index: index,
  342. })
  343.  
  344. index++
  345. }
  346.  
  347. _, err = r.DB.
  348. WithContext(ctx).
  349. Model(&days).
  350. OnConflict("DO NOTHING").
  351. Insert()
  352. if err != nil {
  353. return err
  354. }
  355.  
  356. return nil
  357. })
  358. if err != nil {
  359. displayErr := fmt.Errorf("could not add days to itinerary %d", id)
  360. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  361. return nil, displayErr
  362. }
  363.  
  364. result, err := r.Itinerary(
  365. ctx,
  366. struct{ ID graphql.ID }{
  367. args.ID,
  368. },
  369. )
  370.  
  371. return result, err
  372. }
  373.  
  374. // AddDayAlternativesToItinerary adds alternative days to an itinerary.
  375. func (r *Resolver) AddDayAlternativesToItinerary(
  376. ctx context.Context,
  377. args struct {
  378. ID graphql.ID
  379. DayID graphql.ID
  380. AlternativeIDs []*graphql.ID
  381. },
  382. ) (*itineraryResolver, error) {
  383.  
  384. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  385. return nil, libauth.ErrPermissionDenied
  386. }
  387.  
  388. id, err := decodeID(args.ID)
  389. if err != nil {
  390. log.Error(err.Error())
  391. return nil, err
  392. }
  393.  
  394. did, err := decodeID(args.DayID)
  395. if err != nil {
  396. log.Error(err.Error())
  397. return nil, err
  398. }
  399.  
  400. var itineraryDay ItineraryDay
  401. err = r.DB.
  402. WithContext(ctx).
  403. Model(&itineraryDay).
  404. Where("itinerary_id = ?", id).
  405. Where("day_id = ?", did).
  406. First()
  407. if err != nil {
  408. if err != pg.ErrNoRows {
  409. log.Error(err.Error())
  410. }
  411. return nil, fmt.Errorf("day %d not found for itinerary %d", did, id)
  412. }
  413.  
  414. var alternatives []ItineraryDayAlternative
  415. for _, s := range args.AlternativeIDs {
  416. if s == nil {
  417. continue
  418. }
  419.  
  420. aid, err := decodeID(*s)
  421. if err != nil {
  422. log.Error(err.Error())
  423. return nil, err
  424. }
  425.  
  426. alternatives = append(alternatives, ItineraryDayAlternative{
  427. ItineraryDayID: itineraryDay.ID,
  428. DayID: aid,
  429. })
  430. }
  431.  
  432. _, err = r.DB.
  433. WithContext(ctx).
  434. Model(&alternatives).
  435. OnConflict("DO NOTHING").
  436. Insert()
  437. if err != nil {
  438. log.Error(err.Error())
  439. return nil, err
  440. }
  441.  
  442. result, err := r.Itinerary(
  443. ctx,
  444. struct{ ID graphql.ID }{
  445. args.ID,
  446. },
  447. )
  448.  
  449. return result, err
  450. }
  451.  
  452. // AddTagsToItinerary adds tags to an itinerary.
  453. func (r *Resolver) AddTagsToItinerary(
  454. ctx context.Context,
  455. args struct {
  456. ID graphql.ID
  457. Tags []*string
  458. },
  459. ) (*itineraryResolver, error) {
  460. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  461. return nil, libauth.ErrPermissionDenied
  462. }
  463.  
  464. id, err := decodeID(args.ID)
  465. if err != nil {
  466. log.Error(err.Error())
  467. return nil, err
  468. }
  469.  
  470. var itineraryTags []ItineraryTag
  471. for _, t := range args.Tags {
  472. if t == nil {
  473. continue
  474. }
  475. itineraryTags = append(itineraryTags, ItineraryTag{
  476. ItineraryID: id,
  477. Tag: strings.ToLower(*t),
  478. })
  479. }
  480.  
  481. _, err = r.DB.
  482. WithContext(ctx).
  483. Model(&itineraryTags).
  484. OnConflict("DO NOTHING").
  485. Insert()
  486. if err != nil {
  487. log.Error(err.Error())
  488. return nil, err
  489. }
  490.  
  491. result, err := r.Itinerary(
  492. ctx,
  493. struct{ ID graphql.ID }{
  494. args.ID,
  495. },
  496. )
  497. return result, errors.Wrap(err, "could not get itinerary")
  498. }
  499.  
  500. // CopyItinerary copies the itinerary and creates a duplicate.
  501. func (r *Resolver) CopyItinerary(
  502. ctx context.Context,
  503. args struct {
  504. ID graphql.ID
  505. TripID *graphql.ID
  506. DeepCopy bool
  507. Suffix *string
  508. DayIDs []*graphql.ID
  509. },
  510. ) (*itineraryResolver, error) {
  511. claims := libauth.ClaimsFromContext(ctx)
  512. if claims == nil {
  513. return nil, libauth.ErrPermissionDenied
  514. }
  515.  
  516. id, err := decodeID(args.ID)
  517. if err != nil {
  518. log.Error(err.Error())
  519. return nil, errors.Wrapf(err, "could not decode itinerary id [%s]", args.ID)
  520. }
  521.  
  522. if args.TripID == nil {
  523. if !claims.WriteAnyContent {
  524. return nil, libauth.ErrPermissionDenied
  525. }
  526. } else { // Verify trip ownership by calling user
  527. var trip Trip
  528. err = r.DB.
  529. WithContext(ctx).
  530. Model(&trip).
  531. Where("id = ?", id).
  532. First()
  533. if err != nil {
  534. if err != pg.ErrNoRows {
  535. log.Error(err.Error())
  536. }
  537. return nil, errors.Wrapf(err, "could not select trip [%d]", id)
  538. }
  539.  
  540. userID, err := decodeID(graphql.ID(claims.Subject))
  541. if err != nil {
  542. log.Error(err.Error())
  543. return nil, errors.Wrapf(err, "could not decode user id for the itinerary [%s]", claims.Subject)
  544. }
  545.  
  546. if !claims.WriteAnyContent && id != trip.ItineraryID && userID != trip.UserID {
  547. return nil, libauth.ErrPermissionDenied
  548. }
  549. }
  550.  
  551. var itineraryID *graphql.ID
  552. err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  553. itineraryID, err = copyItinerary(
  554. tx,
  555. args.ID,
  556. args.TripID,
  557. args.DeepCopy,
  558. args.Suffix,
  559. args.DayIDs,
  560. )
  561. return errors.Wrapf(err, "could not copy itinerary [%d]", id)
  562. })
  563. if err != nil {
  564. log.Error(err)
  565. return nil, err
  566. }
  567.  
  568. result, err := r.Itinerary(
  569. ctx,
  570. struct{ ID graphql.ID }{
  571. ID: *itineraryID,
  572. },
  573. )
  574. if err != nil {
  575. return nil, errors.Wrapf(err, "could not get itinerary [%d]", id)
  576. }
  577.  
  578. return result, nil
  579. }
  580.  
  581. // CreateItinerary creates an itinerary.
  582. func (r *Resolver) CreateItinerary(
  583. ctx context.Context,
  584. args struct{ Itinerary *itineraryInput },
  585. ) (*itineraryResolver, error) {
  586. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  587. return nil, libauth.ErrPermissionDenied
  588. }
  589.  
  590. var itineraryID *graphql.ID
  591. err := r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  592. var err error
  593. itineraryID, err = createItinerary(tx, args.Itinerary)
  594. return err
  595. })
  596. if err != nil {
  597. displayErr := errors.New("could not create itinerary")
  598. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  599. return nil, displayErr
  600. }
  601.  
  602. result, err := r.Itinerary(
  603. ctx,
  604. struct{ ID graphql.ID }{
  605. *itineraryID,
  606. },
  607. )
  608.  
  609. return result, err
  610. }
  611.  
  612. // DeleteItinerary deletes an itinerary.
  613. func (r *Resolver) DeleteItinerary(
  614. ctx context.Context,
  615. args struct{ ID graphql.ID },
  616. ) (*graphql.ID, error) {
  617.  
  618. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  619. return nil, libauth.ErrPermissionDenied
  620. }
  621.  
  622. id, err := decodeID(args.ID)
  623. if err != nil {
  624. log.Error(err.Error())
  625. return nil, err
  626. }
  627.  
  628. var itinerary Itinerary
  629. err = r.DB.
  630. WithContext(ctx).
  631. Model(&itinerary).
  632. Where("id = ?", id).
  633. First()
  634. if err != nil {
  635. if err != pg.ErrNoRows {
  636. log.Error(err.Error())
  637. }
  638. return nil, errNotFound
  639. }
  640.  
  641. err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  642. if itinerary.DesktopMapImageID != nil {
  643. numImages, err := tx.
  644. Model(&Itinerary{}).
  645. Where("desktop_map_image_id = ?", *itinerary.DesktopMapImageID).
  646. Count()
  647. if err != nil {
  648. return err
  649. }
  650.  
  651. if numImages == 1 {
  652. err = deleteImage(tx, encodeID(*itinerary.DesktopMapImageID))
  653. if err != nil {
  654. return err
  655. }
  656. }
  657. }
  658.  
  659. if itinerary.MobileMapImageID != nil {
  660. numImages, err := tx.
  661. Model(&Itinerary{}).
  662. Where("mobile_map_image_id = ?", *itinerary.MobileMapImageID).
  663. Count()
  664. if err != nil {
  665. return err
  666. }
  667.  
  668. if numImages == 1 {
  669. err = deleteImage(tx, encodeID(*itinerary.MobileMapImageID))
  670. if err != nil {
  671. return err
  672. }
  673. }
  674. }
  675.  
  676. var itineraryDays []*ItineraryDay
  677. err = tx.
  678. Model(&itineraryDays).
  679. Where("itinerary_id = ?", id).
  680. Select()
  681. if err != nil {
  682. return err
  683. }
  684.  
  685. var itineraryDayIDs []int64
  686. for _, v := range itineraryDays {
  687. itineraryDayIDs = append(itineraryDayIDs, v.ID)
  688. }
  689.  
  690. if len(itineraryDayIDs) > 0 {
  691. _, err = tx.
  692. Model(&ItineraryDayAlternative{}).
  693. Where("itinerary_day_id IN (?)", pg.In(itineraryDayIDs)).
  694. Delete()
  695. if err != nil {
  696. return err
  697. }
  698. }
  699.  
  700. _, err = tx.
  701. Model(&ItineraryDay{}).
  702. Where("itinerary_id = ?", id).
  703. Delete()
  704. if err != nil {
  705. return err
  706. }
  707.  
  708. _, err = tx.
  709. Model(&ItineraryTag{}).
  710. Where("itinerary_id = ?", id).
  711. Delete()
  712. if err != nil {
  713. return err
  714. }
  715.  
  716. _, err = tx.
  717. Model(&Itinerary{}).
  718. Where("id = ?", id).
  719. Delete()
  720. if err != nil {
  721. return err
  722. }
  723.  
  724. return nil
  725. })
  726. if err != nil {
  727. displayErr := fmt.Errorf("could not delete itinerary %d", id)
  728. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  729. return nil, displayErr
  730. }
  731.  
  732. return &args.ID, nil
  733. }
  734.  
  735. // Itinerary returns an itinerary.
  736. func (r *Resolver) Itinerary(
  737. ctx context.Context,
  738. args struct{ ID graphql.ID },
  739. ) (*itineraryResolver, error) {
  740.  
  741. id, err := decodeID(args.ID)
  742. if err != nil {
  743. log.Error(err.Error())
  744. return nil, err
  745. }
  746.  
  747. var itinerary Itinerary
  748. err = r.DB.
  749. WithContext(ctx).
  750. Model(&itinerary).
  751. Column(itineraryColumns...).
  752. Where("?TableAlias.id = ?", id).
  753. First()
  754. if err != nil {
  755. if err != pg.ErrNoRows {
  756. log.Error(err.Error())
  757. }
  758. return nil, errNotFound
  759. }
  760.  
  761. if !itinerary.IsTemplate {
  762. claims := libauth.ClaimsFromContext(ctx)
  763. if claims == nil {
  764. return nil, libauth.ErrPermissionDenied
  765. }
  766. }
  767.  
  768. itinerary.AvailableExperiences, err = r.getAvailableExperiences(itinerary.ID)
  769. if err != nil {
  770. displayErr := errors.Wrap(err, "could not get available experiences")
  771. log.Error(displayErr)
  772. return nil, displayErr
  773. }
  774.  
  775. return &itineraryResolver{
  776. itinerary: itinerary,
  777. baseSVGURL: r.BaseSVGURL,
  778. }, nil
  779. }
  780.  
  781. // Itineraries returns itineraries based on a variety of parameters.
  782. //
  783. // If a search parameter is provided, it will search the itinerary code and
  784. // country name. You can also search by country, airport codes, dates and
  785. // times, tags, and whether the itinerary is a template or not.
  786. func (r *Resolver) Itineraries(
  787. ctx context.Context,
  788. args struct {
  789. Search *string
  790. CountryCode *graphql.ID
  791. ArrivalAirportCode *graphql.ID
  792. ArrivalDate *date.Date
  793. ArrivalTime *date.Time
  794. DepartureAirportCode *graphql.ID
  795. DepartureDate *date.Date
  796. DepartureTime *date.Time
  797. IsTemplate *bool
  798. Limit *int32
  799. Offset *int32
  800. Tags *[]*string
  801. },
  802. ) (*itinerariesResolver, error) {
  803.  
  804. q := r.DB.
  805. WithContext(ctx).
  806. Model(&Itinerary{}).
  807. Column("itinerary.id", "Country._").
  808. Join("LEFT JOIN trip_schema.itinerary_days AS itinerary_day").
  809. JoinOn("?TableAlias.id = itinerary_day.itinerary_id").
  810. Join("LEFT JOIN trip_schema.days AS d").
  811. JoinOn("itinerary_day.day_id = d.id").
  812. Where("itinerary_day.deleted_at IS NULL").
  813. Group("itinerary.id")
  814.  
  815. ct := r.DB.
  816. WithContext(ctx).
  817. Model(&Itinerary{}).
  818. Column("itinerary.*", "Country._")
  819.  
  820. if args.Search != nil {
  821. if _, ok := libauth.Can(ctx, libauth.ClaimReadAnyContent); !ok {
  822. return nil, libauth.ErrPermissionDenied
  823. }
  824.  
  825. search := fmt.Sprintf("%%%s%%", strings.ToLower(*args.Search))
  826. where := "LOWER(?TableAlias.code) LIKE ?"
  827. whereOr := `LOWER("country"."name") LIKE ?`
  828. q = q.WhereGroup(func(q *orm.Query) (*orm.Query, error) {
  829. q = q.
  830. Where(where, search).
  831. WhereOr(whereOr, search)
  832. return q, nil
  833. })
  834. ct = ct.WhereGroup(func(q *orm.Query) (*orm.Query, error) {
  835. q = q.
  836. Where(where, search).
  837. WhereOr(whereOr, search)
  838. return q, nil
  839. })
  840. }
  841.  
  842. if args.CountryCode != nil {
  843. var country model.Country
  844. err := r.DB.
  845. WithContext(ctx).
  846. Model(&country).
  847. Where("code = ?", *args.CountryCode).
  848. First()
  849. if err != nil {
  850. if err != pg.ErrNoRows {
  851. log.Error(err.Error())
  852. }
  853. return nil, fmt.Errorf("unknown country: %s", *args.CountryCode)
  854. }
  855.  
  856. where := `"country"."id" = ?`
  857. q = q.Where(where, country.ID)
  858. ct = ct.Where(where, country.ID)
  859. }
  860.  
  861. if args.ArrivalDate != nil {
  862. arrDate := *args.ArrivalDate
  863.  
  864. var arrivalDate date.Date
  865. if err := arrivalDate.Scan(time.Date(1, arrDate.Time.Month(), arrDate.Time.Day(), 0, 0, 0, 0, time.UTC)); err != nil {
  866. return nil, errors.Wrap(err, "cannot compose arrival date")
  867. }
  868.  
  869. var maxDate date.Date
  870. if err := maxDate.Scan(time.Date(1, time.December, 31, 23, 59, 59, 0, time.UTC)); err != nil {
  871. return nil, errors.Wrap(err, "cannot compose max date")
  872. }
  873.  
  874. var minDate date.Date
  875. if err := minDate.Scan(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)); err != nil {
  876. return nil, errors.Wrap(err, "cannot compose min date")
  877. }
  878.  
  879. where := `
  880. CASE
  881. WHEN ?TableAlias.available_start_date < ?TableAlias.available_end_date THEN
  882. ? BETWEEN ?TableAlias.available_start_date AND ?TableAlias.available_end_date
  883. WHEN ?TableAlias.available_start_date > ?TableAlias.available_end_date THEN
  884. ? BETWEEN ?TableAlias.available_start_date AND ?
  885. OR ? BETWEEN ? AND ?TableAlias.available_end_date
  886. END
  887. `
  888. params := []interface{}{
  889. arrivalDate,
  890. arrivalDate,
  891. maxDate,
  892. arrivalDate,
  893. minDate,
  894. arrivalDate,
  895. }
  896. q = q.Where(where, params...)
  897. ct = ct.Where(where, params...)
  898. }
  899.  
  900. var maxTime date.Time
  901. if err := maxTime.Scan(time.Date(1, time.January, 1, 23, 59, 59, 0, time.UTC)); err != nil {
  902. return nil, errors.Wrap(err, "cannot compose max time")
  903. }
  904.  
  905. var minTime date.Time
  906. if err := minTime.Scan(time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)); err != nil {
  907. return nil, errors.Wrap(err, "cannot compose min time")
  908. }
  909.  
  910. if args.ArrivalTime != nil {
  911. where := `
  912. CASE
  913. WHEN ?TableAlias.arrival_start_time < ?TableAlias.arrival_end_time THEN
  914. ? BETWEEN ?TableAlias.arrival_start_time AND ?TableAlias.arrival_end_time
  915. WHEN ?TableAlias.arrival_start_time > ?TableAlias.arrival_end_time THEN
  916. ? BETWEEN ?TableAlias.arrival_start_time AND ?
  917. OR ? BETWEEN ? AND ?TableAlias.arrival_end_time
  918. END
  919. `
  920. params := []interface{}{
  921. *args.ArrivalTime,
  922. *args.ArrivalTime,
  923. maxTime,
  924. *args.ArrivalTime,
  925. minTime,
  926. *args.ArrivalTime,
  927. }
  928. q = q.Where(where, params...)
  929. ct = ct.Where(where, params...)
  930. }
  931.  
  932. if args.DepartureTime != nil {
  933. where := `
  934. CASE
  935. WHEN ?TableAlias.departure_start_time < ?TableAlias.departure_end_time THEN
  936. ? BETWEEN ?TableAlias.departure_start_time AND ?TableAlias.departure_end_time
  937. WHEN ?TableAlias.departure_start_time > ?TableAlias.departure_end_time THEN
  938. ? BETWEEN ?TableAlias.departure_start_time AND ?
  939. OR ? BETWEEN ? AND ?TableAlias.departure_end_time
  940. END
  941. `
  942. params := []interface{}{
  943. *args.DepartureTime,
  944. *args.DepartureTime,
  945. maxTime,
  946. *args.DepartureTime,
  947. minTime,
  948. }
  949. q = q.Where(where, params...)
  950. ct = ct.Where(where, params...)
  951. }
  952.  
  953. if args.IsTemplate != nil {
  954. q = q.Where("?TableAlias.is_template = ?", *args.IsTemplate)
  955. ct = ct.Where("?TableAlias.is_template = ?", *args.IsTemplate)
  956. }
  957.  
  958. total, err := ct.Count()
  959. if err != nil {
  960. log.Error(err.Error())
  961. return nil, errNotFound
  962. }
  963.  
  964. if args.Offset != nil {
  965. q = q.Offset(int(*args.Offset))
  966. }
  967.  
  968. if args.Limit != nil {
  969. q = q.Limit(int(*args.Limit))
  970. }
  971.  
  972. var numDays int
  973. if args.ArrivalTime != nil && args.DepartureTime != nil {
  974. if args.ArrivalDate != nil && args.DepartureDate != nil {
  975. arrivalDate := args.ArrivalDate.Time
  976. departureDate := args.DepartureDate.Time
  977. numDays = int(departureDate.Sub(arrivalDate)/(24*time.Hour)) + 1
  978. }
  979.  
  980. arrivalTime := args.ArrivalTime.Time
  981. departureTime := args.DepartureTime.Time
  982.  
  983. // If arrival time is between 00:01:00 to 04:00:00...
  984. if (arrivalTime.Hour() == 0 && arrivalTime.Minute() > 0) ||
  985. (arrivalTime.Hour() > 0 && arrivalTime.Hour() <= 4) {
  986.  
  987. numDays++
  988. }
  989.  
  990. if (departureTime.Hour() == 0 && departureTime.Minute() > 0) ||
  991. (departureTime.Hour() > 0 && departureTime.Hour() <= 4) {
  992.  
  993. numDays--
  994. }
  995.  
  996. q = q.Having("COUNT(itinerary_day.*) = ?", numDays)
  997. }
  998.  
  999. var itineraryIDs []int64
  1000. if err := q.Select(&itineraryIDs); err != nil {
  1001. if err != pg.ErrNoRows {
  1002. log.Error(err.Error())
  1003. }
  1004. return nil, errNotFound
  1005. }
  1006.  
  1007. if args.Tags != nil {
  1008. tags := *args.Tags
  1009. if len(tags) > 0 {
  1010. itineraryIDs, err = r.filterTags(ctx, itineraryIDs, tags)
  1011. if err != nil {
  1012. log.Error(err.Error())
  1013. }
  1014. }
  1015. }
  1016.  
  1017. intersectedIDs, err := r.intersectIDs(ctx, struct {
  1018. itineraryIDs []int64
  1019. arrivalAirportCode *graphql.ID
  1020. departureAirportCode *graphql.ID
  1021. }{
  1022. itineraryIDs: itineraryIDs,
  1023. arrivalAirportCode: args.ArrivalAirportCode,
  1024. departureAirportCode: args.DepartureAirportCode,
  1025. })
  1026. if err != nil {
  1027. return nil, errors.Wrap(err, "could not get intersectIDs")
  1028. }
  1029.  
  1030. var itineraries []Itinerary
  1031. if intersectedIDs != nil {
  1032. err = r.DB.
  1033. WithContext(ctx).
  1034. Model(&itineraries).
  1035. Column(itineraryColumns...).
  1036. Where("?TableAlias.id IN (?)", pg.In(intersectedIDs)).
  1037. Order("code").
  1038. Select()
  1039. if err != nil {
  1040. if err != pg.ErrNoRows {
  1041. log.Error(err.Error())
  1042. }
  1043. return nil, errNotFound
  1044. }
  1045.  
  1046. resp, err := r.getAvailableExperiencesMap(itineraryIDs...)
  1047. if err != nil {
  1048. log.Error(err.Error())
  1049. return nil, err
  1050. }
  1051.  
  1052. for i := range itineraries {
  1053. for _, rs := range resp {
  1054. if itineraries[i].ID == rs.ItineraryID {
  1055. itineraries[i].AvailableExperiences =
  1056. append(itineraries[i].AvailableExperiences, rs)
  1057. }
  1058. }
  1059. }
  1060. }
  1061.  
  1062. // TODO: Future debug code
  1063. // if args.ArrivalDate != nil && args.ArrivalTime != nil &&
  1064. // args.DepartureDate != nil && args.DepartureTime != nil {
  1065.  
  1066. // for _, itinerary := range itineraries {
  1067. // fmt.Printf("<- %s (%d days)\n", itinerary.Code, numDays)
  1068. // }
  1069. // }
  1070.  
  1071. result := &itinerariesResolver{
  1072. total: int32(total),
  1073. itineraries: itineraries,
  1074. }
  1075.  
  1076. return result, nil
  1077. }
  1078.  
  1079. // RemoveDaysFromItinerary removes days from an itinerary.
  1080. func (r *Resolver) RemoveDaysFromItinerary(
  1081. ctx context.Context,
  1082. args struct {
  1083. ID graphql.ID
  1084. DayIDs []*graphql.ID
  1085. },
  1086. ) (*itineraryResolver, error) {
  1087.  
  1088. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  1089. return nil, libauth.ErrPermissionDenied
  1090. }
  1091.  
  1092. id, err := decodeID(args.ID)
  1093. if err != nil {
  1094. log.Error(err.Error())
  1095. return nil, err
  1096. }
  1097.  
  1098. var itinerary Itinerary
  1099. err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  1100. err = tx.
  1101. Model(&itinerary).
  1102. Column("itinerary.*", "ItineraryDays").
  1103. Where("id = ?", id).
  1104. Select()
  1105. if err != nil {
  1106. if err != pg.ErrNoRows {
  1107. log.Error(err)
  1108. }
  1109. return errNotFound
  1110. }
  1111.  
  1112. var dayIDs []int64
  1113. for _, s := range args.DayIDs {
  1114. if s == nil {
  1115. continue
  1116. }
  1117.  
  1118. did, err := decodeID(*s)
  1119. if err != nil {
  1120. log.Error(err)
  1121. return err
  1122. }
  1123.  
  1124. dayIDs = append(dayIDs, did)
  1125. }
  1126.  
  1127. if len(dayIDs) > 0 {
  1128. var itineraryDayIDs []int64
  1129. loop:
  1130. for _, did := range dayIDs {
  1131. for _, itd := range itinerary.ItineraryDays {
  1132. if itd.DayID == did {
  1133. itineraryDayIDs = append(itineraryDayIDs, itd.ID)
  1134. continue loop
  1135. }
  1136. }
  1137. }
  1138.  
  1139. _, err = tx.
  1140. Model(&ItineraryDay{}).
  1141. Where("itinerary_id = ?", id).
  1142. Where("id IN (?)", pg.In(itineraryDayIDs)).
  1143. Delete()
  1144. if err != nil {
  1145. log.Error(err)
  1146. return err
  1147. }
  1148. }
  1149. _, err = tx.Exec(`UPDATE trip_schema.itinerary_days as I
  1150. SET order_index = days.seqnum - 1
  1151. FROM (
  1152. SELECT itinerary_days.deleted_at,
  1153. itinerary_days.itinerary_id,
  1154. itinerary_days.id,
  1155. row_number() over () as seqnum
  1156. FROM trip_schema.itinerary_days as itinerary_days
  1157. WHERE itinerary_days.itinerary_id = ?
  1158. AND itinerary_days.deleted_at IS NULL
  1159. ORDER BY order_index
  1160. ) as days
  1161. WHERE I.itinerary_id = ?
  1162. AND I.id = days.id
  1163. AND I.deleted_at IS NULL`, id, id)
  1164. if err != nil {
  1165. return errors.Wrapf(err, "could not set order_index in itinerary days of itinerary [%d]", id)
  1166. }
  1167. return nil
  1168. })
  1169. if err == errNotFound {
  1170. return nil, errNotFound
  1171. }
  1172. if err != nil {
  1173. return nil, errors.Wrap(err, "could not remove days from itinerary")
  1174. }
  1175. result, err := r.Itinerary(
  1176. ctx,
  1177. struct{ ID graphql.ID }{
  1178. args.ID,
  1179. },
  1180. )
  1181.  
  1182. return result, err
  1183. }
  1184.  
  1185. // RemoveDayAlternativesFromItinerary removes alternative days from an
  1186. // itinerary.
  1187. func (r *Resolver) RemoveDayAlternativesFromItinerary(
  1188. ctx context.Context,
  1189. args struct {
  1190. ID graphql.ID
  1191. DayID graphql.ID
  1192. AlternativeIDs []*graphql.ID
  1193. },
  1194. ) (*itineraryResolver, error) {
  1195.  
  1196. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  1197. return nil, libauth.ErrPermissionDenied
  1198. }
  1199.  
  1200. id, err := decodeID(args.ID)
  1201. if err != nil {
  1202. log.Error(err.Error())
  1203. return nil, err
  1204. }
  1205.  
  1206. did, err := decodeID(args.DayID)
  1207. if err != nil {
  1208. log.Error(err.Error())
  1209. return nil, err
  1210. }
  1211.  
  1212. var itineraryDay ItineraryDay
  1213. err = r.DB.
  1214. WithContext(ctx).
  1215. Model(&itineraryDay).
  1216. Where("itinerary_id = ?", id).
  1217. Where("day_id = ?", did).
  1218. First()
  1219. if err != nil {
  1220. if err != pg.ErrNoRows {
  1221. log.Error(err.Error())
  1222. }
  1223. return nil, fmt.Errorf("day %d not found for itinerary %d", did, id)
  1224. }
  1225.  
  1226. var alternativeIDs []int64
  1227. for _, s := range args.AlternativeIDs {
  1228. if s == nil {
  1229. continue
  1230. }
  1231.  
  1232. aid, err := decodeID(*s)
  1233. if err != nil {
  1234. log.Error(err.Error())
  1235. return nil, err
  1236. }
  1237.  
  1238. alternativeIDs = append(alternativeIDs, aid)
  1239. }
  1240.  
  1241. if len(alternativeIDs) > 0 {
  1242. _, err = r.DB.
  1243. WithContext(ctx).
  1244. Model(&ItineraryDayAlternative{}).
  1245. Where("itinerary_day_id = ?", itineraryDay.ID).
  1246. Where("day_id IN (?)", pg.In(alternativeIDs)).
  1247. Delete()
  1248. if err != nil {
  1249. log.Error(err.Error())
  1250. return nil, err
  1251. }
  1252. }
  1253.  
  1254. result, err := r.Itinerary(
  1255. ctx,
  1256. struct{ ID graphql.ID }{
  1257. args.ID,
  1258. },
  1259. )
  1260.  
  1261. return result, err
  1262. }
  1263.  
  1264. // RemoveTagsFromItinerary removes tags from an itinerary.
  1265. func (r *Resolver) RemoveTagsFromItinerary(
  1266. ctx context.Context,
  1267. args struct {
  1268. ID graphql.ID
  1269. Tags []*string
  1270. },
  1271. ) (*itineraryResolver, error) {
  1272. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  1273. return nil, libauth.ErrPermissionDenied
  1274. }
  1275.  
  1276. id, err := decodeID(args.ID)
  1277. if err != nil {
  1278. log.Error(err.Error())
  1279. return nil, err
  1280. }
  1281.  
  1282. var tags []string
  1283. for _, t := range args.Tags {
  1284. if t == nil {
  1285. continue
  1286. }
  1287.  
  1288. tags = append(tags, strings.ToLower(*t))
  1289. }
  1290.  
  1291. _, err = r.DB.
  1292. WithContext(ctx).
  1293. Model(&ItineraryTag{}).
  1294. Where("itinerary_id = ?", id).
  1295. Where("LOWER(tag) IN (?)", pg.In(tags)).
  1296. Delete()
  1297. if err != nil {
  1298. log.Error(err.Error())
  1299. return nil, err
  1300. }
  1301.  
  1302. result, err := r.Itinerary(
  1303. ctx,
  1304. struct{ ID graphql.ID }{
  1305. args.ID,
  1306. },
  1307. )
  1308. return result, errors.Wrap(err, "could no get itinerary")
  1309. }
  1310.  
  1311. // ReorderDaysForItinerary reorders days within an itinerary.
  1312. func (r *Resolver) ReorderDaysForItinerary(
  1313. ctx context.Context,
  1314. args struct {
  1315. ID graphql.ID
  1316. DayIDs []*graphql.ID
  1317. },
  1318. ) (*itineraryResolver, error) {
  1319.  
  1320. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  1321. return nil, libauth.ErrPermissionDenied
  1322. }
  1323.  
  1324. id, err := decodeID(args.ID)
  1325. if err != nil {
  1326. log.Error(err.Error())
  1327. return nil, err
  1328. }
  1329.  
  1330. count, err := r.DB.
  1331. WithContext(ctx).
  1332. Model(&Itinerary{}).
  1333. Where("id = ?", id).
  1334. Count()
  1335. if err != nil {
  1336. log.Error(err.Error())
  1337. return nil, errNotFound
  1338. }
  1339. if count == 0 {
  1340. err = fmt.Errorf("unknown itinerary: %d", id)
  1341. log.Error(err.Error())
  1342. return nil, err
  1343. }
  1344.  
  1345. var days []ItineraryDay
  1346. for index, s := range args.DayIDs {
  1347. if s == nil {
  1348. continue
  1349. }
  1350.  
  1351. did, err := decodeID(*s)
  1352. if err != nil {
  1353. log.Error(err.Error())
  1354. return nil, err
  1355. }
  1356.  
  1357. days = append(days, ItineraryDay{
  1358. ItineraryID: id,
  1359. DayID: did,
  1360. Index: int32(index),
  1361. })
  1362. }
  1363.  
  1364. err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  1365. for _, day := range days {
  1366. _, err = tx.
  1367. Model(&ItineraryDay{}).
  1368. Set("order_index = ?", day.Index).
  1369. Where("itinerary_id = ?", day.ItineraryID).
  1370. Where("day_id = ?", day.DayID).
  1371. Update()
  1372. if err != nil {
  1373. return err
  1374. }
  1375. }
  1376.  
  1377. return nil
  1378. })
  1379. if err != nil {
  1380. displayErr := fmt.Errorf("could not reorder days for itinerary %d", id)
  1381. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  1382. return nil, displayErr
  1383. }
  1384.  
  1385. result, err := r.Itinerary(
  1386. ctx,
  1387. struct{ ID graphql.ID }{
  1388. args.ID,
  1389. },
  1390. )
  1391.  
  1392. return result, err
  1393. }
  1394.  
  1395. // SwapDayForItinerary swaps out an old day for a new day within an itinerary.
  1396. // The new day must be an alternative of the old day.
  1397. func (r *Resolver) SwapDayForItinerary(
  1398. ctx context.Context,
  1399. args struct {
  1400. ID graphql.ID
  1401. OldDayID graphql.ID
  1402. NewDayID graphql.ID
  1403. TripID *graphql.ID
  1404. },
  1405. ) (*itineraryResolver, error) {
  1406. claims := libauth.ClaimsFromContext(ctx)
  1407. if claims == nil {
  1408. return nil, libauth.ErrPermissionDenied
  1409. }
  1410.  
  1411. id, err := decodeID(args.ID)
  1412. if err != nil {
  1413. log.Error(err.Error())
  1414. return nil, err
  1415. }
  1416.  
  1417. columns := []string{
  1418. "itinerary.*",
  1419. "ItineraryDays",
  1420. "ItineraryDays.Alternatives",
  1421. "ItineraryDays.Day",
  1422. }
  1423.  
  1424. var itinerary Itinerary
  1425. err = r.DB.
  1426. WithContext(ctx).
  1427. Model(&itinerary).
  1428. Column(columns...).
  1429. Where("?TableAlias.id = ?", id).
  1430. First()
  1431. if err != nil {
  1432. if err != pg.ErrNoRows {
  1433. log.Error(err.Error())
  1434. }
  1435. return nil, fmt.Errorf("unknown itinerary: %d", id)
  1436. }
  1437.  
  1438. var trip Trip
  1439. if args.TripID == nil {
  1440. if !claims.WriteAnyContent {
  1441. return nil, libauth.ErrPermissionDenied
  1442. }
  1443. } else { // Verify trip ownership by calling user
  1444. tripID, err := decodeID(*args.TripID)
  1445. if err != nil {
  1446. log.Error(err.Error())
  1447. return nil, err
  1448. }
  1449.  
  1450. err = r.DB.
  1451. WithContext(ctx).
  1452. Model(&trip).
  1453. Column("trip.*", "Country", "Invoice", "Invoice.Price").
  1454. Where("?TableAlias.id = ?", tripID).
  1455. First()
  1456. if err != nil {
  1457. if err != pg.ErrNoRows {
  1458. log.Error(err.Error())
  1459. }
  1460. return nil, fmt.Errorf("unknown trip: %d", tripID)
  1461. }
  1462.  
  1463. userID, err := decodeID(graphql.ID(claims.Subject))
  1464. if err != nil {
  1465. log.Error(err.Error())
  1466. return nil, err
  1467. }
  1468.  
  1469. if !claims.WriteAnyContent && userID != trip.UserID {
  1470. return nil, libauth.ErrPermissionDenied
  1471. }
  1472. }
  1473.  
  1474. oldDayID, err := decodeID(args.OldDayID)
  1475. if err != nil {
  1476. log.Error(err.Error())
  1477. return nil, err
  1478. }
  1479.  
  1480. newDayID, err := decodeID(args.NewDayID)
  1481. if err != nil {
  1482. log.Error(err.Error())
  1483. return nil, err
  1484. }
  1485.  
  1486. found := false
  1487. loop:
  1488. for _, itineraryDay := range itinerary.ItineraryDays {
  1489. if itineraryDay.DayID == oldDayID {
  1490. for _, alt := range itineraryDay.Alternatives {
  1491. if alt.DayID == newDayID {
  1492. found = true
  1493. break loop
  1494. }
  1495. }
  1496. if !found {
  1497. err = fmt.Errorf("day %d is not an alternative for %d", newDayID, oldDayID)
  1498. return nil, err
  1499. }
  1500. }
  1501. }
  1502. if !found {
  1503. err = fmt.Errorf("itinerary %d does not include day %d", id, oldDayID)
  1504. return nil, err
  1505. }
  1506.  
  1507. err = r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  1508. if itinerary.IsTemplate {
  1509. copyID, err := copyItinerary(tx, args.ID, args.TripID, false, nil, nil)
  1510. if err != nil {
  1511. return err
  1512. }
  1513.  
  1514. if copyID == nil {
  1515. return errors.New("expected copied itinerary ID")
  1516. }
  1517.  
  1518. if id, err = decodeID(*copyID); err != nil {
  1519. return err
  1520. }
  1521. }
  1522.  
  1523. var itineraryDay ItineraryDay
  1524. err = tx.
  1525. Model(&itineraryDay).
  1526. Where("itinerary_id = ?", id).
  1527. Where("day_id = ?", oldDayID).
  1528. First()
  1529. if err != nil {
  1530. return err
  1531. }
  1532.  
  1533. _, err = tx.
  1534. Model(&ItineraryDayAlternative{}).
  1535. Set("day_id = ?", oldDayID).
  1536. Where("itinerary_day_id = ?", itineraryDay.ID).
  1537. Where("day_id = ?", newDayID).
  1538. Update()
  1539. if err != nil {
  1540. return err
  1541. }
  1542.  
  1543. _, err = tx.
  1544. Model(&ItineraryDay{}).
  1545. Set("day_id = ?", newDayID).
  1546. Where("itinerary_id = ?", id).
  1547. Where("day_id = ?", oldDayID).
  1548. Update()
  1549. if err != nil {
  1550. return err
  1551. }
  1552.  
  1553. var driver1ID *graphql.ID
  1554. if trip.Driver1ID != nil {
  1555. d1id := encodeID(*trip.Driver1ID)
  1556. driver1ID = &d1id
  1557. }
  1558.  
  1559. var driver2ID *graphql.ID
  1560. if trip.Driver2ID != nil {
  1561. d2id := encodeID(*trip.Driver2ID)
  1562. driver2ID = &d2id
  1563. }
  1564.  
  1565. if trip.ID > 0 {
  1566. err = updateUnbookedTrip(
  1567. tx,
  1568. encodeID(trip.ID),
  1569. &tripInput{
  1570. UserID: encodeID(trip.UserID),
  1571. CountryCode: graphql.ID(trip.Country.Code),
  1572. ItineraryID: encodeID(id),
  1573. StartDateTime: trip.StartDateTime,
  1574. EndDateTime: trip.EndDateTime,
  1575. Currency: trip.Invoice.Price.Currency,
  1576. NumAdults: trip.NumAdults,
  1577. NumChildren: &trip.NumChildren,
  1578. Occasion: trip.Occasion,
  1579. Notes: trip.Notes,
  1580. InternalNotes: trip.InternalNotes,
  1581. Phone: trip.Phone,
  1582. Driver1ID: driver1ID,
  1583. Driver2ID: driver2ID,
  1584. },
  1585. r.Converter,
  1586. )
  1587. if err != nil {
  1588. return err
  1589. }
  1590. }
  1591.  
  1592. return nil
  1593. })
  1594. if err != nil {
  1595. displayErr := fmt.Errorf("could not swap days for itinerary %d", id)
  1596. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  1597. return nil, displayErr
  1598. }
  1599.  
  1600. result, err := r.Itinerary(
  1601. ctx,
  1602. struct{ ID graphql.ID }{
  1603. encodeID(id),
  1604. },
  1605. )
  1606.  
  1607. return result, err
  1608. }
  1609.  
  1610. // UpdateItinerary updates an itinerary.
  1611. func (r *Resolver) UpdateItinerary(
  1612. ctx context.Context,
  1613. args struct {
  1614. ID graphql.ID
  1615. Itinerary *itineraryInput
  1616. },
  1617. ) (*itineraryResolver, error) {
  1618.  
  1619. if _, ok := libauth.Can(ctx, libauth.ClaimWriteAnyContent); !ok {
  1620. return nil, libauth.ErrPermissionDenied
  1621. }
  1622.  
  1623. err := r.DB.WithContext(ctx).RunInTransaction(func(tx *pg.Tx) error {
  1624. return updateItinerary(tx, args.ID, args.Itinerary, r.Converter)
  1625. })
  1626. if err != nil {
  1627. displayErr := fmt.Errorf("could not update itinerary %s", string(args.ID))
  1628. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  1629. return nil, displayErr
  1630. }
  1631.  
  1632. result, err := r.Itinerary(
  1633. ctx,
  1634. struct{ ID graphql.ID }{
  1635. args.ID,
  1636. },
  1637. )
  1638.  
  1639. return result, err
  1640. }
  1641.  
  1642. // intersectIDs fetches a set of possible itinerary matches and filters them
  1643. // further within code.
  1644. func (r *Resolver) intersectIDs(
  1645. ctx context.Context,
  1646. args struct {
  1647. itineraryIDs []int64
  1648. arrivalAirportCode *graphql.ID
  1649. departureAirportCode *graphql.ID
  1650. }) ([]int64, error) {
  1651. if len(args.itineraryIDs) == 0 {
  1652. return args.itineraryIDs, nil
  1653. }
  1654. if args.arrivalAirportCode == nil && args.departureAirportCode == nil {
  1655. return args.itineraryIDs, nil
  1656. }
  1657.  
  1658. idList := pg.In(args.itineraryIDs)
  1659.  
  1660. params := []interface{}{
  1661. idList,
  1662. idList,
  1663. idList,
  1664. idList,
  1665. idList,
  1666. }
  1667.  
  1668. var where []string
  1669.  
  1670. var arrivalAirport Airport
  1671. if args.arrivalAirportCode != nil {
  1672. code := formatCode(string(*args.arrivalAirportCode))
  1673. err := r.DB.
  1674. WithContext(ctx).
  1675. Model(&arrivalAirport).
  1676. Where("UPPER(?TableAlias.code) = ?", code).
  1677. Select()
  1678. if err != nil {
  1679. displayErr := fmt.Errorf(`could not find arrival airport "%s"`, code)
  1680. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  1681. return nil, displayErr
  1682. }
  1683.  
  1684. where = append(where, "AND a_start.code = ?")
  1685. params = append(params, arrivalAirport.Code)
  1686. }
  1687.  
  1688. var departureAirport Airport
  1689. if args.departureAirportCode != nil {
  1690. code := formatCode(string(*args.departureAirportCode))
  1691. err := r.DB.
  1692. WithContext(ctx).
  1693. Model(&departureAirport).
  1694. Where("UPPER(?TableAlias.code) = ?", code).
  1695. Select()
  1696. if err != nil {
  1697. displayErr := fmt.Errorf(`could not find departure airport "%s"`, code)
  1698. log.Errorf("%s: %s", displayErr.Error(), err.Error())
  1699. return nil, displayErr
  1700. }
  1701. where = append(where, "AND a_end.code = ?")
  1702. params = append(params, departureAirport.Code)
  1703. }
  1704.  
  1705. sql := fmt.Sprintf(intersectIDsSQL, strings.Join(where, " "))
  1706.  
  1707. var ids []int64
  1708. if _, err := r.DB.WithContext(ctx).Query(&ids, sql, params...); err != nil {
  1709. if err != pg.ErrNoRows {
  1710. log.Error(err.Error())
  1711. }
  1712. return nil, err
  1713. }
  1714.  
  1715. return ids, nil
  1716. }
  1717.  
  1718. // filterTags filters a set of itinerary IDs by matching tags.
  1719. func (r *Resolver) filterTags(
  1720. ctx context.Context,
  1721. itineraryIDs []int64,
  1722. tags []*string) ([]int64, error) {
  1723. var tagList []string
  1724. m := make(map[string]bool)
  1725.  
  1726. for _, t := range tags {
  1727. if t != nil {
  1728. m[strings.ToLower(*t)] = true
  1729. }
  1730. }
  1731.  
  1732. for t := range m {
  1733. tagList = append(tagList, t)
  1734. }
  1735.  
  1736. if len(tagList) == 0 {
  1737. return itineraryIDs, nil
  1738. }
  1739.  
  1740. var t []ItineraryTag
  1741. err := r.DB.
  1742. WithContext(ctx).
  1743. Model(&t).
  1744. Column("itinerary_tag.itinerary_id").
  1745. Where("itinerary_id IN (?)", pg.In(itineraryIDs)).
  1746. Where("LOWER(tag) IN (?)", pg.In(tagList)).
  1747. Group("itinerary_tag.itinerary_id").
  1748. Having("COUNT(tag) = ?", len(tagList)).
  1749. Select()
  1750. if err != nil {
  1751. if err != pg.ErrNoRows {
  1752. log.Error(err.Error())
  1753. }
  1754. return nil, err
  1755. }
  1756.  
  1757. var result []int64
  1758.  
  1759. for _, tag := range t {
  1760. result = append(result, tag.ItineraryID)
  1761. }
  1762.  
  1763. return result, nil
  1764. }
  1765.  
  1766. // Total returns the total number of itineraries returned.
  1767. func (r *itinerariesResolver) Total() int32 {
  1768. return r.total
  1769. }
  1770.  
  1771. // Items returns the itineraries in the itineraries response.
  1772. func (r *itinerariesResolver) Items() []*itineraryResolver {
  1773. var result []*itineraryResolver
  1774. for _, i := range r.itineraries {
  1775. result = append(result, &itineraryResolver{
  1776. itinerary: i,
  1777. baseSVGURL: r.baseSVGURL,
  1778. })
  1779. }
  1780.  
  1781. return result
  1782. }
  1783.  
  1784. // ID returns the itinerary ID.
  1785. func (r *itineraryResolver) ID() graphql.ID {
  1786. return encodeID(r.itinerary.ID)
  1787. }
  1788.  
  1789. // AddDayDescription returns the description for what users would see if they
  1790. // extended their itinerary by one day. It's a kind of up sell. It's not used.
  1791. func (r *itineraryResolver) AddDayDescription() *string {
  1792. return r.itinerary.AddDayDescription
  1793. }
  1794.  
  1795. // ArrivalTimes returns the itinerary's arrival times, the set of times within
  1796. // which the itinerary applies (for example, if an itinerary applies to all
  1797. // arrivals between 9am and 1pm).
  1798. func (r *itineraryResolver) ArrivalTimes() *timeRangeResolver {
  1799. timeRange := TimeRange{
  1800. StartTime: r.itinerary.ArrivalStartTime,
  1801. EndTime: r.itinerary.ArrivalEndTime,
  1802. }
  1803. return &timeRangeResolver{&timeRange}
  1804. }
  1805.  
  1806. // AvailableDates returns the itinerary's available dates, the dates within
  1807. // which an itinerary applies. These are almost always Jan 1 to Dec 31, but
  1808. // there may be a need for an itinerary that's available for a short time.
  1809. func (r *itineraryResolver) AvailableDates() *dateRangeResolver {
  1810. dateRange := DateRange{
  1811. StartDate: r.itinerary.AvailableStartDate,
  1812. EndDate: r.itinerary.AvailableEndDate,
  1813. }
  1814. return &dateRangeResolver{&dateRange}
  1815. }
  1816.  
  1817. // AvailableExperiences returns the available experiences. See the Available
  1818. // Experiences type for more information.
  1819. func (r *itineraryResolver) AvailableExperiences() []*availableExperienceResolver {
  1820. var result []*availableExperienceResolver
  1821. for _, e := range r.itinerary.AvailableExperiences {
  1822. result = append(result, &availableExperienceResolver{*e})
  1823. }
  1824.  
  1825. return result
  1826. }
  1827.  
  1828. // Code returns the itinerary code.
  1829. func (r *itineraryResolver) Code() string {
  1830. return r.itinerary.Code
  1831. }
  1832.  
  1833. // Country returns the itinerary's country.
  1834. func (r *itineraryResolver) Country() *countryResolver {
  1835. return &countryResolver{r.itinerary.Country}
  1836. }
  1837.  
  1838. // Days returns the itinerary's days.
  1839. func (r *itineraryResolver) Days() []*itineraryDayResolver {
  1840. days := r.itinerary.ItineraryDays
  1841. sort.Slice(days, func(i, j int) bool {
  1842. return days[i].Index < days[j].Index
  1843. })
  1844.  
  1845. var result []*itineraryDayResolver
  1846. for _, d := range days {
  1847. result = append(result, &itineraryDayResolver{
  1848. itineraryDay: *d,
  1849. baseSVGURL: r.baseSVGURL,
  1850. })
  1851. }
  1852.  
  1853. return result
  1854. }
  1855.  
  1856. // DepartureTimes returns the itinerary's departure times. See ArrivalTimes for
  1857. // an explanation of a similar type.
  1858. func (r *itineraryResolver) DepartureTimes() *timeRangeResolver {
  1859. timeRange := TimeRange{
  1860. StartTime: r.itinerary.DepartureStartTime,
  1861. EndTime: r.itinerary.DepartureEndTime,
  1862. }
  1863. return &timeRangeResolver{&timeRange}
  1864. }
  1865.  
  1866. // DesktopMapImage returns the itinerary's desktop map image.
  1867. func (r *itineraryResolver) DesktopMapImage() *imageResolver {
  1868. if r.itinerary.DesktopMapImage == nil {
  1869. return nil
  1870. }
  1871. return &imageResolver{*r.itinerary.DesktopMapImage}
  1872. }
  1873.  
  1874. // Description returns the itinerary's description text.
  1875. func (r *itineraryResolver) Description() *string {
  1876. return r.itinerary.Description
  1877. }
  1878.  
  1879. // IsTemplate returns whether or not an itinerary is a template.
  1880. func (r *itineraryResolver) IsTemplate() bool {
  1881. return r.itinerary.IsTemplate
  1882. }
  1883.  
  1884. // SubtractDayDescription returns the description for what users would see if
  1885. // they reduce their itinerary by one day. It's not used.
  1886. func (r *itineraryResolver) SubtractDayDescription() *string {
  1887. return r.itinerary.SubtractDayDescription
  1888. }
  1889.  
  1890. // MobileMapImage returns the itinerary's mobile map image.
  1891. func (r *itineraryResolver) MobileMapImage() *imageResolver {
  1892. if r.itinerary.MobileMapImage == nil {
  1893. return nil
  1894. }
  1895. return &imageResolver{*r.itinerary.MobileMapImage}
  1896. }
  1897.  
  1898. // Name returns the itinerary's name.
  1899. func (r *itineraryResolver) Name() *string {
  1900. return r.itinerary.Name
  1901. }
  1902.  
  1903. // PricingDescription returns the description of the itinerary's pricing.
  1904. func (r *itineraryResolver) PricingDescription() *string {
  1905. return r.itinerary.PricingDescription
  1906. }
  1907.  
  1908. // StyleDescription returns the description of the itinerary's stays. This
  1909. // was originally for describing the style (pace), but has been co-opted for
  1910. // this new use.
  1911. func (r *itineraryResolver) StyleDescription() *string {
  1912. return r.itinerary.StyleDescription
  1913. }
  1914.  
  1915. // Summary returns the itinerary's summary text.
  1916. func (r *itineraryResolver) Summary() *string {
  1917. return r.itinerary.Summary
  1918. }
  1919.  
  1920. // Tags returns the itinerary's tags.
  1921. func (r *itineraryResolver) Tags() *[]*string {
  1922. var result []*string
  1923. for _, v := range r.itinerary.Tags {
  1924. s := v.Tag
  1925. result = append(result, &s)
  1926. }
  1927.  
  1928. return &result
  1929. }
  1930.  
  1931. // TransportationDescription returns the description of the itinerary's
  1932. // transportation. This isn't used.
  1933. func (r *itineraryResolver) TransportationDescription() *string {
  1934. return r.itinerary.TransportationDescription
  1935. }
  1936.  
  1937. // TransportationSummary returns the summary of the itinerary's transportation.
  1938. // This isn't used.
  1939. func (r *itineraryResolver) TransportationSummary() *string {
  1940. return r.itinerary.TransportationSummary
  1941. }
  1942.  
  1943. // createItinerary creates an itinerary.
  1944. func createItinerary(tx *pg.Tx, itinerary *itineraryInput) (*graphql.ID, error) {
  1945. countryCode := formatCode(string(itinerary.CountryCode))
  1946.  
  1947. var country model.Country
  1948. err := tx.
  1949. Model(&country).
  1950. Where("code = ?", countryCode).
  1951. First()
  1952. if err != nil {
  1953. return nil, fmt.Errorf("unknown country: %s", countryCode)
  1954. }
  1955.  
  1956. if itinerary.DesktopMapImage != nil {
  1957. id, err := createImage(tx, itinerary.DesktopMapImage)
  1958. if err != nil {
  1959. return nil, err
  1960. }
  1961. itinerary.DesktopMapImageID = id
  1962. }
  1963.  
  1964. var desktopMapImageID *int64
  1965. if itinerary.DesktopMapImageID != nil {
  1966. id, err := decodeID(*itinerary.DesktopMapImageID)
  1967. if err != nil {
  1968. return nil, err
  1969. }
  1970. desktopMapImageID = &id
  1971. }
  1972.  
  1973. if itinerary.MobileMapImage != nil {
  1974. id, err := createImage(tx, itinerary.MobileMapImage)
  1975. if err != nil {
  1976. return nil, err
  1977. }
  1978. itinerary.MobileMapImageID = id
  1979. }
  1980.  
  1981. var mobileMapImageID *int64
  1982. if itinerary.MobileMapImageID != nil {
  1983. id, err := decodeID(*itinerary.MobileMapImageID)
  1984. if err != nil {
  1985. return nil, err
  1986. }
  1987. mobileMapImageID = &id
  1988. }
  1989.  
  1990. var (
  1991. addDayDescription *string
  1992. isTemplate bool
  1993. SubtractDayDescription *string
  1994. name *string
  1995. pricingDetails *string
  1996. styleDescription *string
  1997. summary *string
  1998. transportationDescription *string
  1999. transportationSummary *string
  2000. )
  2001.  
  2002. if itinerary.AddDayDescription != nil && len(*itinerary.AddDayDescription) > 0 {
  2003. addDayDescription = itinerary.AddDayDescription
  2004. }
  2005.  
  2006. if itinerary.IsTemplate != nil {
  2007. isTemplate = *itinerary.IsTemplate
  2008. }
  2009.  
  2010. if itinerary.SubtractDayDescription != nil && len(*itinerary.SubtractDayDescription) > 0 {
  2011. SubtractDayDescription = itinerary.SubtractDayDescription
  2012. }
  2013.  
  2014. if itinerary.Name != nil && len(*itinerary.Name) > 0 {
  2015. name = itinerary.Name
  2016. }
  2017.  
  2018. if itinerary.PricingDescription != nil && len(*itinerary.PricingDescription) > 0 {
  2019. pricingDetails = itinerary.PricingDescription
  2020. }
  2021.  
  2022. if itinerary.Summary != nil && len(*itinerary.Summary) > 0 {
  2023. summary = itinerary.Summary
  2024. }
  2025.  
  2026. if itinerary.TransportationDescription != nil && len(*itinerary.TransportationDescription) > 0 {
  2027. transportationDescription = itinerary.TransportationDescription
  2028. }
  2029.  
  2030. if itinerary.TransportationSummary != nil && len(*itinerary.TransportationSummary) > 0 {
  2031. transportationSummary = itinerary.TransportationSummary
  2032. }
  2033.  
  2034. if itinerary.StyleDescription != nil && len(*itinerary.StyleDescription) > 0 {
  2035. styleDescription = itinerary.StyleDescription
  2036. }
  2037.  
  2038. i := Itinerary{
  2039. AddDayDescription: addDayDescription,
  2040. ArrivalEndTime: itinerary.ArrivalTimes.EndTime,
  2041. ArrivalStartTime: itinerary.ArrivalTimes.StartTime,
  2042. AvailableEndDate: itinerary.AvailableDates.EndDate,
  2043. AvailableStartDate: itinerary.AvailableDates.StartDate,
  2044. Code: itinerary.Code,
  2045. CountryID: country.ID,
  2046. DepartureEndTime: itinerary.DepartureTimes.EndTime,
  2047. DepartureStartTime: itinerary.DepartureTimes.StartTime,
  2048. Description: itinerary.Description,
  2049. DesktopMapImageID: desktopMapImageID,
  2050. IsTemplate: isTemplate,
  2051. MobileMapImageID: mobileMapImageID,
  2052. Name: name,
  2053. PricingDescription: pricingDetails,
  2054. StyleDescription: styleDescription,
  2055. SubtractDayDescription: SubtractDayDescription,
  2056. Summary: summary,
  2057. TransportationDescription: transportationDescription,
  2058. TransportationSummary: transportationSummary,
  2059. CreatedAt: time.Now(),
  2060. UpdatedAt: time.Now(),
  2061. }
  2062. if err := tx.Insert(&i); err != nil {
  2063. return nil, err
  2064. }
  2065.  
  2066. id := encodeID(i.ID)
  2067.  
  2068. return &id, nil
  2069. }
  2070.  
  2071. // copyItinerary copies an itinerary, its days, and all its assets.
  2072. func copyItinerary(
  2073. tx *pg.Tx,
  2074. id graphql.ID,
  2075. tripID *graphql.ID,
  2076. deepCopy bool,
  2077. suffix *string,
  2078. dayIDs []*graphql.ID,
  2079. ) (*graphql.ID, error) {
  2080. var itinerary Itinerary
  2081. err := tx.
  2082. Model(&itinerary).
  2083. Where("id = ?", id).
  2084. First()
  2085. if err != nil {
  2086. if err != pg.ErrNoRows {
  2087. log.Error(err)
  2088. }
  2089. return nil, errNotFound
  2090. }
  2091.  
  2092. if itinerary.DesktopMapImageID != nil {
  2093. var image model.Image
  2094. err = tx.
  2095. Model(&image).
  2096. Where("id = ?", itinerary.DesktopMapImageID).
  2097. Select()
  2098. if err != nil {
  2099. return nil, errors.Wrapf(err, "could not select itinerary desktop map image [%d]",
  2100. itinerary.DesktopMapImageID)
  2101. }
  2102.  
  2103. image.ID = 0
  2104. image.CreatedAt = time.Now()
  2105. image.UpdatedAt = time.Now()
  2106.  
  2107. if err := tx.Insert(&image); err != nil {
  2108. return nil, errors.Wrap(err, "could not insert copy of desktop map image")
  2109. }
  2110.  
  2111. itinerary.DesktopMapImageID = &image.ID
  2112. }
  2113.  
  2114. if itinerary.MobileMapImageID != nil {
  2115. var image model.Image
  2116. err = tx.
  2117. Model(&image).
  2118. Where("id = ?", itinerary.MobileMapImageID).
  2119. Select()
  2120. if err != nil {
  2121. return nil, errors.Wrapf(err, "could not select mobile map image [%d]", itinerary.MobileMapImageID)
  2122. }
  2123.  
  2124. image.ID = 0
  2125. image.CreatedAt = time.Now()
  2126. image.UpdatedAt = time.Now()
  2127.  
  2128. if err := tx.Insert(&image); err != nil {
  2129. return nil, errors.Wrap(err, "could not insert copy of mobile map image")
  2130. }
  2131.  
  2132. itinerary.MobileMapImageID = &image.ID
  2133. }
  2134.  
  2135. itinerary.ID = 0
  2136. itinerary.IsTemplate = false
  2137. itinerary.CreatedAt = time.Now()
  2138. itinerary.UpdatedAt = time.Now()
  2139.  
  2140. if suffix != nil && len(*suffix) > 0 {
  2141. itinerary.Code += *suffix
  2142. } else if tripID != nil {
  2143. itinerary.Code += "_" + string(*tripID)
  2144. } else {
  2145. itinerary.Code += "_COPY"
  2146. }
  2147.  
  2148. if err := tx.Insert(&itinerary); err != nil {
  2149. return nil, errors.Wrap(err, "could not insert copy of itinerary into db")
  2150. }
  2151.  
  2152. var itineraryDays []ItineraryDay
  2153. err = tx.
  2154. Model(&itineraryDays).
  2155. Where("itinerary_id = ?", id).
  2156. Select()
  2157. if err != nil {
  2158. if err != pg.ErrNoRows {
  2159. log.Error(err.Error())
  2160. }
  2161. return nil, errNotFound
  2162. }
  2163.  
  2164. idMap := make(map[int64]int64) // Old => New
  2165. didMap := make(map[int64]int64)
  2166. var itineraryDayIDs []int64
  2167. var deepCopyDayIDs []int64
  2168. for _, itineraryDay := range itineraryDays {
  2169. idid := itineraryDay.ID
  2170. itineraryDayIDs = append(itineraryDayIDs, idid)
  2171.  
  2172. did := itineraryDay.DayID
  2173.  
  2174. // create new itineraryDay
  2175. itineraryDay.ID = 0
  2176. itineraryDay.ItineraryID = itinerary.ID
  2177. itineraryDay.CreatedAt = time.Now()
  2178. itineraryDay.UpdatedAt = time.Now()
  2179.  
  2180. var deepCopyDay bool
  2181. for _, adid := range dayIDs {
  2182. if adid == nil {
  2183. continue
  2184. }
  2185.  
  2186. deepCopyDayID, err := decodeID(*adid)
  2187. if err != nil {
  2188. log.Error(err.Error())
  2189. return nil, errors.Wrapf(err, "could not decode id [%s]", *adid)
  2190. }
  2191.  
  2192. if deepCopyDayID == did {
  2193. deepCopyDay = true
  2194. break
  2195. }
  2196. }
  2197.  
  2198. if deepCopy && deepCopyDay {
  2199. // take duplicate days into account
  2200. updatedSuffix := new(string)
  2201. if suffix != nil && didMap[did] > 0 {
  2202. *updatedSuffix = fmt.Sprintf("%s%s", *suffix, strconv.FormatInt(didMap[did], 10))
  2203. } else {
  2204. updatedSuffix = suffix
  2205. }
  2206.  
  2207. dayID, err := copyDay(tx, encodeID(did), updatedSuffix)
  2208. if err != nil {
  2209. log.Error(err.Error())
  2210. return nil, errors.Wrapf(err, "could not copy day [%d]", did)
  2211. }
  2212.  
  2213. id, err := decodeID(*dayID)
  2214. if err != nil {
  2215. log.Error(err.Error())
  2216. return nil, errors.Wrapf(err, "could not decode id [%s]", *dayID)
  2217. }
  2218. itineraryDay.DayID = id
  2219. }
  2220.  
  2221. if err := tx.Insert(&itineraryDay); err != nil {
  2222. log.Error(err.Error())
  2223. return nil, errors.Wrapf(err, "could not insert copy of itinerary day [%d]", itineraryDay.ID)
  2224. }
  2225. if deepCopy && deepCopyDay {
  2226. deepCopyDayIDs = append(deepCopyDayIDs, itineraryDay.ID)
  2227. }
  2228.  
  2229. idMap[idid] = itineraryDay.ID
  2230. didMap[did]++
  2231. }
  2232.  
  2233. var itineraryTags []ItineraryTag
  2234. err = tx.
  2235. Model(&itineraryTags).
  2236. Where("itinerary_id = ?", id).
  2237. Select()
  2238. if err != nil {
  2239. if err != pg.ErrNoRows {
  2240. log.Error(err.Error())
  2241. }
  2242. return nil, errNotFound
  2243. }
  2244.  
  2245. for _, itineraryTag := range itineraryTags {
  2246. itineraryTag.ID = 0
  2247. itineraryTag.ItineraryID = itinerary.ID
  2248. itineraryTag.CreatedAt = time.Now()
  2249. itineraryTag.UpdatedAt = time.Now()
  2250.  
  2251. iTag := itineraryTag
  2252. if err := tx.Insert(&iTag); err != nil {
  2253. log.Error(err.Error())
  2254. return nil, errors.Wrap(err, "could not insert itinerary tag")
  2255. }
  2256. }
  2257.  
  2258. var alternatives []ItineraryDayAlternative
  2259. if len(itineraryDayIDs) > 0 {
  2260. err = tx.
  2261. Model(&alternatives).
  2262. Where("itinerary_day_id IN (?)", pg.In(itineraryDayIDs)).
  2263. Select()
  2264. if err != nil {
  2265. if err != pg.ErrNoRows {
  2266. log.Error(err.Error())
  2267. }
  2268. return nil, errors.Wrap(err, "could not select alternatives day")
  2269. }
  2270. }
  2271.  
  2272. altDayIDsNum := make(map[int64]int64)
  2273. for _, alternative := range alternatives {
  2274. alternative.ID = 0
  2275. alternative.ItineraryDayID = idMap[alternative.ItineraryDayID]
  2276. alternative.CreatedAt = time.Now()
  2277. alternative.UpdatedAt = time.Now()
  2278. var deepCopyDay bool
  2279. for _, day := range deepCopyDayIDs {
  2280. if day == alternative.ItineraryDayID {
  2281. deepCopyDay = true
  2282. break
  2283. }
  2284. }
  2285. if deepCopy && deepCopyDay {
  2286. updatedSuffix := new(string)
  2287. if suffix != nil && altDayIDsNum[alternative.DayID] > 0 {
  2288. *updatedSuffix = fmt.Sprintf("%s%s", *suffix,
  2289. strconv.FormatInt(didMap[alternative.DayID], 10))
  2290. } else {
  2291. updatedSuffix = suffix
  2292. }
  2293.  
  2294. dayID, err := copyDay(tx, encodeID(alternative.DayID), updatedSuffix)
  2295. if err != nil {
  2296. log.Error(err.Error())
  2297. return nil, errors.Wrapf(err, "could not copy day [%d]", alternative.DayID)
  2298. }
  2299.  
  2300. id, err := decodeID(*dayID)
  2301. if err != nil {
  2302. log.Error(err.Error())
  2303. return nil, errors.Wrapf(err, "could not decode id [%s]", *dayID)
  2304. }
  2305. alternative.DayID = id
  2306. }
  2307. if err := tx.Insert(&alternative); err != nil {
  2308. log.Error(err.Error())
  2309. return nil, errors.Wrap(err, "could not insert copy of alternative day")
  2310. }
  2311. altDayIDsNum[alternative.DayID]++
  2312. }
  2313.  
  2314. itineraryID := encodeID(itinerary.ID)
  2315.  
  2316. return &itineraryID, nil
  2317. }
  2318.  
  2319. // updateItinerary updates an itinerary.
  2320. //
  2321. // This cascades to all non-booked trips, as well.
  2322. func updateItinerary(
  2323. tx *pg.Tx,
  2324. id graphql.ID,
  2325. itinerary *itineraryInput,
  2326. conv *libcurrency.Converter,
  2327. ) error {
  2328. intID, err := decodeID(id)
  2329. if err != nil {
  2330. return err
  2331. }
  2332.  
  2333. countryCode := formatCode(string(itinerary.CountryCode))
  2334.  
  2335. var country model.Country
  2336. err = tx.
  2337. Model(&country).
  2338. Where("code = ?", countryCode).
  2339. First()
  2340. if err != nil {
  2341. return fmt.Errorf("unknown country: %s", countryCode)
  2342. }
  2343.  
  2344. var i Itinerary
  2345. err = tx.
  2346. Model(&i).
  2347. Where("id = ?", intID).
  2348. First()
  2349. if err != nil {
  2350. return err
  2351. }
  2352.  
  2353. if itinerary.DesktopMapImage != nil {
  2354. var (
  2355. iid *graphql.ID
  2356. err error
  2357. )
  2358. if i.DesktopMapImageID == nil {
  2359. iid, err = createImage(tx, itinerary.DesktopMapImage)
  2360. } else {
  2361. gqlID := encodeID(*i.DesktopMapImageID)
  2362. iid = &gqlID
  2363. err = updateImage(tx, *iid, itinerary.DesktopMapImage)
  2364. }
  2365. if err != nil {
  2366. return err
  2367. }
  2368. itinerary.DesktopMapImageID = iid
  2369. }
  2370.  
  2371. var desktopMapImageID *int64
  2372. if itinerary.DesktopMapImageID != nil {
  2373. iid, err := decodeID(*itinerary.DesktopMapImageID)
  2374. if err != nil {
  2375. return err
  2376. }
  2377. desktopMapImageID = &iid
  2378. }
  2379.  
  2380. if itinerary.MobileMapImage != nil {
  2381. var (
  2382. iid *graphql.ID
  2383. err error
  2384. )
  2385. if i.MobileMapImageID == nil {
  2386. iid, err = createImage(tx, itinerary.MobileMapImage)
  2387. } else {
  2388. gqlID := encodeID(*i.MobileMapImageID)
  2389. iid = &gqlID
  2390. err = updateImage(tx, *iid, itinerary.MobileMapImage)
  2391. }
  2392. if err != nil {
  2393. return err
  2394. }
  2395. itinerary.MobileMapImageID = iid
  2396. }
  2397.  
  2398. var mobileMapImageID *int64
  2399. if itinerary.MobileMapImageID != nil {
  2400. iid, err := decodeID(*itinerary.MobileMapImageID)
  2401. if err != nil {
  2402. return err
  2403. }
  2404. mobileMapImageID = &iid
  2405. }
  2406.  
  2407. var (
  2408. addDayDescription *string
  2409. isTemplate bool
  2410. subtractDayDescription *string
  2411. name *string
  2412. pricingDescription *string
  2413. styleDescription *string
  2414. summary *string
  2415. transportationDescription *string
  2416. transportationSummary *string
  2417. )
  2418.  
  2419. if itinerary.AddDayDescription != nil {
  2420. if len(*itinerary.AddDayDescription) > 0 {
  2421. addDayDescription = itinerary.AddDayDescription
  2422. }
  2423. }
  2424.  
  2425. if itinerary.IsTemplate != nil {
  2426. isTemplate = *itinerary.IsTemplate
  2427. }
  2428.  
  2429. if itinerary.SubtractDayDescription != nil {
  2430. if len(*itinerary.SubtractDayDescription) > 0 {
  2431. subtractDayDescription = itinerary.SubtractDayDescription
  2432. }
  2433. }
  2434.  
  2435. if itinerary.Name != nil {
  2436. if len(*itinerary.Name) > 0 {
  2437. name = itinerary.Name
  2438. }
  2439. }
  2440.  
  2441. if itinerary.PricingDescription != nil {
  2442. if len(*itinerary.PricingDescription) > 0 {
  2443. pricingDescription = itinerary.PricingDescription
  2444. }
  2445. }
  2446.  
  2447. if itinerary.StyleDescription != nil {
  2448. if len(*itinerary.StyleDescription) > 0 {
  2449. styleDescription = itinerary.StyleDescription
  2450. }
  2451. }
  2452.  
  2453. if itinerary.Summary != nil {
  2454. if len(*itinerary.Summary) > 0 {
  2455. summary = itinerary.Summary
  2456. }
  2457. }
  2458.  
  2459. if itinerary.TransportationDescription != nil {
  2460. if len(*itinerary.TransportationDescription) > 0 {
  2461. transportationDescription = itinerary.TransportationDescription
  2462. }
  2463. }
  2464.  
  2465. if itinerary.TransportationSummary != nil {
  2466. if len(*itinerary.TransportationSummary) > 0 {
  2467. transportationSummary = itinerary.TransportationSummary
  2468. }
  2469. }
  2470.  
  2471. _, err = tx.
  2472. Model(&Itinerary{}).
  2473. Set("add_day_description = ?", addDayDescription).
  2474. Set("arrival_end_time = ?", itinerary.ArrivalTimes.EndTime).
  2475. Set("arrival_start_time = ?", itinerary.ArrivalTimes.StartTime).
  2476. Set("available_end_date = ?", itinerary.AvailableDates.EndDate).
  2477. Set("available_start_date = ?", itinerary.AvailableDates.StartDate).
  2478. Set("code = ?", itinerary.Code).
  2479. Set("country_id = ?", country.ID).
  2480. Set("departure_end_time = ?", itinerary.DepartureTimes.EndTime).
  2481. Set("departure_start_time = ?", itinerary.DepartureTimes.StartTime).
  2482. Set("description = ?", itinerary.Description).
  2483. Set("desktop_map_image_id = ?", desktopMapImageID).
  2484. Set("is_template = ?", isTemplate).
  2485. Set("mobile_map_image_id = ?", mobileMapImageID).
  2486. Set("name = ?", name).
  2487. Set("pricing_description = ?", pricingDescription).
  2488. Set("style_description = ?", styleDescription).
  2489. Set("subtract_day_description = ?", subtractDayDescription).
  2490. Set("summary = ?", summary).
  2491. Set("transportation_description = ?", transportationDescription).
  2492. Set("transportation_summary = ?", transportationSummary).
  2493. Where("id = ?", intID).
  2494. Update()
  2495. if err != nil {
  2496. return err
  2497. }
  2498.  
  2499. columns := []string{
  2500. "trip.*",
  2501. "ArrivalAirport",
  2502. "Country",
  2503. "DepartureAirport",
  2504. "Invoice",
  2505. "Invoice.Price",
  2506. }
  2507.  
  2508. var trips []Trip
  2509. err = tx.
  2510. Model(&trips).
  2511. Column(columns...).
  2512. Where("itinerary_id = ?", intID).
  2513. Where("booked = ?", false).
  2514. Select()
  2515. if err != nil {
  2516. return err
  2517. }
  2518.  
  2519. for _, trip := range trips {
  2520. trip := trip
  2521. err = updateUnbookedTrip(
  2522. tx,
  2523. encodeID(trip.ID),
  2524. buildTripInputFromTrip(&trip),
  2525. conv,
  2526. )
  2527. if err != nil {
  2528. return err
  2529. }
  2530. }
  2531.  
  2532. return nil
  2533. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement