1package main23import (4 "database/sql"5 "flag"6 "fmt"7 "log"8 "net/http"9 "os"10 "strings"11 "time"1213 _ "github.com/lib/pq"14)1516func UpdateTrainsInDatabase(db *sql.DB, time time.Time, trains []Train) {17 for _, train := range trains {18 for _, car := range train.Cars {19 if err := InsertOrUpdateCarInDatabase(db, car, time); err != nil {20 log.Println(err)21 }22 }23 }24}2526func InsertOrUpdateCarInDatabase(db *sql.DB, c int, t time.Time) error {27 iou := `28 INSERT INTO cars (car, last_seen)29 VALUES ($1, $2)30 ON CONFLICT (car) DO UPDATE31 SET last_seen = $232 `3334 _, err := db.Exec(iou, c, t)35 if err != nil {36 return err37 }3839 return nil40}4142func ReadCarsFromDatabase(db *sql.DB) (map[int]time.Time, error) {43 query := `44 SELECT car, last_seen45 FROM cars46 `4748 rows, err := db.Query(query)49 if err != nil {50 return nil, err51 }5253 cars := make(map[int]time.Time)5455 for rows.Next() {56 var car int57 var lastSeen time.Time58 if err := rows.Scan(&car, &lastSeen); err != nil {59 panic(err)60 }61 cars[car] = lastSeen62 }6364 if err = rows.Close(); err != nil {65 return nil, err66 }6768 return cars, nil69}7071func ConnectToDatabase(url string) *sql.DB {72 db, err := sql.Open("postgres", url)73 if err != nil {74 panic(err)75 }7677 if err = db.Ping(); err != nil {78 panic(err)79 }8081 return db82}8384func SendMessages(url string, trains []Train, cars map[int]time.Time, time time.Time) {85 log.Println("Found new trains: ")86 for _, train := range trains {87 log.Println(train)8889 found := false90 for _, car := range train.Cars {91 // If the we haven't seen the car in a few minutes, it's probably running a new trip.92 delta := time.Sub(cars[car])93 if delta.Seconds() <= 60*5 {94 log.Printf(95 "Saw car too recently (%v seconds ago) to send a notification for it",96 delta.Seconds())97 continue98 }99 found = true100 }101102 // Alert for the entire train if we found a single car in it that's notifiable103 if !found {104 continue105 }106 resp, err := http.Post(107 url,108 "application/json",109 strings.NewReader(110 fmt.Sprintf("{\"value1\": %q}", train)))111 if err != nil {112 log.Printf("Unable to send IFTTT message: %v", err)113 }114 resp.Body.Close()115 log.Println("Successfully sent message!")116 }117}118119func main() {120 var (121 IFTTTURL = flag.String("ifttt_url", "", "The IFTTT URL to send the notification to.")122 DatabaseURL = flag.String("database_url", "", "The database connection string.")123 )124125 flag.Parse()126127 if *IFTTTURL == "" {128 fmt.Println("ifttt_url must be set")129 os.Exit(1)130 }131132 if *DatabaseURL == "" {133 fmt.Println("database_url must be set")134 os.Exit(1)135 }136137 log.Println("Connecting to database")138 db := ConnectToDatabase(*DatabaseURL)139 defer db.Close()140 log.Println("Successfully connected to database")141142 trains, err := GetTrains()143 if err != nil {144 panic(fmt.Sprintf("Unable to get trains: %v", err))145 }146147 log.Println("Found trains: ")148 for _, train := range trains {149 log.Println(train)150 }151152 newTrains := FilterNewTrains(trains)153 if len(newTrains) == 0 {154 log.Println("Didn't find any trains with new cars :(")155 return156 }157158 // Read before we update the database with the new trips.159 cars, err := ReadCarsFromDatabase(db)160 if err != nil {161 panic(err)162 }163164 now := time.Now().UTC()165 SendMessages(*IFTTTURL, newTrains, cars, now)166167 // We want to update the database last so we don't miss any notifications.168 UpdateTrainsInDatabase(db, now, trains)169}