From b4c2a102367f27d00092d442bc05958bd47ff991 Mon Sep 17 00:00:00 2001 From: Pierre-Olivier Mercier Date: Mon, 18 Jun 2018 19:56:11 +0200 Subject: [PATCH] API server initial commit --- .gitmodules | 6 + server/.gitignore | 1 + server/api/handlers.go | 82 +++++++++++ server/api/router.go | 11 ++ server/api/version.go | 13 ++ server/main.go | 108 ++++++++++++++ server/models/db.go | 135 ++++++++++++++++++ server/models/fleet.go | 74 ++++++++++ server/models/mission.go | 95 ++++++++++++ server/models/requestor.go | 85 +++++++++++ server/models/vehicle.go | 85 +++++++++++ server/models/vehicle_location.go | 106 ++++++++++++++ server/vendor/github.com/gorilla/websocket | 1 + .../github.com/julienschmidt/httprouter | 1 + 14 files changed, 803 insertions(+) create mode 100644 .gitmodules create mode 100644 server/.gitignore create mode 100644 server/api/handlers.go create mode 100644 server/api/router.go create mode 100644 server/api/version.go create mode 100644 server/main.go create mode 100644 server/models/db.go create mode 100644 server/models/fleet.go create mode 100644 server/models/mission.go create mode 100644 server/models/requestor.go create mode 100644 server/models/vehicle.go create mode 100644 server/models/vehicle_location.go create mode 160000 server/vendor/github.com/gorilla/websocket create mode 160000 server/vendor/github.com/julienschmidt/httprouter diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..70a0d8e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "server/vendor/github.com/julienschmidt/httprouter"] + path = server/vendor/github.com/julienschmidt/httprouter + url = https://github.com/julienschmidt/httprouter.git +[submodule "server/vendor/github.com/gorilla/websocket"] + path = server/vendor/github.com/gorilla/websocket + url = https://github.com/gorilla/websocket.git diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..254defd --- /dev/null +++ b/server/.gitignore @@ -0,0 +1 @@ +server diff --git a/server/api/handlers.go b/server/api/handlers.go new file mode 100644 index 0000000..6fa5550 --- /dev/null +++ b/server/api/handlers.go @@ -0,0 +1,82 @@ +package api + +import ( + "encoding/json" + "fmt" + "io" + "log" + "net/http" + + "github.com/julienschmidt/httprouter" +) + +type DispatchFunction func(httprouter.Params, []byte) (interface{}, error) + +func apiHandler(f DispatchFunction) func(http.ResponseWriter, *http.Request, httprouter.Params) { + return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { + if addr := r.Header.Get("X-Forwarded-For"); addr != "" { + r.RemoteAddr = addr + } + log.Printf("%s \"%s %s\" [%s]\n", r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent()) + + // Read the body + if r.ContentLength < 0 || r.ContentLength > 6553600 { + http.Error(w, fmt.Sprintf("{errmsg:\"Request too large or request size unknown\"}"), http.StatusRequestEntityTooLarge) + return + } + var body []byte + if r.ContentLength > 0 { + tmp := make([]byte, 1024) + for { + n, err := r.Body.Read(tmp) + for j := 0; j < n; j++ { + body = append(body, tmp[j]) + } + if err != nil || n <= 0 { + break + } + } + } + + var ret interface{} + var err error = nil + + ret, err = f(ps, body) + + // Format response + resStatus := http.StatusOK + if err != nil { + ret = map[string]string{"errmsg": err.Error()} + resStatus = http.StatusBadRequest + log.Println(r.RemoteAddr, resStatus, err.Error()) + } + + if ret == nil { + ret = map[string]string{"errmsg": "Page not found"} + resStatus = http.StatusNotFound + } + + if str, found := ret.(string); found { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(resStatus) + io.WriteString(w, str) + } else if bts, found := ret.([]byte); found { + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment") + w.Header().Set("Content-Transfer-Encoding", "binary") + w.WriteHeader(resStatus) + w.Write(bts) + } else if j, err := json.Marshal(ret); err != nil { + w.Header().Set("Content-Type", "application/json") + http.Error(w, fmt.Sprintf("{\"errmsg\":%q}", err), http.StatusInternalServerError) + } else { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(resStatus) + w.Write(j) + } + } +} + +func notFound(ps httprouter.Params, _ []byte) (interface{}, error) { + return nil, nil +} diff --git a/server/api/router.go b/server/api/router.go new file mode 100644 index 0000000..a6bd873 --- /dev/null +++ b/server/api/router.go @@ -0,0 +1,11 @@ +package api + +import ( + "github.com/julienschmidt/httprouter" +) + +var router = httprouter.New() + +func Router() *httprouter.Router { + return router +} diff --git a/server/api/version.go b/server/api/version.go new file mode 100644 index 0000000..810829b --- /dev/null +++ b/server/api/version.go @@ -0,0 +1,13 @@ +package api + +import ( + "github.com/julienschmidt/httprouter" +) + +func init() { + router.GET("/version", apiHandler(showVersion)) +} + +func showVersion(_ httprouter.Params, body []byte) (interface{}, error) { + return map[string]interface{}{"version": 0.1}, nil +} diff --git a/server/main.go b/server/main.go new file mode 100644 index 0000000..794bd44 --- /dev/null +++ b/server/main.go @@ -0,0 +1,108 @@ +package main + +import ( + "context" + "flag" + "fmt" + "log" + "net/http" + "net/url" + "os" + "os/signal" + "path" + "strings" + "syscall" + + "git.nemunai.re/filicop/server/api" + "git.nemunai.re/filicop/server/models" +) + +type ResponseWriterPrefix struct { + real http.ResponseWriter + prefix string +} + +func (r ResponseWriterPrefix) Header() http.Header { + return r.real.Header() +} + +func (r ResponseWriterPrefix) WriteHeader(s int) { + if v, exists := r.real.Header()["Location"]; exists { + r.real.Header().Set("Location", r.prefix+v[0]) + } + r.real.WriteHeader(s) +} + +func (r ResponseWriterPrefix) Write(z []byte) (int, error) { + return r.real.Write(z) +} + +func StripPrefix(prefix string, h http.Handler) http.Handler { + if prefix == "" { + return h + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if prefix != "/" && r.URL.Path == "/" { + http.Redirect(w, r, prefix+"/", http.StatusFound) + } else if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) { + r2 := new(http.Request) + *r2 = *r + r2.URL = new(url.URL) + *r2.URL = *r.URL + r2.URL.Path = p + h.ServeHTTP(ResponseWriterPrefix{w, prefix}, r2) + } else { + h.ServeHTTP(w, r) + } + }) +} + +func main() { + // Read parameters from command line + var bind = flag.String("bind", "127.0.0.1:8081", "Bind port/socket") + var dsn = flag.String("dsn", filicop.DSNGenerator(), "DSN to connect to the MySQL server") + var baseURL = flag.String("baseurl", "/", "URL prepended to each URL") + flag.Parse() + + // Sanitize options + if *baseURL != "/" { + tmp := path.Clean(*baseURL) + baseURL = &tmp + } else { + tmp := "" + baseURL = &tmp + } + + log.Println("Opening database...") + if err := filicop.DBInit(*dsn); err != nil { + log.Fatal("Cannot open the database: ", err) + } + defer filicop.DBClose() + + log.Println("Creating database...") + if err := filicop.DBCreate(); err != nil { + log.Fatal("Cannot create database: ", err) + } + + // Prepare graceful shutdown + interrupt := make(chan os.Signal, 1) + signal.Notify(interrupt, os.Interrupt, syscall.SIGTERM) + + srv := &http.Server{ + Addr: *bind, + Handler: StripPrefix(*baseURL, api.Router()), + } + + // Serve content + go func() { + log.Fatal(srv.ListenAndServe()) + }() + log.Println(fmt.Sprintf("Ready, listening on %s", *bind)) + + // Wait shutdown signal + <-interrupt + + log.Print("The service is shutting down...") + srv.Shutdown(context.Background()) + log.Println("done") +} diff --git a/server/models/db.go b/server/models/db.go new file mode 100644 index 0000000..4b3a5ad --- /dev/null +++ b/server/models/db.go @@ -0,0 +1,135 @@ +package filicop + +import ( + "database/sql" + "log" + "os" + "time" + + _ "github.com/go-sql-driver/mysql" +) + +// db stores the connection to the database +var db *sql.DB + +// DSNGenerator returns DSN filed with values from environment +func DSNGenerator() string { + db_user := "filicop" + db_password := "filicop" + db_host := "" + db_db := "filicop" + + if v, exists := os.LookupEnv("MYSQL_HOST"); exists { + db_host = v + } + if v, exists := os.LookupEnv("MYSQL_PASSWORD"); exists { + db_password = v + } else if v, exists := os.LookupEnv("MYSQL_ROOT_PASSWORD"); exists { + db_user = "root" + db_password = v + } + if v, exists := os.LookupEnv("MYSQL_USER"); exists { + db_user = v + } + if v, exists := os.LookupEnv("MYSQL_DATABASE"); exists { + db_db = v + } + + return db_user + ":" + db_password + "@" + db_host + "/" + db_db +} + +// DBInit establishes the connection to the database +func DBInit(dsn string) (err error) { + if db, err = sql.Open("mysql", dsn+"?parseTime=true&foreign_key_checks=1"); err != nil { + return + } + + _, err = db.Exec(`SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO';`) + for i := 0; err != nil && i < 15; i += 1 { + if _, err = db.Exec(`SET SESSION sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO';`); err != nil && i <= 5 { + log.Println("An error occurs when trying to connect to DB, will retry in 2 seconds: ", err) + time.Sleep(2 * time.Second) + } + } + + return +} + +// DBCreate creates all necessary tables used by the package +func DBCreate() error { + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS fleets( + id_fleet INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + label VARCHAR(255) NOT NULL +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS vehicles( + id_vehicle INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_fleet INTEGER NOT NULL, + label VARCHAR(255) NOT NULL, + FOREIGN KEY(id_fleet) REFERENCES fleets(id_fleet) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS vehicle_locations( + id_location INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_vehicle INTEGER NOT NULL, + date DATETIME NOT NULL, + location POINT NOT NULL, + FOREIGN KEY(id_vehicle) REFERENCES vehicles(id_vehicle) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS requestors( + id_requestor INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_fleet INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + FOREIGN KEY(id_fleet) REFERENCES fleets(id_fleet) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + if _, err := db.Exec(` +CREATE TABLE IF NOT EXISTS missions( + id_mission INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, + id_requestor INTEGER NOT NULL, + id_vehicle INTEGER, + creation DATETIME NOT NULL, + location_source POINT NOT NULL, + location_destination POINT NOT NULL, + FOREIGN KEY(id_vehicle) REFERENCES vehicles(id_vehicle), + FOREIGN KEY(id_requestor) REFERENCES requestors(id_requestor) +) DEFAULT CHARACTER SET = utf8 COLLATE = utf8_bin; +`); err != nil { + return err + } + return nil +} + +// DBClose closes the connection to the database +func DBClose() error { + return db.Close() +} + +func DBPrepare(query string) (*sql.Stmt, error) { + return db.Prepare(query) +} + +func DBQuery(query string, args ...interface{}) (*sql.Rows, error) { + return db.Query(query, args...) +} + +func DBExec(query string, args ...interface{}) (sql.Result, error) { + return db.Exec(query, args...) +} + +func DBQueryRow(query string, args ...interface{}) *sql.Row { + return db.QueryRow(query, args...) +} diff --git a/server/models/fleet.go b/server/models/fleet.go new file mode 100644 index 0000000..95b8d88 --- /dev/null +++ b/server/models/fleet.go @@ -0,0 +1,74 @@ +package filicop + +import () + +type Fleet struct { + Id int64 `json:"id"` + Label string `json:"label"` +} + +func GetFleets() (fleets []Fleet, err error) { + if rows, errr := DBQuery("SELECT id_fleet, label FROM fleets"); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var f Fleet + if err = rows.Scan(&f.Id, &f.Label); err != nil { + return + } + fleets = append(fleets, f) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func GetFleet(id int64) (f Fleet, err error) { + err = DBQueryRow("SELECT id_fleet, label FROM fleets WHERE id_fleet = ?", id).Scan(&f.Id, &f.Label) + return +} + +func NewFleet(label string) (Fleet, error) { + if res, err := DBExec("INSERT INTO fleets (label) VALUES (?)", label); err != nil { + return Fleet{}, err + } else if fid, err := res.LastInsertId(); err != nil { + return Fleet{}, err + } else { + return Fleet{fid, label}, nil + } +} + +func (f Fleet) Update() (int64, error) { + if res, err := DBExec("UPDATE fleets SET label = ? WHERE id_fleet = ?", f.Label, f.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (f Fleet) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM fleets WHERE id_fleet = ?", f.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearFleets() (int64, error) { + if res, err := DBExec("DELETE FROM fleets"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/server/models/mission.go b/server/models/mission.go new file mode 100644 index 0000000..9959a76 --- /dev/null +++ b/server/models/mission.go @@ -0,0 +1,95 @@ +package filicop + +import ( + "time" +) + +type Mission struct { + Id int64 `json:"id"` + IdRequestor int64 `json:"id_requestor"` + IdVehicle *int64 `json:"id_vehicle"` + Creation time.Time `json:"creation"` + LocationFrom Point `json:"from"` + LocationTo Point `json:"to"` +} + +func GetMissions() (missions []Mission, err error) { + if rows, errr := DBQuery("SELECT id_mission, id_requestor, id_vehicle, creation, location_source, location_destination FROM missions"); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var m Mission + if err = rows.Scan(&m.Id, &m.IdRequestor, &m.IdVehicle, &m.Creation, &m.LocationFrom, &m.LocationTo); err != nil { + return + } + missions = append(missions, m) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func GetMission(id int64) (m Mission, err error) { + err = DBQueryRow("SELECT id_mission, id_requestor, id_vehicle, creation, location_source,location_destination FROM missions WHERE id_mission = ?", id).Scan(&m.Id, &m.IdRequestor, &m.IdVehicle, &m.Creation, &m.LocationFrom, &m.LocationTo) + return +} + +func (r Requestor) NewMission(from Point, to Point, vehicle *Vehicle) (Mission, error) { + var vid *int64 = nil + if vehicle != nil { + vid = &vehicle.Id + } + + if res, err := DBExec("INSERT INTO missions (id_requestor, id_vehicle, location_source, location_destination) VALUES (?, ?, ?, ?)", r.Id, vid, from, to); err != nil { + return Mission{}, err + } else if mid, err := res.LastInsertId(); err != nil { + return Mission{}, err + } else { + return Mission{mid, r.Id, vid, time.Now(), from, to}, nil + } +} + +func (m Mission) Update() (int64, error) { + if res, err := DBExec("UPDATE missions SET id_requestor = ?, creation = ?, location_source = ?, location_destination = ? WHERE id_mission = ?", m.IdRequestor, m.Creation, m.LocationFrom, m.LocationTo, m.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (m Mission) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM missions WHERE id_mission = ?", m.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearMissions() (int64, error) { + if res, err := DBExec("DELETE FROM missions"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (r Requestor) ClearMissions() (int64, error) { + if res, err := DBExec("DELETE FROM missions WHERE id_requestor = ?", r.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/server/models/requestor.go b/server/models/requestor.go new file mode 100644 index 0000000..4a44e48 --- /dev/null +++ b/server/models/requestor.go @@ -0,0 +1,85 @@ +package filicop + +import () + +type Requestor struct { + Id int64 `json:"id"` + IdFleet int64 `json:"id_fleet"` + Name string `json:"name"` +} + +func GetRequestors() (requestors []Requestor, err error) { + if rows, errr := DBQuery("SELECT id_requestor, id_fleet, name FROM requestors"); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var r Requestor + if err = rows.Scan(&r.Id, &r.IdFleet, &r.Name); err != nil { + return + } + requestors = append(requestors, r) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func GetRequestor(id int64) (r Requestor, err error) { + err = DBQueryRow("SELECT id_requestor, id_fleet, name FROM requestors WHERE id_requestor = ?", id).Scan(&r.Id, &r.IdFleet, &r.Name) + return +} + +func (f Fleet) NewRequestor(name string) (Requestor, error) { + if res, err := DBExec("INSERT INTO requestors (id_fleet, name) VALUES (?, ?)", f.Id, name); err != nil { + return Requestor{}, err + } else if rid, err := res.LastInsertId(); err != nil { + return Requestor{}, err + } else { + return Requestor{rid, f.Id, name}, nil + } +} + +func (r Requestor) Update() (int64, error) { + if res, err := DBExec("UPDATE requestors SET id_fleet = ?, name = ? WHERE id_requestor = ?", r.IdFleet, r.Name, r.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (r Requestor) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM requestors WHERE id_requestor = ?", r.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearRequestors() (int64, error) { + if res, err := DBExec("DELETE FROM requestors"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (f Fleet) ClearRequestors() (int64, error) { + if res, err := DBExec("DELETE FROM requestors WHERE id_fleet = ?", f.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/server/models/vehicle.go b/server/models/vehicle.go new file mode 100644 index 0000000..4590af8 --- /dev/null +++ b/server/models/vehicle.go @@ -0,0 +1,85 @@ +package filicop + +import () + +type Vehicle struct { + Id int64 `json:"id"` + IdFleet int64 `json:"id_fleet"` + Label string `json:"label"` +} + +func GetVehicles() (vehicles []Vehicle, err error) { + if rows, errr := DBQuery("SELECT id_vehicle, id_fleet, label FROM vehicles"); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var v Vehicle + if err = rows.Scan(&v.Id, &v.IdFleet, &v.Label); err != nil { + return + } + vehicles = append(vehicles, v) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func GetVehicle(id int64) (v Vehicle, err error) { + err = DBQueryRow("SELECT id_vehicle, id_fleet, label FROM vehicles WHERE id_vehicle = ?", id).Scan(&v.Id, &v.IdFleet, &v.Label) + return +} + +func (f Fleet) NewVehicle(label string) (Vehicle, error) { + if res, err := DBExec("INSERT INTO vehicles (id_fleet, label) VALUES (?, ?)", f.Id, label); err != nil { + return Vehicle{}, err + } else if vid, err := res.LastInsertId(); err != nil { + return Vehicle{}, err + } else { + return Vehicle{vid, f.Id, label}, nil + } +} + +func (v Vehicle) Update() (int64, error) { + if res, err := DBExec("UPDATE vehicles SET id_fleet = ?, label = ? WHERE id_vehicle = ?", v.IdFleet, v.Label, v.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (v Vehicle) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM vehicles WHERE id_vehicle = ?", v.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearVehicles() (int64, error) { + if res, err := DBExec("DELETE FROM vehicles"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (f Fleet) ClearVehicles() (int64, error) { + if res, err := DBExec("DELETE FROM vehicles WHERE id_fleet = ?", f.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/server/models/vehicle_location.go b/server/models/vehicle_location.go new file mode 100644 index 0000000..6c45280 --- /dev/null +++ b/server/models/vehicle_location.go @@ -0,0 +1,106 @@ +package filicop + +import ( + "time" +) + +type Point struct { + X float32 `json:"x"` + Y float32 `json:"y"` +} + +type VehicleLocation struct { + Id int64 `json:"id"` + IdVehicle int64 `json:"id_vehicle"` + Location Point `json:"location"` + Date time.Time `json:"date"` +} + +func (v Vehicle) GetLocations() (locations []VehicleLocation, err error) { + if rows, errr := DBQuery("SELECT id_location, id_vehicle, location, date FROM vehicles_locations WHERE id_vehicle = ?", v.Id); errr != nil { + return nil, errr + } else { + defer rows.Close() + + for rows.Next() { + var l VehicleLocation + if err = rows.Scan(&l.Id, &l.IdVehicle, &l.Location, &l.Date); err != nil { + return + } + locations = append(locations, l) + } + if err = rows.Err(); err != nil { + return + } + + return + } +} + +func GetVehicleLocation(id int64) (l VehicleLocation, err error) { + err = DBQueryRow("SELECT id_location, id_vehicle, location, date FROM vehicle_locations WHERE id_location = ?", id).Scan(&l.Id, &l.IdVehicle, &l.Location, &l.Date) + return +} + +func (v Vehicle) GetLocation(id int64) (l VehicleLocation, err error) { + err = DBQueryRow("SELECT id_location, id_vehicle, location, date FROM vehicle_locations WHERE id_location = ? AND id_vehicle = ?", id, v.Id).Scan(&l.Id, &l.IdVehicle, &l.Location, &l.Date) + return +} + +func (l VehicleLocation) GetVehicle() (Vehicle, error) { + return GetVehicle(l.IdVehicle) +} + +func (l VehicleLocation) SetVehicle(v Vehicle) { + l.IdVehicle = v.Id +} + +func (v Vehicle) NewLocation(point Point) (VehicleLocation, error) { + if res, err := DBExec("INSERT INTO vehicle_locations (id_vehicle, location) VALUES (?, ?)", v.Id, point); err != nil { + return VehicleLocation{}, err + } else if lid, err := res.LastInsertId(); err != nil { + return VehicleLocation{}, err + } else { + return VehicleLocation{lid, v.Id, point, time.Now()}, nil + } +} + +func (l VehicleLocation) Update() (int64, error) { + if res, err := DBExec("UPDATE vehicle_locations SET id_vehicle = ?, location = ?, date = ? WHERE id_location = ?", l.IdVehicle, l.Location, l.Date, l.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (l VehicleLocation) Delete() (int64, error) { + if res, err := DBExec("DELETE FROM vehicle_locations WHERE id_location = ?", l.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func ClearVehicleLocations() (int64, error) { + if res, err := DBExec("DELETE FROM vehicle_locations"); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} + +func (v Vehicle) ClearVehicleLocations() (int64, error) { + if res, err := DBExec("DELETE FROM vehicle_locations WHERE id_vehicle = ?", v.Id); err != nil { + return 0, err + } else if nb, err := res.RowsAffected(); err != nil { + return 0, err + } else { + return nb, err + } +} diff --git a/server/vendor/github.com/gorilla/websocket b/server/vendor/github.com/gorilla/websocket new file mode 160000 index 0000000..5ed622c --- /dev/null +++ b/server/vendor/github.com/gorilla/websocket @@ -0,0 +1 @@ +Subproject commit 5ed622c449da6d44c3c8329331ff47a9e5844f71 diff --git a/server/vendor/github.com/julienschmidt/httprouter b/server/vendor/github.com/julienschmidt/httprouter new file mode 160000 index 0000000..adbc77e --- /dev/null +++ b/server/vendor/github.com/julienschmidt/httprouter @@ -0,0 +1 @@ +Subproject commit adbc77eec0d91467376ca515bc3a14b8434d0f18