package storage import ( "database/sql" "encoding/json" "errors" "fmt" "github.com/google/uuid" _ "github.com/lib/pq" "github.com/paulmach/orb" "github.com/paulmach/orb/geojson" "github.com/rs/zerolog/log" "github.com/spf13/viper" "solidarity-service/internal" "solidarity-service/utils" "strconv" "strings" "time" ) type PostgresqlStorage struct { DbConnection *sql.DB Schema string Tables map[string]string } func NewPostgresqlStorage(cfg *viper.Viper) (PostgresqlStorage, error) { var ( host = cfg.GetString("storage.db.psql.host") port = cfg.GetString("storage.db.psql.port") user = cfg.GetString("storage.db.psql.user") password = cfg.GetString("storage.db.psql.password") dbname = cfg.GetString("storage.db.psql.dbname") sslmode = cfg.GetString("storage.db.psql.sslmode") pg_schema = cfg.GetString("storage.db.psql.schema") pgtables_drivers = cfg.GetString("storage.db.psql.tables.drivers") pgtables_passengers = cfg.GetString("storage.db.psql.tables.passengers") pgtables_bookings = cfg.GetString("storage.db.psql.tables.bookings") ) portInt, _ := strconv.Atoi(port) psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", host, portInt, user, password, dbname, sslmode) db, err := sql.Open("postgres", psqlconn) if err != nil { log.Error().Err(err).Msg("opening connection to postgresql failed") return PostgresqlStorage{}, fmt.Errorf("connection to postgresql failed") } err = db.Ping() if err != nil { log.Error().Err(err).Msg("ping to postgresql failed") return PostgresqlStorage{}, fmt.Errorf("connection to postgresql database failed") } return PostgresqlStorage{ DbConnection: db, Schema: pg_schema, Tables: map[string]string{ "drivers": fmt.Sprintf("%s.%s", pg_schema, pgtables_drivers), "bookings": fmt.Sprintf("%s.%s", pg_schema, pgtables_bookings), "passengers": fmt.Sprintf("%s.%s", pg_schema, pgtables_passengers), }, }, nil } func (s PostgresqlStorage) CreatePassenger(passenger internal.Passenger) (err error) { _, err = uuid.Parse(passenger.Passenger.ID) if err != nil { log.Error().Err(err).Msg("Postgresql Storage CreatePassenger invalid ID") return err } if passenger.Passenger_pickup_date == 0 { errMsg := "Postgresql Storage CreatePassenger empty UNIX pickup Date timestamp" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg) } if passenger.Passenger.Alias == "" || passenger.Passenger.Operator == "" { errMsg := "Postgresql Storage CreatePassenger empty alias or operator FQDN." log.Error().Msg(errMsg) return errors.New(errMsg) } preferencesJSON, err := json.Marshal(passenger.Preferences) if err != nil { errMsg := "Postgresql Storage CreatePassenger Error encoding Preferences to JSON" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } departureJSON, err := json.Marshal(passenger.Passenger_departure_address) if err != nil { errMsg := "Postgresql Storage CreatePassenger Error encoding departure Feature to JSON" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } destinationJSON, err := json.Marshal(passenger.Passenger_destination_address) if err != nil { errMsg := "Postgresql Storage CreatePassenger Error converting destination Feature to JSON" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } _, err = s.DbConnection.Exec(fmt.Sprintf("INSERT INTO %s (passenger_id, passenger_departure_route, passenger_destination_route, alias, last_name, first_name, grade, picture, verified_identity, operator, preferences, passenger_pickup_date) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12)", s.Tables["passengers"]), passenger.Passenger.ID, departureJSON, destinationJSON, passenger.Passenger.Alias, passenger.Passenger.LastName, passenger.Passenger.FirstName, passenger.Passenger.Grade, passenger.Passenger.Picture, passenger.Passenger.VerifiedIdentity, passenger.Passenger.Operator, preferencesJSON, passenger.Passenger_pickup_date) if err != nil { errMsg := "Postgresql Storage CreatePassenger Error inserting data into the database" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } return nil } func (s PostgresqlStorage) GetPassenger(passengerID string) (passenger internal.Passenger, err error) { var preferencesJSON []byte var departure_address []byte var destination_address []byte err = s.DbConnection.QueryRow(fmt.Sprintf("SELECT passenger_departure_route, passenger_destination_route, alias, last_name, first_name, grade, picture, verified_identity, operator, preferences, passenger_pickup_date , passenger_id FROM %s WHERE passenger_id = $1", s.Tables["passengers"]), passengerID). Scan( &departure_address, &destination_address, &passenger.Passenger.Alias, &passenger.Passenger.LastName, &passenger.Passenger.FirstName, &passenger.Passenger.Grade, &passenger.Passenger.Picture, &passenger.Passenger.VerifiedIdentity, &passenger.Passenger.Operator, &preferencesJSON, &passenger.Passenger_pickup_date, &passenger.Passenger.ID, ) if err != nil { fmt.Println(err) errMsg := "Postgresql Storage GetPassenger Error querying data from the database" log.Error().Err(err).Msg(errMsg) return passenger, errors.New(errMsg + err.Error()) } err = json.Unmarshal(preferencesJSON, &passenger.Preferences) if err != nil { errMsg := "Postgresql Storage GetPassenger Error decoding Preferences from JSON" log.Error().Err(err).Msg(errMsg) return passenger, errors.New(errMsg + err.Error()) } passenger.Passenger_destination_address, err = geojson.UnmarshalFeature(destination_address) if err != nil { errMsg := "Postgresql Storage GetPassenger Error decoding Passenger destination route into GeoJSON" log.Error().Err(err).Msg(errMsg) return passenger, errors.New(errMsg + err.Error()) } passenger.Passenger_departure_address, err = geojson.UnmarshalFeature(departure_address) if err != nil { errMsg := "Postgresql Storage GetPassenger Error decoding Passenger departure route into GeoJSON" log.Error().Err(err).Msg(errMsg) return passenger, errors.New(errMsg + err.Error()) } return passenger, nil } func (s PostgresqlStorage) CreateDriver(driver internal.Driver) (err error) { var availabilities []byte _, err = uuid.Parse(driver.Driver.ID) if err != nil { log.Error().Err(err).Msg("Postgresql Storage CreateDriver invalid ID") return err } departureJSON, err := json.Marshal(driver.Driver_departure_address) if err != nil { errMsg := "Postgresql Storage CreateDriver Error encoding departure Feature to JSON" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } preferencesJSON, err := json.Marshal(driver.Preferences) if err != nil { errMsg := "Postgresql Storage CreateDriver Error encoding Preferences to JSON" log.Error().Err(err).Msg(errMsg + err.Error()) return errors.New(errMsg) } carJSON, err := json.Marshal(driver.Car) if err != nil { errMsg := "Postgresql Storage CreateDriver Error encoding Car to JSON" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } if driver.Driver.Alias == "" || driver.Driver.Operator == "" { errMsg := "Postgresql Storage CreateDriver empty alias or operator FQDN." log.Error().Msg(errMsg) return errors.New(errMsg) } if driver.AvailabilitiesType != internal.Punctual && driver.AvailabilitiesType != internal.Regular { errMsg := "Postgresql Storage CreateDriver invalid Availabilities Type" log.Error().Msg(errMsg) return errors.New(errMsg) } if driver.Radius == 0 { errMsg := "Postgresql Storage CreateDriver Radius has to be defined" log.Error().Msg(errMsg) return errors.New(errMsg) } switch driver.AvailabilitiesType { case internal.Punctual: availabilities, err = json.Marshal(driver.PunctualAvailabilities) if err != nil { errMsg := "Postgresql Storage CreateDriver error converting Punctual availabilities" log.Error().Msg(errMsg) return errors.New(errMsg + err.Error()) } case internal.Regular: availabilities, err = json.Marshal(driver.RegularAvailabilities) if err != nil { errMsg := "Postgresql Storage CreateDriver error converting Regular availabilities" log.Error().Msg(errMsg) return errors.New(errMsg + err.Error()) } } _, err = s.DbConnection.Exec(fmt.Sprintf("INSERT INTO %s (driver_id,driver_departure_route,driver_radius,last_name,first_name,grade,alias,picture,verified_identity,preferences,availabilities_type,availabilities,operator , car) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)", s.Tables["drivers"]), driver.Driver.ID, departureJSON, driver.Radius, driver.Driver.LastName, driver.Driver.FirstName, driver.Driver.Grade, driver.Driver.Alias, driver.Driver.Picture, driver.Driver.VerifiedIdentity, preferencesJSON, driver.AvailabilitiesType, string(availabilities), driver.Driver.Operator, carJSON, ) if err != nil { fmt.Println(err) errMsg := "Postgresql Storage CreateDriver Error inserting data into the database" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } return nil } func (s PostgresqlStorage) GetDriver(driverID string) (driver internal.Driver, err error) { var preferencesJSON []byte var departureAddress []byte var availabilitiesJSON []byte var carJSON []byte err = s.DbConnection.QueryRow(fmt.Sprintf("SELECT driver_departure_route, driver_radius, last_name, first_name, grade, alias, picture, verified_identity, preferences, availabilities_type, availabilities, operator , driver_id , car FROM %s WHERE driver_id = $1", s.Tables["drivers"]), driverID). Scan( &departureAddress, &driver.Radius, &driver.Driver.LastName, &driver.Driver.FirstName, &driver.Driver.Grade, &driver.Driver.Alias, &driver.Driver.Picture, &driver.Driver.VerifiedIdentity, &preferencesJSON, &driver.AvailabilitiesType, &availabilitiesJSON, &driver.Driver.Operator, &driver.Driver.ID, &carJSON, ) if err != nil { errMsg := "Postgresql Storage GetDriver Error querying data from the database" log.Error().Err(err).Msg(errMsg) return driver, errors.New(errMsg + err.Error()) } err = json.Unmarshal(preferencesJSON, &driver.Preferences) if err != nil { errMsg := "Postgresql Storage GetDriver Error decoding Preferences from JSON" log.Error().Err(err).Msg(errMsg) return driver, errors.New(errMsg + err.Error()) } err = json.Unmarshal(carJSON, &driver.Car) if err != nil { errMsg := "Postgresql Storage GetDriver Error decoding Car from JSON" log.Error().Err(err).Msg(errMsg) return driver, errors.New(errMsg + err.Error()) } driver.Driver_departure_address, err = geojson.UnmarshalFeature(departureAddress) if err != nil { errMsg := "Postgresql Storage GetDriver Error decoding Driver departure route into GeoJSON" log.Error().Err(err).Msg(errMsg) return driver, errors.New(errMsg + err.Error()) } switch driver.AvailabilitiesType { case internal.Regular: err = json.Unmarshal(availabilitiesJSON, &driver.RegularAvailabilities) if err != nil { errMsg := "Postgresql Storage GetDriver Error decoding Regular Availabilities from JSON" log.Error().Err(err).Msg(errMsg) return driver, errors.New(errMsg + err.Error()) } case internal.Punctual: err = json.Unmarshal(availabilitiesJSON, &driver.PunctualAvailabilities) if err != nil { errMsg := "Postgresql Storage GetDriver Error decoding Punctual Availabilities from JSON" log.Error().Err(err).Msg(errMsg) return driver, errors.New(errMsg + err.Error()) } default: errMsg := "Postgresql Storage GetDriver Invalid Availabilities Type" log.Error().Msg(errMsg) return driver, errors.New(errMsg + err.Error()) } return driver, nil } func (s PostgresqlStorage) CreateBooking(booking internal.BookingRequest) (err error) { _, err = uuid.Parse(booking.Driver_id) if err != nil { log.Error().Err(err).Msg("Postgresql Storage CreateBooking invalid Driver ID") return err } _, err = uuid.Parse(booking.Passenger_id) if err != nil { log.Error().Err(err).Msg("Postgresql Storage CreateBooking invalid Passenger ID") return err } _, err = uuid.Parse(booking.ID) if err != nil { log.Error().Err(err).Msg("Postgresql Storage CreateBooking invalid Booking ID") return err } if booking.Operator == "" { log.Error().Err(err).Msg("Postgresql Storage CreateBooking empty operator") return err } _, err = s.DbConnection.Exec(fmt.Sprintf("INSERT INTO %s (booking_id , passenger_id , driver_id , operator, booking_status) VALUES ($1,$2,$3,$4,$5)", s.Tables["bookings"]), booking.ID, booking.Passenger_id, booking.Driver_id, booking.Operator, booking.Status, ) if err != nil { errMsg := "Postgresql Storage CreateBooking Error inserting data into the database" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } return nil } func (s PostgresqlStorage) GetBooking(id string) (booking internal.Booking, err error) { err = s.DbConnection.QueryRow(fmt.Sprintf("SELECT booking_id , passenger_id , driver_id , booking_status FROM %s WHERE booking_id = $1", s.Tables["bookings"]), id). Scan( &booking.ID, &booking.Passenger.ID, &booking.Driver.ID, &booking.Status, ) if err != nil { errMsg := "Postgresql Storage GetBooking Error getting booking" log.Error().Err(err).Msg(errMsg) return booking, errors.New(errMsg + err.Error()) } passenger, err := s.GetPassenger(booking.Passenger.ID) if err != nil { errMsg := "Postgresql Storage GetBooking Error getting passenger" log.Error().Err(err).Msg(errMsg) return booking, errors.New(errMsg + err.Error()) } booking.Passenger = passenger.Passenger booking.PassengerPickupAddress = passenger.Passenger_departure_address booking.PassengerDropAddress = passenger.Passenger_destination_address driver, err := s.GetDriver(booking.Driver.ID) if err != nil { errMsg := "Postgresql Storage GetBooking Error getting driver" log.Error().Err(err).Msg(errMsg) return booking, errors.New(errMsg + err.Error()) } booking.Driver = driver.Driver return booking, nil } func (s PostgresqlStorage) UpdateBookingStatus(id string, status internal.BookingStatus) (err error) { _, err = uuid.Parse(id) if err != nil { log.Error().Err(err).Msg("Postgresql Storage UpdateBookingStatus invalid Booking ID") return err } _, err = s.GetBooking(id) if err != nil { return errors.New(err.Error()) } query := fmt.Sprintf("UPDATE %s SET booking_status = $1 WHERE booking_id = $2", s.Tables["bookings"]) _, err = s.DbConnection.Exec(query, status, id) if err != nil { errMsg := "Postgresql Storage UpdateBookingStatus Error updating booking status" log.Error().Err(err).Msg(errMsg) return errors.New(errMsg + err.Error()) } return nil } func (s PostgresqlStorage) FilterUserBookingsByStatus(user_type string, status internal.BookingStatus, user_id string) (bookings []internal.Booking, err error) { _, err = uuid.Parse(user_id) if err != nil { return nil, errors.New("invalid uuid") } if user_type != "driver" && user_type != "passenger" { return nil, errors.New("invalid user type") } switch user_type { case "driver": rows, err := s.DbConnection.Query(fmt.Sprintf("SELECT booking_id, passenger_id, driver_id, booking_status FROM %s WHERE driver_id = $1 AND booking_status = $2", s.Tables["bookings"]), user_id, status) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var booking internal.Booking if err := rows.Scan(&booking.ID, &booking.Passenger.ID, &booking.Driver.ID, &booking.Status); err != nil { return nil, err } bookings = append(bookings, booking) } if err := rows.Err(); err != nil { return nil, err } case "passenger": rows, err := s.DbConnection.Query(fmt.Sprintf("SELECT booking_id, passenger_id, driver_id, booking_status FROM %s WHERE passenger_id = $1 AND booking_status = $2", s.Tables["bookings"]), user_id, status) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var booking internal.Booking if err := rows.Scan(&booking.ID, &booking.Passenger.ID, &booking.Driver.ID, &booking.Status); err != nil { return nil, err } bookings = append(bookings, booking) } if err := rows.Err(); err != nil { return nil, err } } return bookings, nil } func (s PostgresqlStorage) GetAllDrivers(date int64) (drivers []internal.Driver, err error) { rows, err := s.DbConnection.Query(fmt.Sprintf("SELECT driver_id, driver_departure_route, driver_radius, last_name, first_name, grade, alias, picture, verified_identity, preferences, availabilities_type, availabilities, operator, car FROM %s", s.Tables["drivers"])) if err != nil { return nil, err } defer rows.Close() for rows.Next() { var driver internal.Driver var departureRoute []byte var preferencesJSON []byte var availabilitiesJSON []byte var carJSON []byte if err := rows.Scan( &driver.Driver.ID, &departureRoute, &driver.Radius, &driver.Driver.LastName, &driver.Driver.FirstName, &driver.Driver.Grade, &driver.Driver.Alias, &driver.Driver.Picture, &driver.Driver.VerifiedIdentity, &preferencesJSON, &driver.AvailabilitiesType, &availabilitiesJSON, &driver.Driver.Operator, &carJSON, ); err != nil { return nil, err } if err := json.Unmarshal(departureRoute, &driver.Driver_departure_address); err != nil { return nil, err } if err := json.Unmarshal(preferencesJSON, &driver.Preferences); err != nil { return nil, err } if err := json.Unmarshal(carJSON, &driver.Car); err != nil { return nil, err } // Filter drivers based on date matching punctual or regular availabilities. if driver.AvailabilitiesType == internal.Punctual { if err := json.Unmarshal(availabilitiesJSON, &driver.PunctualAvailabilities); err != nil { return nil, err } // Convert the date to the day of the week and make it lowercase. dayOfWeek := strings.ToLower(time.Unix(date, 0).UTC().Format("Mon")) for _, avail := range driver.PunctualAvailabilities { // Extract the day part of the punctual driver's date and compare. availDate := time.Unix(avail.Date, 0) if strings.ToLower(availDate.Format("Mon")) == dayOfWeek { drivers = append(drivers, driver) break } } } else if driver.AvailabilitiesType == internal.Regular { if err := json.Unmarshal(availabilitiesJSON, &driver.RegularAvailabilities); err != nil { return nil, err } // Convert the date to the day of the week and make it lowercase. dayOfWeek := strings.ToLower(time.Unix(date, 0).UTC().Format("Mon")) fmt.Println(dayOfWeek) for _, avail := range driver.RegularAvailabilities { fmt.Println(strings.ToLower(avail.DayOfWeek)) if strings.ToLower(avail.DayOfWeek) == dayOfWeek { drivers = append(drivers, driver) break } } } } if err := rows.Err(); err != nil { return nil, err } return drivers, nil } func (s PostgresqlStorage) DriverJourneys(departure_route *geojson.Feature, departure_date int64) (drivers []internal.Driver, err error) { allDrivers, err := s.GetAllDrivers(departure_date) if err != nil { return nil, err } for _, driver := range allDrivers { // Check if the departure route is the same as the driver's departure route. if departure_route.Point().Y() == driver.Driver_departure_address.Point().Y() && departure_route.Point().X() == driver.Driver_departure_address.Point().X() { drivers = append(drivers, driver) } else { // Calculate the distance between the departure point and the driver's departure route. coords1 := departure_route.Geometry.(orb.Point) coords2 := driver.Driver_departure_address.Geometry.(orb.Point) distance := utils.Haversine(coords1[1], coords1[0], coords2[1], coords2[0]) if int64(distance) <= int64(driver.Radius) { drivers = append(drivers, driver) } } } return drivers, nil }