예제 #1
0
파일: ui.go 프로젝트: vichuda/PiScan
// RemoveSingleItem looks up the single item represented by the itemId form
// post variable, and attempts to delete it, if it exists. The reply is a
// jsonified string, passed back to MakeHandler() to be coupled with the
// right mime type
func RemoveSingleItem(r *http.Request, dbCoords database.ConnCoordinates, opts ...interface{}) string {
	// prepare the ajax reply object
	ack := AjaxAck{Message: "", Error: ""}

	// attempt to connect to the db
	db, err := database.InitializeDB(dbCoords)
	if err != nil {
		ack.Error = err.Error()
	}
	defer db.Close()

	if err == nil {
		// get the Account for this request
		acc, accErr := database.GetDesignatedAccount(db)
		if accErr != nil {
			ack.Error = accErr.Error()
		}

		// find the specific Item to remove
		// get the item id from the POST values
		if "POST" == r.Method {
			r.ParseForm()
			if idVal, exists := r.PostForm["itemId"]; exists {
				if len(idVal) > 0 {
					id, idErr := strconv.ParseInt(idVal[0], 10, 64)
					if idErr != nil {
						ack.Error = idErr.Error()
					} else {
						deleteSuccess, deleteErr := deleteItem(db, acc, id)
						if deleteSuccess {
							ack.Message = "Ok"
						} else {
							if deleteErr != nil {
								ack.Error = deleteErr.Error()
							} else {
								ack.Error = "No such item"
							}
						}
					}
				} else {
					ack.Error = "Missing item id"
				}
			} else {
				ack.Error = BAD_POST
			}
		} else {
			ack.Error = BAD_REQUEST
		}
	}

	// convert the ajax reply object to json
	ackObj, ackObjErr := json.Marshal(ack)
	if ackObjErr != nil {
		return ackObjErr.Error()
	}
	return string(ackObj)
}
예제 #2
0
파일: ui.go 프로젝트: vichuda/PiScan
// processItems fetches all the Items for the given Account, and the compares
// them to the id list posted from the form. All the matches get applied
// the given function: delete, favorite, unfavorite, etc.
func processItems(w http.ResponseWriter, r *http.Request, dbCoords database.ConnCoordinates, fn func(*database.Item, *sqlite3.Conn), successTarget string) {
	// attempt to connect to the db
	db, err := database.InitializeDB(dbCoords)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer db.Close()

	// get the Account for this request
	acc, accErr := database.GetDesignatedAccount(db)
	if accErr != nil {
		http.Error(w, accErr.Error(), http.StatusInternalServerError)
		return
	}

	// get all the Items for this Account
	// and store them in a map by their Idscanned/
	items, itemsErr := database.GetItems(db, acc)
	if itemsErr != nil {
		http.Error(w, itemsErr.Error(), http.StatusInternalServerError)
		return
	}
	accountItems := make(map[int64]*database.Item)
	for _, item := range items {
		accountItems[item.Id] = item
	}

	// get the list of item ids from the POST values
	// and apply the processing function
	if "POST" == r.Method {
		r.ParseForm()
		if idVals, exists := r.PostForm["item"]; exists {
			for _, idString := range idVals {
				id, idErr := strconv.ParseInt(idString, 10, 64)
				if idErr == nil {
					if accountItem, ok := accountItems[id]; ok {
						fn(accountItem, db)
					}
				}
			}
		}
	}

	// finally, return home, to the scanned items list
	http.Redirect(w, r, successTarget, http.StatusFound)
}
예제 #3
0
// InputUnknownItem handles the form for user contributions of unknown
// barcode scans: a GET presents the form, and a POST responds to the
// user-contributed input
func InputUnknownItem(w http.ResponseWriter, r *http.Request, dbCoords database.ConnCoordinates, opts ...interface{}) {
	// attempt to connect to the db
	db, err := database.InitializeDB(dbCoords)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer db.Close()

	// get the Account for this request
	acc, accErr := database.GetDesignatedAccount(db)
	if accErr != nil {
		http.Error(w, accErr.Error(), http.StatusInternalServerError)
		return
	}

	// get the api server + port from the optional parameters
	apiHost, apiHostOk := opts[0].(string)
	if !apiHostOk {
		http.Error(w, BAD_REQUEST, http.StatusInternalServerError)
		return
	}

	// prepare the html page response
	form := &ItemForm{Title: "Contribute Product Information",
		CancelUrl:    HOME_URL,
		Unregistered: (acc.Email == database.ANONYMOUS_EMAIL)}

	//lookup the item from the request id
	// and show the input form (if a GET)
	// or process it (if a POST)
	if "GET" == r.Method {
		// derive the item id from the url path
		urlPaths := strings.Split(r.URL.Path[1:], "/")
		if len(urlPaths) >= 2 {
			itemId, itemIdErr := strconv.ParseInt(urlPaths[1], 10, 64)
			if itemIdErr == nil {
				item, itemErr := database.GetSingleItem(db, acc, itemId)
				if itemErr == nil {
					if item.Id != database.BAD_PK && item.Desc == "" {
						// requested item has been found and is valid
						form.Item = item
					}
				}
			}
		}

		if form.Item == nil {
			// no matching item was found
			http.Error(w, BAD_REQUEST, http.StatusInternalServerError)
			return
		}

	} else if "POST" == r.Method {
		// get the item id from the posted data
		r.ParseForm()
		idVal, idExists := r.PostForm["item"]
		barcodeVal, barcodeExists := r.PostForm["barcode"]
		prodNameVal, prodNameExists := r.PostForm["prodName"]
		if idExists && barcodeExists && prodNameExists {
			itemId, itemIdErr := strconv.ParseInt(idVal[0], 10, 64)
			if itemIdErr != nil {
				form.FormError = itemIdErr.Error()
			} else {
				item, itemErr := database.GetSingleItem(db, acc, itemId)
				if itemErr != nil {
					form.FormError = itemErr.Error()
				} else {
					// the hidden barcode value must match the retrieved item
					if item.Barcode == barcodeVal[0] {
						// update the item in the local client db
						item.Desc = prodNameVal[0]
						item.UserContributed = true
						item.Update(db)

						// also need to mark the contribution to POD in the server
						if acc.Email != database.ANONYMOUS_EMAIL {
							// get the form's prodDesc, brandName, brandUrl data
							prodDesc, prodDescExists := r.PostForm["prodDesc"]
							brandName, brandNameExists := r.PostForm["brandName"]
							brandUrl, brandUrlExists := r.PostForm["brandUrl"]

							// ping the server with the contribution data
							ping := func() {
								v := url.Values{}
								v.Set("email", acc.Email)
								v.Set("barcode", barcodeVal[0])
								v.Set("prodName", prodNameVal[0])
								if prodDescExists {
									v.Set("prodDesc", prodDesc[0])
								}
								if brandNameExists {
									v.Set("brandName", brandName[0])
								}
								if brandUrlExists {
									v.Set("brandUrl", brandUrl[0])
								}

								// use the account api code as the digest key
								hmac := digest.GenerateDigest(acc.APICode, v.Encode())
								v.Set("hmac", hmac)

								res, err := http.PostForm(strings.Join([]string{apiHost, "/contribute/"}, ""), v)
								if err == nil {
									res.Body.Close()
								}
							}

							go ping() // do not wait for the server to reply

						}

						// return success
						http.Redirect(w, r, HOME_URL, http.StatusFound)
						return
					} else {
						// bad form post: the hidden barcode value does not match the retrieved item
						form.FormError = BAD_POST
					}
				}
			}
		} else {
			// required form parameters are missing
			form.FormError = BAD_POST
		}
	}

	renderItemEditTemplate(w, form)
}
예제 #4
0
파일: account.go 프로젝트: vichuda/PiScan
// EditAccount presents the form for editing Account information (in
// response to a GET request) and handles to add/updates (in response to
// a POST request)
func EditAccount(w http.ResponseWriter, r *http.Request, dbCoords database.ConnCoordinates, opts ...interface{}) {
	// attempt to connect to the db
	db, err := database.InitializeDB(dbCoords)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer db.Close()

	// get the Account for this request
	acc, accErr := database.GetDesignatedAccount(db)
	if accErr != nil {
		http.Error(w, accErr.Error(), http.StatusInternalServerError)
		return
	}

	// get the api server + port from the optional parameters
	apiHost, apiHostOk := opts[0].(string)
	if !apiHostOk {
		http.Error(w, BAD_REQUEST, http.StatusInternalServerError)
		return
	}

	// prepare the html page response
	regStatus := (acc.Email == database.ANONYMOUS_EMAIL)
	cancelUrl := HOME_URL
	if !regStatus {
		cancelUrl = ACCOUNT_URL
	}

	form := &AccountForm{Title: "My Account",
		ActiveTab:    &ActiveTab{Scanned: false, Favorites: false, Account: true, ShowTabs: true},
		Account:      acc,
		CancelUrl:    cancelUrl,
		Unregistered: regStatus}

	if "POST" == r.Method {
		form.FormError = BAD_POST // in event of problems

		// get the item id from the posted data
		r.ParseForm()
		accVal, accExists := r.PostForm["account"]
		emailVal, emailExists := r.PostForm["accountEmail"]
		if accExists && emailExists {
			// make sure the hidden account id value matches the Account
			accId, accIdErr := strconv.ParseInt(accVal[0], 10, 64)
			if accIdErr != nil {
				form.FormError = accIdErr.Error()
			} else {
				if acc.Id == accId {
					// update the account email address in the local client db
					updateErr := acc.Update(db, emailVal[0], acc.APICode)
					if updateErr != nil {
						form.FormError = updateErr.Error()
					} else {
						// ping the server with the api code and email for verification
						ping := func() {
							v := url.Values{}
							v.Set("email", emailVal[0])
							v.Set("api", acc.APICode)

							// use the email address as the digest key
							hmac := digest.GenerateDigest(emailVal[0], v.Encode())
							v.Set("hmac", hmac)

							res, err := http.Get(strings.Join([]string{apiHost, "/register?", v.Encode()}, ""))
							if err == nil {
								res.Body.Close()
							}
						}

						go ping() // do not wait for the server to reply

						// return success
						http.Redirect(w, r, ACCOUNT_URL, http.StatusFound)
						return
					}
				}
			}
		}
	}

	renderAccountEditTemplate(w, form)
}
예제 #5
0
파일: account.go 프로젝트: vichuda/PiScan
// ConfirmServerAccount responds to the ajax request from the client to
// lookup and return the status of the given account
func ConfirmServerAccount(r *http.Request, dbCoords database.ConnCoordinates, opts ...interface{}) string {
	// prepare the ajax reply object
	ack := AjaxAck{Message: "", Error: ""}

	// attempt to connect to the db
	db, err := database.InitializeDB(dbCoords)
	if err != nil {
		ack.Error = err.Error()
	}
	defer db.Close()

	// get the api server + port from the optional parameters
	apiHost, apiHostOk := opts[0].(string)
	if !apiHostOk {
		ack.Error = BAD_REQUEST
	}

	if ack.Error == "" {
		// get the Account for this request
		acc, accErr := database.GetDesignatedAccount(db)
		if accErr != nil {
			ack.Error = accErr.Error()
		}

		// get the account from the POST values
		if "POST" == r.Method {
			r.ParseForm()
			if accVal, exists := r.PostForm["account"]; exists {
				if len(accVal) > 0 {
					id, idErr := strconv.ParseInt(accVal[0], 10, 64)
					if idErr != nil {
						ack.Error = idErr.Error()
					} else {
						if acc.Id != id {
							ack.Error = BAD_REQUEST
						} else {
							// prepare the API Server request
							v := url.Values{}
							v.Set("email", acc.Email)

							// use the email address as the digest key
							hmac := digest.GenerateDigest(acc.Email, v.Encode())
							v.Set("hmac", hmac)

							// ping the API Server for the status of this account
							res, resErr := http.Get(strings.Join([]string{apiHost, "/status?", v.Encode()}, ""))
							defer res.Body.Close()
							if resErr != nil {
								ack.Error = resErr.Error()
							} else {
								// read and parse the json message from the API Server
								m := new(api.SimpleMessage)
								dec := json.NewDecoder(res.Body)
								dec.Decode(&m)

								// assign the json ack accordingly
								if m.Err != nil {
									ack.Error = m.Err.Error()
								} else {
									ack.Message = m.Ack
								}
							}
						}
					}
				} else {
					ack.Error = "Missing account id"
				}
			} else {
				ack.Error = BAD_POST
			}
		} else {
			ack.Error = BAD_REQUEST
		}
	}

	// convert the ajax reply object to json
	ackObj, ackObjErr := json.Marshal(ack)
	if ackObjErr != nil {
		return ackObjErr.Error()
	}
	return string(ackObj)
}
예제 #6
0
파일: scanner.go 프로젝트: vichuda/PiScan
func main() {
	var (
		device, apiServer, sqlitePath, sqliteFile, sqliteTablesDefinitionPath string
		apiPort                                                               int
	)

	flag.StringVar(&device, "device", scanner.SCANNER_DEVICE, fmt.Sprintf("The '/dev/input/event' device associated with your scanner (defaults to '%s')", scanner.SCANNER_DEVICE))
	flag.StringVar(&apiServer, "apiHost", apiServerHost, fmt.Sprintf("The hostname or IP address of the API server (defaults to '%s')", apiServerHost))
	flag.IntVar(&apiPort, "apiPort", apiServerPort, fmt.Sprintf("The API server port (defaults to '%d')", apiServerPort))
	flag.StringVar(&sqlitePath, "sqlitePath", database.SQLITE_PATH, fmt.Sprintf("Path to the sqlite file (defaults to '%s')", database.SQLITE_PATH))
	flag.StringVar(&sqliteFile, "sqliteFile", database.SQLITE_FILE, fmt.Sprintf("The sqlite database file (defaults to '%s')", database.SQLITE_FILE))
	flag.StringVar(&sqliteTablesDefinitionPath, "sqliteTables", "", fmt.Sprintf("Path to the sqlite database definitions file, %s, (use only if creating the client db for the first time)", database.TABLE_SQL_DEFINITIONS))
	flag.Parse()

	if len(sqliteTablesDefinitionPath) > 0 {
		// this is a request to create the client db for the first time
		initDb, initErr := database.InitializeDB(database.ConnCoordinates{sqlitePath, sqliteFile, sqliteTablesDefinitionPath})
		if initErr != nil {
			log.Fatal(initErr)
		}
		defer initDb.Close()
		log.Println(fmt.Sprintf("Client database '%s' created in '%s'", sqliteFile, sqlitePath))

	} else {
		// a regular scanner processing event

		// coordinates for connecting to the sqlite database (from the command line options)
		dbCoordinates := database.ConnCoordinates{DBPath: sqlitePath, DBFile: sqliteFile}

		// attempt to connect to the sqlite db
		db, dbErr := database.InitializeDB(dbCoordinates)
		if dbErr != nil {
			log.Fatal(dbErr)
		}
		defer db.Close()

		processScanFn := func(barcode string) {
			// Lookup the barcode in the API server
			apiResponse, apiErr := http.PostForm(fmt.Sprintf("%s:%d/lookup", apiServer, apiPort), url.Values{"barcode": {barcode}})
			if apiErr != nil {
				fmt.Println(fmt.Sprintf("API access error: %s", apiErr))
				return
			}
			rawJson, _ := ioutil.ReadAll(apiResponse.Body)
			apiResponse.Body.Close()

			var products []*commerce.API
			err := json.Unmarshal(rawJson, &products)
			if err != nil {
				fmt.Println(fmt.Sprintf("API barcode lookup error: %s", err))
				return
			}

			// get the Account for this request
			acc, accErr := database.GetDesignatedAccount(db)
			if accErr != nil {
				fmt.Println(fmt.Sprintf("Client db account access error: %s", accErr))
				return
			}

			// get the list of current Vendors according to the Pi client database
			// and map them according to their API vendor id string
			vendors := make(map[string]*database.Vendor)
			for _, v := range database.GetAllVendors(db) {
				vendors[v.VendorId] = v
			}

			productsFound := 0
			for i, product := range products {
				v, exists := vendors[product.Vendor]
				if !exists {
					if len(product.Vendor) > 0 {
						amazonId, amazonErr := database.AddVendor(db, product.Vendor, "Amazon")
						if amazonErr == nil {
							v = database.GetVendor(db, amazonId)
							vendors[product.Vendor] = v
							exists = true
						}
					}
				}

				if len(product.ProductName) > 0 {
					// convert the commerce.API struct into a database.Item
					// so that it can be logged into the Pi client sqlite db
					item := database.Item{
						Index:           int64(i),
						Barcode:         barcode,
						Desc:            product.ProductName,
						UserContributed: false}
					pk, insertErr := item.Add(db, acc)
					if insertErr == nil {
						// also log the vendor/product code combination
						if exists {
							database.AddVendorProduct(db, product.SKU, v.Id, pk)
						}
					}
					productsFound += 1
				}
			}

			if productsFound == 0 {
				// add it to the Pi client sqlite db as "unknown"
				// so that it can be manually edited/input
				unknownItem := database.Item{Index: 0, Barcode: barcode}
				unknownItem.Add(db, acc)
			}
		}

		errorFn := func(e error) {
			log.Fatal(e)
		}

		log.Println(fmt.Sprintf("Starting the scanner %s", device))
		scanner.ScanForever(device, processScanFn, errorFn)
	}
}
예제 #7
0
파일: email.go 프로젝트: vichuda/PiScan
// EmailItems handles the client form post, to send a list of the selected
// items via email to the given user
func EmailItems(w http.ResponseWriter, r *http.Request, dbCoords database.ConnCoordinates, opts ...interface{}) {
	// attempt to connect to the db
	db, err := database.InitializeDB(dbCoords)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer db.Close()

	// get the api server + port from the optional parameters
	apiHost, apiHostOk := opts[0].(string)
	if !apiHostOk {
		http.Error(w, BAD_REQUEST, http.StatusInternalServerError)
		return
	}

	// get the Account for this request
	acc, accErr := database.GetDesignatedAccount(db)
	if accErr != nil {
		http.Error(w, accErr.Error(), http.StatusInternalServerError)
		return
	}

	// get the account from the POST values
	if "POST" == r.Method {
		r.ParseForm()

		// make sure the form sent the required params
		accVal, accExists := r.PostForm["account"]
		items, itemsExist := r.PostForm["item"]

		if itemsExist && accExists {
			if len(accVal) > 0 {
				// check the submitted account info
				id, idErr := strconv.ParseInt(accVal[0], 10, 64)
				if idErr != nil {
					http.Error(w, idErr.Error(), http.StatusInternalServerError)
					return
				} else {
					if acc.Id != id {
						http.Error(w, BAD_REQUEST, http.StatusInternalServerError)
						return
					} else {
						// proceed with the send only if registered
						if acc.Email != database.ANONYMOUS_EMAIL {
							// lookup all the items for this account
							accountItems, accountItemsErr := database.GetItems(db, acc)

							if accountItemsErr == nil {
								// ping the server with the selected item data
								ping := func() {
									v := url.Values{}
									v.Set("email", acc.Email)

									// attach the list of descriptions for the selected items
									for _, accItem := range accountItems {
										for _, item := range items {
											if strconv.FormatInt(accItem.Id, 10) == item {
												v.Add("item", accItem.Desc)
												break
											}
										}
									}

									// use the account api code as the digest key
									hmac := digest.GenerateDigest(acc.APICode, v.Encode())
									v.Set("hmac", hmac)

									res, err := http.PostForm(strings.Join([]string{apiHost, "/email/"}, ""), v)
									if err == nil {
										res.Body.Close()
									}
								}

								go ping() // do not wait for the server to reply
							}
						}
					}
				}
			} else {
				http.Error(w, "Missing account id", http.StatusInternalServerError)
				return
			}
		} else {
			http.Error(w, BAD_POST, http.StatusInternalServerError)
			return
		}
	} else {
		http.Error(w, BAD_REQUEST, http.StatusInternalServerError)
		return
	}

	// finally, return home, to the scanned items list with an ack message
	http.Redirect(w, r, HOME_URL+"?ack=email", http.StatusFound)

}
예제 #8
0
파일: ui.go 프로젝트: vichuda/PiScan
// getItems returns a list of scanned or favorited products, and the correct
// corresponding options for the HTML page template
func getItems(w http.ResponseWriter, r *http.Request, dbCoords database.ConnCoordinates, favorites bool) {
	// attempt to connect to the db
	db, err := database.InitializeDB(dbCoords)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer db.Close()

	// get the Account for this request
	acc, accErr := database.GetDesignatedAccount(db)
	if accErr != nil {
		http.Error(w, accErr.Error(), http.StatusInternalServerError)
		return
	}

	// define the appropriate fetch item function
	fetch := func(db *sqlite3.Conn, acc *database.Account) ([]*database.Item, error) {
		if favorites {
			return database.GetFavoriteItems(db, acc)
		} else {
			return database.GetItems(db, acc)
		}
	}

	// get all the desired items for this Account
	items := make([]*database.Item, 0)
	itemList, itemsErr := fetch(db, acc)
	if itemsErr != nil {
		http.Error(w, itemsErr.Error(), http.StatusInternalServerError)
		return
	}
	for _, item := range itemList {
		items = append(items, item)
	}

	// actions
	actions := make([]*Action, 0)
	// commerce options
	for _, vendor := range database.GetAllVendors(db) {
		actions = append(actions, &Action{Link: fmt.Sprintf("/buy%s/", vendor.VendorId), Icon: "fa fa-shopping-cart", Action: fmt.Sprintf("Buy from %s", vendor.DisplayName)})
	}
	if acc.Email != database.ANONYMOUS_EMAIL {
		actions = append(actions, &Action{Link: "/email/", Icon: "fa fa-envelope", Action: "Email to me"})
	}
	if favorites {
		actions = append(actions, &Action{Link: "/unfavorite/", Icon: "fa fa-star-o", Action: "Remove from favorites"})
	} else {
		actions = append(actions, &Action{Link: "/favorite/", Icon: "fa fa-star", Action: "Add to favorites"})
	}
	actions = append(actions, &Action{Link: "/delete/", Icon: "fa fa-trash", Action: "Delete"})

	// define the page title
	var titleBuffer bytes.Buffer
	if favorites {
		titleBuffer.WriteString("Favorite")
	} else {
		titleBuffer.WriteString("Scanned")
	}
	titleBuffer.WriteString(" Item")
	if len(itemList) != 1 {
		titleBuffer.WriteString("s")
	}

	p := &ItemsPage{Title: titleBuffer.String(),
		Scanned:   !favorites,
		ActiveTab: &ActiveTab{Scanned: !favorites, Favorites: favorites, Account: false, ShowTabs: true},
		Actions:   actions,
		Account:   acc,
		Items:     items}

	// check for any message to display on page load
	r.ParseForm()
	if msg, exists := r.Form["ack"]; exists {
		ackType := strings.Join(msg, "")
		if ackType == "email" {
			p.PageMessage = EMAIL_SENT
		}
	}

	renderItemListTemplate(w, p)
}