// 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) }
// 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) }
// 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) }
// 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) }
// 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) }
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) } }
// 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) }
// 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) }