2023-10-20 11:41:39 +00:00
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"
2023-10-20 11:47:51 +00:00
"solidarity-service/utils"
2023-10-20 11:41:39 +00:00
"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 )
}
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 )
}
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 = 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 )
}
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 = 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 )
}
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 )
}
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 )
}
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 )
}
preferencesJSON , err := json . Marshal ( driver . Preferences )
if err != nil {
errMsg := "Postgresql Storage CreateDriver Error encoding Preferences to JSON"
log . Error ( ) . Err ( err ) . Msg ( errMsg )
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 )
}
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 )
}
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 = 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 )
}
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 = 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 = 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 )
}
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 )
}
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 )
}
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 )
}
default :
errMsg := "Postgresql Storage GetDriver Invalid Availabilities Type"
log . Error ( ) . Msg ( errMsg )
return driver , errors . New ( errMsg )
}
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 )
}
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 ,
)
fmt . Println ( err )
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 )
}
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" ) )
for _ , avail := range driver . RegularAvailabilities {
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
}