Skip to content

Commit

Permalink
Add a way to load existing products from the database. If existing
Browse files Browse the repository at this point in the history
products are being edited, don't create new ones. Move UUIDs to an
object of their own, including tests.
  • Loading branch information
caoimhechaos committed Jan 2, 2014
1 parent 2519605 commit 28b4af9
Show file tree
Hide file tree
Showing 6 changed files with 438 additions and 79 deletions.
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ func main() {
// Register the URL handler to be invoked.
http.Handle("/css/", http.FileServer(http.Dir(template_dir)))
http.Handle("/js/", http.FileServer(http.Dir(template_dir)))
http.Handle("/api/product", &ProductViewAPI{
authenticator: authenticator,
client: client,
scope: requested_scope,
})
http.Handle("/api/edit-product", &ProductEditAPI{
authenticator: authenticator,
client: client,
Expand Down
164 changes: 144 additions & 20 deletions productedit.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,10 @@ package main

import (
"bytes"
"crypto/rand"
"database/cassandra"
"encoding/binary"
"encoding/json"
"expvar"
"io"
"log"
"net/http"
"regexp"
Expand All @@ -48,45 +47,164 @@ import (
"code.google.com/p/goprotobuf/proto"
)

const NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000
// Number of errors which occurred viewing products, mapped by type.
var productViewErrors *expvar.Map = expvar.NewMap("num-product-view-errors")

// Number of errors which occurred editing products, mapped by type.
var productEditErrors *expvar.Map = expvar.NewMap("num-product-edit-errors")

type Product struct {
Name string
Price float64
Barcodes []string
VendorId string
Stock int64
}

type ProductViewAPI struct {
authenticator *ancientauth.Authenticator
client *cassandra.RetryCassandraClient
scope string
}

type ProductEditAPI struct {
authenticator *ancientauth.Authenticator
client *cassandra.RetryCassandraClient
scope string
}

func GenTimeUUID(when *time.Time) ([]byte, error) {
var uuid *bytes.Buffer = new(bytes.Buffer)
var stamp int64 = when.UnixNano()/100 + NUM_100NS_INTERVALS_SINCE_UUID_EPOCH
var stampLow int64 = stamp & 0xffffffff
var stampMid int64 = stamp & 0xffff00000000
var stampHi int64 = stamp & 0xfff000000000000
func (self *ProductViewAPI) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var cp *cassandra.ColumnParent
var pred *cassandra.SlicePredicate
var res []*cassandra.ColumnOrSuperColumn
var csc *cassandra.ColumnOrSuperColumn
var ire *cassandra.InvalidRequestException
var ue *cassandra.UnavailableException
var te *cassandra.TimedOutException
var prod Product
var err error
var uuidstr string = req.FormValue("id")
var ts int64 = 0
var uuid UUID

var rawdata []byte

numRequests.Add(1)
numAPIRequests.Add(1)

// Check the user is in the reqeuested scope.
if !self.authenticator.IsAuthenticatedScope(req, self.scope) {
numDisallowedScope.Add(1)
http.Error(w,
"You are not in the right group to access this resource",
http.StatusForbidden)
return
}

if len(uuidstr) <= 0 {
http.Error(w, "Requested UUID empty", http.StatusNotAcceptable)
return
}

uuid, err = ParseUUID(uuidstr)
if err != nil {
http.Error(w, "Requested UUID invalid", http.StatusNotAcceptable)
return
}

var upper int64 = (stampLow << 32) | (stampMid >> 16) | (1 << 12) |
(stampHi >> 48)
cp = cassandra.NewColumnParent()
cp.ColumnFamily = "products"

err = binary.Write(uuid, binary.LittleEndian, upper)
pred = cassandra.NewSlicePredicate()
pred.ColumnNames = [][]byte{
[]byte("name"), []byte("price"), []byte("vendor"),
[]byte("barcodes"), []byte("stock"),
}

res, ire, ue, te, err = self.client.GetSlice([]byte(uuid), cp, pred,
cassandra.ConsistencyLevel_ONE)
if ire != nil {
log.Print("Invalid request: ", ire.Why)
productViewErrors.Add(ire.Why, 1)
return
}
if ue != nil {
log.Print("Unavailable")
productViewErrors.Add("unavailable", 1)
return
}
if te != nil {
log.Print("Request to database backend timed out")
productViewErrors.Add("timeout", 1)
return
}
if err != nil {
return []byte{}, err
log.Print("Generic error: ", err)
productViewErrors.Add(err.Error(), 1)
return
}
uuid.WriteByte(0xC0)
uuid.WriteByte(0x00)

_, err = io.CopyN(uuid, rand.Reader, 6)
for _, csc = range res {
var col = csc.Column
var cname string
if !csc.IsSetColumn() {
continue
}

cname = string(col.Name)
if col.IsSetTimestamp() && col.Timestamp > ts {
ts = col.Timestamp
}

if cname == "name" {
prod.Name = string(col.Value)
} else if cname == "price" {
var buf *bytes.Buffer = bytes.NewBuffer(col.Value)
err = binary.Read(buf, binary.LittleEndian, &prod.Price)
if err != nil {
log.Print("Row ", uuid.String(), " price is invalid")
productViewErrors.Add("corrupted-price", 1)
}
} else if cname == "vendor" {
prod.VendorId = UUID(col.Value).String()
} else if cname == "barcodes" {
var bc Barcodes
err = proto.Unmarshal(col.Value, &bc)
if err != nil {
log.Print("Row ", uuid.String(), " barcode is invalid")
productViewErrors.Add("corrupted-barcode", 1)
return
}
} else if cname == "stock" {
var buf *bytes.Buffer = bytes.NewBuffer(col.Value)
err = binary.Read(buf, binary.BigEndian, &prod.Stock)
if err != nil {
log.Print("Row ", uuid.String(), " stock is invalid")
productViewErrors.Add("corrupted-stock", 1)
}
}
}

rawdata, err = json.Marshal(prod)
if err != nil {
return []byte{}, err
log.Print("Error marshalling JSON: ", err)
numJSONMarshalErrors.Add(1)
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

return uuid.Bytes(), nil
w.Header().Add("Content-type", "application/json")
w.WriteHeader(http.StatusOK)
_, err = w.Write(rawdata)
if err != nil {
log.Print("Error writing JSON response: ", err)
numHTTPWriteErrors.Add(err.Error(), 1)
}
}

func (self *ProductEditAPI) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var uuid []byte
var specid string = req.PostFormValue("id")
var uuid UUID
var mmap map[string]map[string][]*cassandra.Mutation
var mutations []*cassandra.Mutation
var mutation *cassandra.Mutation
Expand Down Expand Up @@ -165,7 +283,13 @@ func (self *ProductEditAPI) ServeHTTP(w http.ResponseWriter, req *http.Request)
mutations = append(mutations, mutation)
}

uuid, err = GenTimeUUID(&now)
// If we're editing an existing product, re-use that UUID. Otherwise,
// generate one.
if len(specid) > 0 {
uuid, err = ParseUUID(specid)
} else {
uuid, err = GenTimeUUID(&now)
}
if err != nil {
productEditErrors.Add(err.Error(), 1)
http.Error(w, err.Error(), http.StatusInternalServerError)
Expand Down
50 changes: 7 additions & 43 deletions productsearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"database/cassandra"
"encoding/json"
"expvar"
"fmt"
"html/template"
"log"
"net/http"
Expand All @@ -47,7 +46,7 @@ import (
type SearchResult struct {
Name string
Picture string
Path string
Uuid string
}

// Search results with different categories of results.
Expand Down Expand Up @@ -88,41 +87,6 @@ type ProductSearchAPI struct {
scope string
}

func UUID2String(uuid []byte) string {
var ret string
var i int

for i = 0; i < 4; i++ {
ret += fmt.Sprintf("%02X", uuid[i])
}

ret += "-"

for i = 4; i < 6; i++ {
ret += fmt.Sprintf("%02X", uuid[i])
}

ret += "-"

for i = 6; i < 8; i++ {
ret += fmt.Sprintf("%02X", uuid[i])
}

ret += "-"

for i = 8; i < 12; i++ {
ret += fmt.Sprintf("%02X", uuid[i])
}

ret += "-"

for i = 12; i < len(uuid); i++ {
ret += fmt.Sprintf("%02X", uuid[i])
}

return ret
}

func (self *ProductSearchForm) ServeHTTP(w http.ResponseWriter, req *http.Request) {
var err error
numRequests.Add(1)
Expand Down Expand Up @@ -243,7 +207,7 @@ func (self *ProductSearchAPI) ServeHTTP(w http.ResponseWriter, req *http.Request

r = new(SearchResult)
r.Name = string(slice.Key)
r.Path = "/product/" + UUID2String(col.Value)
r.Uuid = UUID(col.Value).String()
res.Products = append(res.Products, r)
}
}
Expand Down Expand Up @@ -352,7 +316,7 @@ func (self *ProductSearchAPI) ServeHTTP(w http.ResponseWriter, req *http.Request

r = new(SearchResult)
r.Name = string(col.Value)
r.Path = "/product/" + UUID2String([]byte(key))
r.Uuid = UUID([]byte(key)).String()
res.Products = append(res.Products, r)
}
}
Expand All @@ -361,22 +325,22 @@ func (self *ProductSearchAPI) ServeHTTP(w http.ResponseWriter, req *http.Request
// TODO(caoimhe): stub
r = new(SearchResult)
r.Name = "ACME Inc."
r.Path = "/vendor/acme"
r.Uuid = "/vendor/acme"
res.Vendors = append(res.Vendors, r)

r = new(SearchResult)
r.Name = "Starship Factory"
r.Path = "/vendor/starshipfactory"
r.Uuid = "/vendor/starshipfactory"
res.Vendors = append(res.Vendors, r)

r = new(SearchResult)
r.Name = "RaumZeitLabor e.V."
r.Path = "/vendor/rzl"
r.Uuid = "/vendor/rzl"
res.Vendors = append(res.Vendors, r)

r = new(SearchResult)
r.Name = "Doctor in the TARDIS"
r.Path = "/vendor/doctor"
r.Uuid = "/vendor/doctor"
res.Vendors = append(res.Vendors, r)
}

Expand Down
Loading

0 comments on commit 28b4af9

Please sign in to comment.