diff --git a/.gitignore b/.gitignore index 5e07caf..088ba6b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,23 +1,10 @@ -.DS_Store +# Generated by Cargo +# will have compiled files and executables +/target/ -node_modules/ - -Pipfile -/.venv -*.pyc -__pycache__/ -.cache -pipenv.egg-info/ -*.swp -.sublime-text-git-autocommit -docs/_build -dist -build -.DS_Store -test_project -Pipfile.lock -/.vscode/ -.idea - -CS_CityIO_Backend +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1346edb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cs_cityio_backend" +version = "0.1.0" +authors = ["yasushi"] +edition = "2018" + +[dependencies] +actix-web = "1.0.0-alpha.4" + +bytes = "0.4" +futures = "0.1" +env_logger = "*" + +serde = "1.0" +serde_json = "1.0" +serde_derive = "1.0" +json = "*" +postgres = {version = "0.15", features = ["with-serde_json"]} + +sha256 = { path="../sha256" } + diff --git a/log/.gitignore b/log/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/log/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/main.go b/main.go deleted file mode 100644 index cdccd4c..0000000 --- a/main.go +++ /dev/null @@ -1,171 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "time" - - "github.com/CityScope/CS_CityIO_Backend/models" - "github.com/google/logger" - "github.com/labstack/echo" - "github.com/labstack/echo/middleware" -) - -var port string = "8080" -var prefix string = "/api" - -var tables map[string]interface{} = make(map[string]interface{}) - -// GET "/" -func getFrontend(c echo.Context) error { - logger.Info("GET frontend") - return c.Redirect(http.StatusMovedPermanently, "http://cityscope.media.mit.edu/CS_CityIO_Frontend/") -} - -// GET "/api" -func getAPI(c echo.Context) error { - logger.Info("GET request " + prefix) - return c.JSON(http.StatusOK, "OK") -} - -// GET "/api/tables/list" -func listTables(c echo.Context) error { - logger.Info("GET /table/list") - tableList := make([]string, 0, len(tables)) - baseUrl := "https://cityio.media.mit.edu/api/table/" - for k := range tables { - tableList = append(tableList, baseUrl+k) - } - return c.JSON(http.StatusOK, tableList) -} - -// GET "api/table/:tableName" -func getTable(c echo.Context) error { - tableName := c.Param("tableName") - logger.Infof("GET /table/%v", tableName) - table, ok := tables[tableName] - - if ok { - logger.Infof(" found table: %v", tableName) - return c.JSON(http.StatusOK, table) - } else { - logger.Infof(" could not find table: %v", tableName) - return c.JSON(http.StatusNotFound, - map[string]string{"status": "table not found"}) - } -} - -// GET "api/table/clear/:tableName" -func clearTable(c echo.Context) error { - tableName := c.Param("tableName") - logger.Infof("GET /table/clear/%v", tableName) - //TODO: do we want to delete it? perhaps inactivate it? - delete(tables, tableName) - return c.JSON(http.StatusOK, - map[string]string{"status": "deleted " + tableName}) -} - -// POST "api/table/update/:tableName" -func postTable(c echo.Context) error { - - data := make(map[string]interface{}) - tableName := c.Param("tableName") - logger.Infof("POST /table/update/%v", tableName) - - err := json.NewDecoder(c.Request().Body).Decode(&data) - - if err != nil { - log.Printf("error: %v\n", err.Error()) - logger.Errorf("error decoding table data from json: %v\n", err.Error()) - } - - byteData, _ := json.Marshal(data) - - table := models.Table{} - - err = json.Unmarshal(byteData, &table) - if err != nil { - log.Printf("[%v]: invalid type: %v\n", tableName, err.Error()) - tables[tableName] = data - } else { - log.Printf("[%v]: valid type \n", tableName) - - hash := table.Hash() - update := true - - // don't update when the hash is the same - if lastTable, ok := tables[tableName]; ok { - lt, yep := lastTable.(models.Table) - if yep && hash == lt.Meta.Id { - update = false - } - } - - if update { - table.Qualify(hash) - tables[tableName] = table - } - } - - logger.Info("POST SUCCESS") - return c.JSON(http.StatusOK, - map[string]string{tableName: "done"}) -} - -func main() { - - t := time.Now() - time := fmt.Sprintf("%d-%02d-%02d_%02d:%02d:%02d", - t.Year(), - t.Month(), - t.Day(), - t.Hour(), - t.Minute(), - t.Second()) - - logPath := fmt.Sprintf("./log/cityio_%v.log", time) - lf, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660) - - if err != nil { - logger.Fatalf("Failed to open log file: %v", err) - } - - defer lf.Close() - defer logger.Init("Logger", false, false, lf).Close() - - ////////////////////////////////////////////// - - router := echo.New() - - tables = make(map[string]interface{}) - - router.Use(middleware.CORSWithConfig(middleware.CORSConfig{ - AllowOrigins: []string{"*"}, - AllowHeaders: []string{ - echo.HeaderOrigin, - echo.HeaderContentType, - echo.HeaderAccept}, - AllowMethods: []string{echo.GET, echo.POST}, - })) - - // Frontend redirects to city IO frontend - router.GET("/", getFrontend) - - // API endpoints - - router.GET(prefix, getAPI) - - router.POST(prefix+"/table/update/:tableName", postTable) - - router.GET(prefix+"/table/clear/:tableName", clearTable) - - router.GET(prefix+"/table/:tableName", getTable) - - router.GET(prefix+"/tables/list", listTables) - - logger.Info("server started") - logger.Fatal(router.Start(":" + port)) -} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index eda7c19..0000000 --- a/main_test.go +++ /dev/null @@ -1,214 +0,0 @@ -package main - -import ( - "bytes" - "encoding/json" - "github.com/CityScope/CS_CityIO_Backend/models" - "github.com/labstack/echo" - "github.com/stretchr/testify/assert" - "net/http" - "net/http/httptest" - "testing" -) - -// isolated tests for each handler functions - -func TestSimpleGet(t *testing.T) { - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - - if assert.NoError(t, getAPI(c)) { - assert.Equal(t, http.StatusOK, rec.Code) - assert.Equal(t, "\"OK\"", rec.Body.String()) - } -} - -func TestGetFrontend(t *testing.T) { - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - - if assert.NoError(t, getFrontend(c)) { - assert.Equal(t, http.StatusMovedPermanently, rec.Code) - } -} - -func TestGetListTables(t *testing.T) { - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - - listTables(c) - - var tables map[string]interface{} - - err := json.Unmarshal(rec.Body.Bytes(), &tables) - - if err == nil { - assert.Equal(t, &tables, "[]") - } -} - -func TestPostTable(t *testing.T) { - e := echo.New() - - // make a sample table - table := models.CreateSampleTable() - byteData, _ := json.Marshal(table) - - // make new request - req := httptest.NewRequest( - http.MethodPost, - "/api/table/update/:tableName", - bytes.NewReader(byteData)) - - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - c.SetPath("api/table/update/:tableName") - c.SetParamNames("tableName") - c.SetParamValues("test_table") - - postTable(c) - - result := make(map[string]string) - err := json.Unmarshal(rec.Body.Bytes(), &result) - - if err == nil { - assert.Equal(t, result["test_table"], "done") - } -} - -func TestGetTable(t *testing.T) { - table := models.CreateSampleTable() - - tables[table.Header.Name] = table - - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - c.SetPath("api/table/:tableName") - c.SetParamNames("tableName") - c.SetParamValues(table.Header.Name) - - getTable(c) - - result := models.Table{} - - err := json.Unmarshal(rec.Body.Bytes(), &result) - delete(tables, "test_table") - - if err == nil { - assert.Equal(t, result.Header.Name, table.Header.Name) - } - -} - -func TestClearTable(t *testing.T) { - table := models.CreateSampleTable() - tables[table.Header.Name] = table - - e := echo.New() - req := httptest.NewRequest(http.MethodGet, "/", nil) - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - c.SetPath("api/table/clear/:tableName") - c.SetParamNames("tableName") - c.SetParamValues(table.Header.Name) - - clearTable(c) - - assert.Equal(t, len(tables), 0) -} - -// one example pass of -// post, list, get, clear - -func TestSinglePass(t *testing.T) { - - table := models.CreateSampleTable() - byteData, _ := json.Marshal(table) - - // POST a table - e := echo.New() - req := httptest.NewRequest(http.MethodPost, "/", bytes.NewReader(byteData)) - rec := httptest.NewRecorder() - - c := e.NewContext(req, rec) - c.SetPath("api/table/update/:tableName") - c.SetParamNames("tableName") - c.SetParamValues(table.Header.Name) - - postTable(c) - - postResult := make(map[string]string) - err := json.Unmarshal(rec.Body.Bytes(), &postResult) - - if err != nil { - assert.Equal(t, postResult[table.Header.Name], "done") - } - - // list - req = httptest.NewRequest(http.MethodGet, "/", nil) - rec = httptest.NewRecorder() - - c = e.NewContext(req, rec) - - listTables(c) - - var listResult []string - - err = json.Unmarshal(rec.Body.Bytes(), &listResult) - - if err != nil { - assert.Equal(t, listResult[0], table.Header.Name) - } - - // GET a table - req = httptest.NewRequest(http.MethodGet, "/", nil) - rec = httptest.NewRecorder() - - c = e.NewContext(req, rec) - c.SetPath("api/table/:tableName") - c.SetParamNames("tableName") - c.SetParamValues(table.Header.Name) - - getTable(c) - - getResult := models.Table{} - - err = json.Unmarshal(rec.Body.Bytes(), &getResult) - - if err != nil { - assert.Equal(t, getResult.Header.Name, table.Header.Name) - } - - // clear a table - req = httptest.NewRequest(http.MethodGet, "/", nil) - rec = httptest.NewRecorder() - - c = e.NewContext(req, rec) - c.SetPath("api/table/clear/:tableName") - c.SetParamNames("tableName") - c.SetParamValues(table.Header.Name) - - clearTable(c) - - clearResult := []string{} - - err = json.Unmarshal(rec.Body.Bytes(), &clearResult) - - if err != nil { - assert.Equal(t, len(clearResult), 0) - } -} diff --git a/models/models.go b/models/models.go deleted file mode 100644 index 0c7d943..0000000 --- a/models/models.go +++ /dev/null @@ -1,143 +0,0 @@ -package models - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "math/rand" - "time" -) - -// Table struct -type Table struct { - Meta Meta `json:"meta"` - Header Header `json:"header"` - Grid []Cell `json:"grid"` - Objects interface{} `json:"objects"` -} - -// helper to create Empty Meta data -// func CreateEmptyMeta() Meta { -// now := time.Now() -// ts := int(now.UnixNano() / 1000000) -// return Meta{"", ts, "2.0.0"} -// } - -func (t *Table) CreateEmptyMeta() { - now := time.Now() - ts := int(now.UnixNano() / 1000000) - t.Meta = Meta{"", ts, "2.0.0"} -} - -func (t *Table) UpdateTimeStamp() { - t.Meta.Timestamp = int(time.Now().UnixNano() / 1000000) -} - -func CreateSampleTable() Table { - t := Table{} - t.CreateEmptyMeta() - t.Header.CreateSampleHeader() - for i := 0; i < 4; i++ { - t.Grid = append(t.Grid, CreateSampleCell()) - } - - t.Objects = "this is a sample table for testing purposes" - - return t -} - -// Meta struct -// data will be (over) written by the server -type Meta struct { - Id string `json:"id"` - Timestamp int `json:"timestamp"` - Apiv string `json:"apiv"` -} - -func (t *Table) Qualify(hash string) { - t.UpdateTimeStamp() - t.Meta.Apiv = "2.1.0" - t.Meta.Id = hash -} - -// hash of the Grid, Header, Objects -func (t *Table) Hash() string { - headerBytes, _ := json.Marshal(t.Header) - gridBytes, _ := json.Marshal(t.Grid) - objectsBytes, _ := json.Marshal(t.Objects) - bytes := append(append(headerBytes[:], gridBytes[:]...), objectsBytes[:]...) - hashed := sha256.Sum256(bytes) - return fmt.Sprintf("%64x", hashed) -} - -// Header has info that is unlikely to chage -type Header struct { - Name string `json:"name"` - Spatial Spatial `json:"spatial"` - Owner Owner `json:"owner"` - Block []string `json:"block"` - Mapping interface{} `json:"mapping"` -} - -func (h *Header) CreateSampleHeader() { - h.Name = "sample_table" - h.Spatial.CreateSampleSpatialData(2, 2) - h.Owner.CreateSampleOwner() - h.Block = []string{"type", "rot"} - - m := make(map[int]string) - m[0] = "RS" - m[1] = "RM" - m[2] = "RL" - m[3] = "OS" - m[4] = "OM" - m[5] = "OL" - - h.Mapping = m -} - -// Owner is the info to addres the ownership -type Owner struct { - Name string `json:"name"` - Title string `json:"title"` - Institute string `json:"institute"` -} - -func (o *Owner) CreateSampleOwner() { - o.Name = "Yasushi Sakai" - o.Title = "Research Assistant" - o.Institute = "MIT Media Lab" -} - -type Spatial struct { - Nrows byte `json:"nrows"` - Ncols byte `json:"ncols"` - PhysicalLongitude float64 `json:"physical_longitude"` - PhysicalLatitude float64 `json:"physical_latitude"` - Longitude float64 `json:"longitude"` - Latitude float64 `json:"latitude"` - CellSize float64 `json:"cellSize"` - Rotation float64 `json:"rotation"` -} - -func (s *Spatial) CreateSampleSpatialData(r byte, c byte) { - s.Nrows = r - s.Ncols = c - s.PhysicalLongitude = -71.08768 - s.PhysicalLatitude = 42.3608 - s.Longitude = -71.08768 - s.Latitude = 42.3608 - s.CellSize = 10.0 - s.Rotation = 0.0 -} - -// Cell is data for each grid cell, we don't -// know what will be inside prior -type Cell interface{} - -func CreateSampleCell() Cell { - return []int{rand.Intn(4), 90} -} - -// OldTable is a table with formats before 2.0.0 -type OldTable interface{} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}