func TestReadItem2(t *testing.T) { readItem := new(data.Item) testRead(t, "/items/"+strconv.FormatInt(item.ID, 10), readItem) readItem.StartTime = item.StartTime // hack readItem.StopTime = item.StopTime // hack structJSONCompare(t, item, readItem) }
func scanItem(item *data.Item, rows *sql.Rows) error { var startTimeString, stopTimeString string err := rows.Scan( &item.ID, &item.Type, &item.Author, &item.ThumbnailURI, &item.ContentURI, &item.UploadURI, &item.ContentToken, &item.HasHeading, &item.Heading, &item.HasGeo, &item.Lat, &item.Lng, &item.Radius, &item.Live, &startTimeString, &stopTimeString, ) if err != nil { return err } // TODO: this is a hacky fix for null times if startTimeString == "0000-00-00 00:00:00" { startTimeString = "0001-01-01 00:00:00" } startTime, err := time.Parse("2006-01-02 15:04:05", startTimeString) // this assumes UTC as timezone if err != nil { log.Println("Item scanner failed to parse time. " + startTimeString) return err } item.StartTime = startTime // TODO: this is a hacky fix for null times if stopTimeString == "0000-00-00 00:00:00" { stopTimeString = "0001-01-01 00:00:00" } stopTime, err := time.Parse("2006-01-02 15:04:05", stopTimeString) // this assumes UTC as timezone if err != nil { log.Println("Item scanner failed to parse time. " + stopTimeString) return err } item.StopTime = stopTime return nil }
func TestReadItem3(t *testing.T) { readItem := new(data.Item) testRead(t, "/items/"+strconv.FormatInt(item.ID, 10), readItem) readItem.StartTime = item.StartTime // hack readItem.StopTime = item.StopTime // hack if len(readItem.Terms) < 1 { t.Error("There should be a term here") } readItem.Terms[0].UpdateTime = term.UpdateTime structJSONCompare(t, item, readItem) }
func TestSelectItem(t *testing.T) { selectedItem := new(data.Item) selectedItem.ID = item.ID err := Select(selectedItem) if err != nil { t.Error(err) } t.Log(item.StartTime.UTC()) t.Log(selectedItem.StartTime.UTC()) t.Log(item.StopTime.UTC()) t.Log(selectedItem.StopTime.UTC()) selectedItem.StartTime = item.StartTime // hack selectedItem.StopTime = item.StopTime // hack structJSONCompare(t, item, selectedItem) }
// Generic Update handler func Update(w http.ResponseWriter, r *http.Request) { user, permissions := auth.Challenge(w, r, true) if user == nil || permissions < 1 { http.Error(w, "Please Login", http.StatusUnauthorized) return } w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") vars := mux.Vars(r) var ( val interface{} // Generic container for the updated object err error ) // Build a URI like representation of the datatype types := []string{vars["datatype"]} if childtype, ok := vars["childtype"]; ok { types = append(types, childtype) } // Switch based on that URI like representation and instantiate something in the generic container. Also infer the identifier from the vars and perform validation. switch strings.Join(types, "/") { case "items": v := new(data.Item) v.ID, err = strconv.ParseInt(vars["key"], 10, 64) val = v case "items/comments": v := new(data.ItemComment) v.ID, err = strconv.ParseInt(vars["childkey"], 10, 64) val = v case "users": if vars["key"] != user.Username { http.Error(w, "Please don't hack other users", http.StatusUnauthorized) return } v := new(data.User) v.Username = vars["key"] val = v case "users/direction": v := new(data.UserDirection) v.Username = vars["key"] val = v case "roles": v := new(data.Role) v.Title = vars["key"] val = v case "taxonomy": v := new(data.Term) v.Term = vars["key"] val = v case "taxonomy/ranking": v := new(data.TermRanking) v.Term = vars["key"] val = v default: http.NotFound(w, r) return } if err != nil { log.Println(err, vars) http.Error(w, "Malformed key in URI", http.StatusBadRequest) return } err = storage.Select(val) //Load previous values so that update is non distructive of empty fields if err == storage.ErrZeroAffected { http.NotFound(w, r) return } else if err != nil { log.Println(err) http.Error(w, "Select3 Database error, likely due to malformed request.", http.StatusInternalServerError) return } // Decode the JSON into our generic object. The decode will leave unscpecified fields untouched. decoder := json.NewDecoder(r.Body) err = decoder.Decode(&val) if err != nil { log.Println(err) http.Error(w, "Malformed json.", http.StatusBadRequest) return } // Validate JSON, run pre-update hooks, etc... //We must reset fields we set earlier incase they were changed during the JSON decode switch v := val.(type) { case (*data.Item): v.ID, err = strconv.ParseInt(vars["key"], 10, 64) v.Author = user.Username case (*data.ItemComment): v.ID, err = strconv.ParseInt(vars["childkey"], 10, 64) v.Author = user.Username case (*data.User): v.Username = vars["key"] case (*data.UserDirection): v.Username = vars["key"] v.LockUsername = user.Username case (*data.Role): v.Title = vars["key"] case (*data.Term): v.Term = vars["key"] v.Author = user.Username case (*data.TermRanking): v.Term = vars["key"] } if err != nil { log.Println(err, vars) http.Error(w, "Malformed key in URI", http.StatusBadRequest) return } // Run the update err = storage.Update(val) if err == storage.ErrZeroAffected { w.WriteHeader(http.StatusNotModified) return } else if err != nil { log.Println(err) http.Error(w, "Update2 Database error, likely due to malformed request.", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") // Header are important when GZIP is enabled // Return the updated item encoder := json.NewEncoder(w) err = encoder.Encode(val) if err != nil { log.Println(err) } }
func Insert(val interface{}) error { var ( res sql.Result err error ) now := time.Now().UTC() switch v := val.(type) { case *data.Item: res, err = Exec( "INSERT INTO Items (Type, Author, ThumbnailURI, ContentURI, UploadURI, ContentToken, HasHeading, Heading, HasGeo, Lat, Lng, Radius, Live, StartTime, StopTime) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", v.Type, v.Author, v.ThumbnailURI, v.ContentURI, v.UploadURI, v.ContentToken, v.HasHeading, v.Heading, v.HasGeo, v.Lat, v.Lng, v.Radius, v.Live, v.StartTime.UTC(), v.StopTime.UTC(), ) case *data.ItemComment: res, err = Exec( "INSERT INTO ItemComments (ItemID, Author, Body, UpdateTime) VALUES (?, ?, ?, ?)", v.ItemID, v.Author, v.Body, now, ) case *data.Geolocation: res, err = Exec( "INSERT INTO Geolocations (ItemID, Lat, Lng, Heading, Radius, Timestamp) VALUES (?, ?, ?, ?, ?, ?)", v.ItemID, v.Lat, v.Lng, v.Heading, v.Radius, now, ) case *data.Term: // There is basically no danger with INSERT IGNORE there is nothing we would want to change if there is // accidental remake of a term res, err = Exec( "INSERT IGNORE INTO Terms (Term, Automated, Author, UpdateTime) VALUES (?, ?, ?, ?)", v.Term, v.Automated, v.Author, now, ) case *data.TermRelationship: // Nothing can go wrong with INSERT IGNORE since the key is whole entry res, err = Exec( "INSERT IGNORE INTO TermRelationships (Term, ItemID) VALUES (?, ?)", v.Term, v.ItemID, ) case *data.TermRanking: // There is basically no danger with INSERT IGNORE there is nothing we would want to change if there is // accidental remake of a term res, err = Exec( "INSERT IGNORE INTO TermRankings (Term, Ranking, UpdateTime) VALUES (?, ?, ?)", v.Term, v.Ranking, now, ) case *data.Role: res, err = Exec( "INSERT INTO Roles (Title, Permissions) VALUES (?, ?)", v.Title, v.Permissions, ) case *data.User: res, err = Exec( "INSERT INTO Users (Username, Password, Salt, Role, TrustLevel, CreateTime) VALUES (?, ?, ?, ?, ?, ?)", v.Username, v.Password, v.Salt, v.Role, v.TrustLevel, now, ) case *data.UserDirection: res, err = Exec( "INSERT INTO UserDirections (Username, LockUsername, Command, Heading, Lat, Lng, UpdateTime) VALUES (?, ?, ?, ?, ?, ?, ?)", v.Username, v.LockUsername, v.Command, v.Heading, v.Lat, v.Lng, now, ) default: return ErrUnsupportedDataType } if err != nil { return err } affected, err := res.RowsAffected() if err != nil { return err } if affected < 1 { return ErrZeroAffected } ID, err := res.LastInsertId() if err != nil { return err } switch v := val.(type) { case *data.Item: v.ID = ID v.AddTerm("all", v.Author) v.AddTerm("type:"+v.Type, v.Author) v.AddTerm(v.Author, v.Author) _, err = ReconcileTerms(v, &v.Terms) case *data.ItemComment: v.ID = ID v.UpdateTime = now case *data.Geolocation: v.Timestamp = &now item := new(data.Item) item.ID = v.ItemID err = Select(item) if err != nil { return err } listeners.NotifyUpdate(item) case *data.Term: v.UpdateTime = now ranking := new(data.TermRanking) ranking.Term = v.Term err = Insert(ranking) case *data.TermRanking: v.UpdateTime = now case *data.User: v.CreateTime = now direction := new(data.UserDirection) direction.Username = v.Username err = Insert(direction) case *data.UserDirection: v.UpdateTime = now } listeners.NotifyInsert(val) return err }
// Handle a POST request with an image, phone_id, lat, lng and heading. Return a target heading from the web UI func MultiUploadHandler(rterDir string, uploadPath string, w http.ResponseWriter, r *http.Request) { imageFile, header, err := r.FormFile("image") if err != nil { return } user := new(data.User) user.Username = r.FormValue("phone_id") err = storage.Select(user) if err == storage.ErrZeroAffected { user.Role = "public" storage.Insert(user) // log.Println("upload failed, phone_id invalid:", user.Username) // http.Error(w, "Invalid credentials.", http.StatusUnauthorized) // return } else if err != nil { log.Println(err) http.Error(w, "Database error, likely due to malformed request.", http.StatusInternalServerError) return } os.Mkdir(filepath.Join(uploadPath, user.Username), os.ModeDir|0775) matchingItems := make([]*data.Item, 0) err = storage.SelectWhere(&matchingItems, "WHERE Type=\"streaming-video-v0\" AND Author=?", user.Username) exists := true if err == storage.ErrZeroAffected { exists = false } else if err != nil { log.Println(err) http.Error(w, "Database error, likely due to malformed request.", http.StatusInternalServerError) return } var item *data.Item if exists { item = matchingItems[0] } else { item = new(data.Item) } item.Author = user.Username item.Type = "streaming-video-v0" item.Live = true item.HasGeo = true item.HasHeading = true item.Lat, err = strconv.ParseFloat(r.FormValue("lat"), 64) if err != nil { item.HasGeo = false } item.Lng, err = strconv.ParseFloat(r.FormValue("lng"), 64) if err != nil { item.HasGeo = false } item.Heading, err = strconv.ParseFloat(r.FormValue("heading"), 64) if err != nil { item.HasHeading = false } item.StartTime = time.Now() item.StopTime = item.StartTime path := uploadPath if strings.HasSuffix(header.Filename, ".png") { path = filepath.Join(path, fmt.Sprintf("%v/%v.png", user.Username, item.StopTime.UnixNano())) } else if strings.HasSuffix(header.Filename, ".jpg") || strings.HasSuffix(header.Filename, "jpeg") { path = filepath.Join(path, fmt.Sprintf("%v/%v.jpg", user.Username, item.StopTime.UnixNano())) } outputFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0664) if err != nil { log.Println(err) http.Error(w, "Error, likely due to malformed request.", http.StatusInternalServerError) } defer outputFile.Close() io.Copy(outputFile, imageFile) path = path[len(rterDir):] item.ContentURI = path item.ThumbnailURI = path if exists { storage.Update(item) } else { storage.Insert(item) } userDirection := new(data.UserDirection) userDirection.Username = user.Username err = storage.Select(userDirection) if err != nil { log.Println(err) http.Error(w, "Database error, likely due to malformed request.", http.StatusInternalServerError) return } w.Write([]byte(strconv.FormatFloat(userDirection.Heading, 'f', 6, 64))) }