diff --git a/jsonstore/helper.go b/jsonstore/helper.go new file mode 100644 index 0000000..29a8827 --- /dev/null +++ b/jsonstore/helper.go @@ -0,0 +1,64 @@ +package jsonstore + +import ( + "fmt" + "math" + "strconv" + "time" + + mgo "gopkg.in/mgo.v2" + "gopkg.in/mgo.v2/bson" +) + +func byteToHex(input []byte) string { + var hexValue string + for _, v := range input { + hexValue += fmt.Sprintf("%02x", v) + } + return hexValue +} + +func getPosts(db *mgo.Database, sortBy string, searchConfig map[string]interface{}) ([]interface{}, error) { + var posts []interface{} + var user User + + err := db.C("posts").Find(searchConfig).Sort("-" + sortBy).All(&posts) + if err != nil { + panic(err) + } + + for i := range posts { + err = db.C("users").Find(bson.M{"_id": posts[i].(bson.M)["author"].(bson.ObjectId)}).One(&user) + if err != nil { + panic(err) + } + + posts[i].(bson.M)["author"] = user + } + + return posts, nil +} + +func findTotalDocuments(db *mgo.Database) int64 { + collections := [5]string{"posts", "comments", "users", "userpostvotes", "usercommentvotes"} + var sum int64 + + for _, collection := range collections { + count, _ := db.C(collection).Find(nil).Count() + sum += int64(count) + } + + return sum +} + +func hotScore(votes int, date time.Time) float64 { + gravity := 1.8 + hoursAge := float64(date.Unix() * 3600) + return float64(votes-1) / math.Pow(hoursAge+2, gravity) +} + +// FindTimeFromObjectID ... Convert ObjectID string to Time +func FindTimeFromObjectID(id string) time.Time { + ts, _ := strconv.ParseInt(id[0:8], 16, 64) + return time.Unix(ts, 0) +} diff --git a/jsonstore/jsonstore.go b/jsonstore/jsonstore.go index 3ce7baa..655f63d 100644 --- a/jsonstore/jsonstore.go +++ b/jsonstore/jsonstore.go @@ -6,14 +6,12 @@ import ( "encoding/hex" "encoding/json" "fmt" - "math" "net/url" "regexp" - "strconv" "strings" - "time" + "sync" - "mint/code" + "github.com/Hashnode/mint/code" "github.com/tendermint/abci/types" "golang.org/x/crypto/ed25519" @@ -24,93 +22,6 @@ import ( var _ types.Application = (*JSONStoreApplication)(nil) var db *mgo.Database -// Post ... -type Post struct { - ID bson.ObjectId `bson:"_id" json:"_id"` - Title string `bson:"title" json:"title"` - URL string `bson:"url" json:"url"` - Text string `bson:"text" json:"text"` - Author bson.ObjectId `bson:"author" json:"author"` - Upvotes int `bson:"upvotes" json:"upvotes"` - Date time.Time `bson:"date" json:"date"` - Score float64 `bson:"score" json:"score"` - NumComments int `bson:"numComments" json:"numComments"` - AskUH bool `bson:"askUH" json:"askUH"` - ShowUH bool `bson:"showUH" json:"showUH"` - Spam bool `bson:"spam" json:"spam"` -} - -// Comment ... -type Comment struct { - ID bson.ObjectId `bson:"_id" json:"_id"` - Content string `bson:"content" json:"content"` - Author bson.ObjectId `bson:"author" json:"author"` - Upvotes int `bson:"upvotes" json:"upvotes"` - Score float64 `bson:"score" json:"score"` - Date time.Time - PostID bson.ObjectId `bson:"postID" json:"postID"` - ParentCommentID bson.ObjectId `bson:"parentCommentId,omitempty" json:"parentCommentId"` -} - -// User ... -type User struct { - ID bson.ObjectId `bson:"_id" json:"_id"` - Name string `bson:"name" json:"name"` - Username string `bson:"username" json:"username"` - PublicKey string `bson:"publicKey" json:"publicKey"` -} - -// UserPostVote ... -type UserPostVote struct { - ID bson.ObjectId `bson:"_id" json:"_id"` - UserID bson.ObjectId `bson:"userID" json:"userID"` - PostID bson.ObjectId `bson:"postID" json:"postID"` -} - -// UserCommentVote ... -type UserCommentVote struct { - ID bson.ObjectId `bson:"_id" json:"_id"` - UserID bson.ObjectId `bson:"userID" json:"userID"` - CommentID bson.ObjectId `bson:"commentID" json:"commentID"` -} - -// JSONStoreApplication ... -type JSONStoreApplication struct { - types.BaseApplication -} - -func byteToHex(input []byte) string { - var hexValue string - for _, v := range input { - hexValue += fmt.Sprintf("%02x", v) - } - return hexValue -} - -func findTotalDocuments(db *mgo.Database) int64 { - collections := [5]string{"posts", "comments", "users", "userpostvotes", "usercommentvotes"} - var sum int64 - - for _, collection := range collections { - count, _ := db.C(collection).Find(nil).Count() - sum += int64(count) - } - - return sum -} - -func hotScore(votes int, date time.Time) float64 { - gravity := 1.8 - hoursAge := float64(date.Unix() * 3600) - return float64(votes-1) / math.Pow(hoursAge+2, gravity) -} - -// FindTimeFromObjectID ... Convert ObjectID string to Time -func FindTimeFromObjectID(id string) time.Time { - ts, _ := strconv.ParseInt(id[0:8], 16, 64) - return time.Unix(ts, 0) -} - // NewJSONStoreApplication ... func NewJSONStoreApplication(dbCopy *mgo.Database) *JSONStoreApplication { db = dbCopy @@ -522,5 +433,203 @@ func (app *JSONStoreApplication) Commit() types.ResponseCommit { // Query ... Query the blockchain. Unimplemented as of now. func (app *JSONStoreApplication) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { + var temp interface{} + var user User + var t map[string]interface{} + + err := json.Unmarshal(reqQuery.Data, &temp) + if err != nil { + panic(err) + } + + t = temp.(map[string]interface{}) + + switch reqQuery.Path { + case "/fetch-user": + err := db.C("users").Find(bson.M{"publicKey": t["publicKey"].(string)}).One(&user) + if err != nil { + panic(err) + // resQuery.Log = err.Error() + // break + } + + resData, err := json.Marshal(user) + if err != nil { + panic(err) + // resQuery.Log = err.Error() + // break + } + resQuery.Value = resData + case "/get-posts": + + searchConfig := make(map[string]interface{}) + + searchConfig["spam"] = false + if val, ok := t["type"]; ok { + if val.(string) == "askUH" || val.(string) == "showUH" { + searchConfig[val.(string)] = true + } else { + resQuery.Log = "type field can only have values 'showUH' or 'askUH'" + break + } + } + + if t["sortBy"].(string) != "score" && t["sortBy"].(string) != "date" { + resQuery.Log = "sortBy field can only have values 'score' or 'date'" + break + } + + posts, err := getPosts(db, t["sortBy"].(string), searchConfig) + if err != nil { + panic(err) + } + + resData, err := json.Marshal(posts) + if err != nil { + panic(err) + // resQuery.Log = err.Error() + // break + } + resQuery.Value = resData + case "/get-upvote-status": + var wg sync.WaitGroup + var status map[string]interface{} + + status = make(map[string]interface{}) + + err := db.C("users").Find(bson.M{"publicKey": t["publicKey"].(string)}).One(&user) + if err != nil { + panic(err) + } + + for _, postID := range t["postIds"].([]interface{}) { + wg.Add(1) + go func(postID string) { + defer wg.Done() + count, err := db.C("userpostvotes").Find(bson.M{"postID": bson.ObjectIdHex(postID), "userID": user.ID}).Count() + if err != nil { + panic(err) + } + + if count > 0 { + status[postID] = true + } + }(postID.(string)) + } + + wg.Wait() + resData, err := json.Marshal(status) + if err != nil { + resQuery.Log = err.Error() + break + } + resQuery.Value = resData + case "/comment": + var comment map[string]interface{} + err := db.C("comments").Find(bson.M{"_id": bson.ObjectIdHex(t["id"].(string))}).One(&comment) + if err != nil { + panic(err) + } + + err = db.C("users").Find(bson.M{"_id": comment["author"].(bson.ObjectId)}).One(&user) + if err != nil { + panic(err) + } + + comment["author"] = user + + resData, err := json.Marshal(comment) + if err != nil { + panic(err) + // resQuery.Log = err.Error() + // break + } + resQuery.Value = resData + case "/get-comment-upvote-status": + var wg sync.WaitGroup + var status map[string]interface{} + + status = make(map[string]interface{}) + + err := db.C("users").Find(bson.M{"publicKey": t["publicKey"].(string)}).One(&user) + if err != nil { + panic(err) + } + + for _, commentID := range t["commentIds"].([]interface{}) { + wg.Add(1) + go func(commentID string) { + defer wg.Done() + count, err := db.C("usercommentvotes").Find(bson.M{"commentID": bson.ObjectIdHex(commentID), "userID": user.ID}).Count() + if err != nil { + panic(err) + } + + if count > 0 { + status[commentID] = true + } + }(commentID.(string)) + } + + wg.Wait() + resData, err := json.Marshal(status) + if err != nil { + panic(err) + // resQuery.Log = err.Error() + // break + } + resQuery.Value = resData + case "/post": + var wg sync.WaitGroup + var post map[string]interface{} + var user User + var comments []map[string]interface{} + + post = make(map[string]interface{}) + + err := db.C("posts").Find(bson.M{"_id": bson.ObjectIdHex(t["id"].(string))}).One(&post) + if err != nil { + panic(err) + } + + err = db.C("users").Find(bson.M{"_id": post["author"].(bson.ObjectId)}).One(&user) + if err != nil { + panic(err) + } + + post["author"] = user + + err = db.C("comments").Find(bson.M{"postID": bson.ObjectIdHex(t["id"].(string))}).All(&comments) + if err != nil { + panic(err) + } + + for i := range comments { + wg.Add(1) + go func(i int) { + defer wg.Done() + var user User + err = db.C("users").Find(bson.M{"_id": comments[i]["author"].(bson.ObjectId)}).One(&user) + if err != nil { + panic(err) + } + + comments[i]["author"] = user + }(i) + } + + post["comments"] = comments + + wg.Wait() + resData, err := json.Marshal(post) + if err != nil { + panic(err) + // resQuery.Log = err.Error() + // break + } + resQuery.Value = resData + + } + return } diff --git a/jsonstore/types.go b/jsonstore/types.go new file mode 100644 index 0000000..bac7549 --- /dev/null +++ b/jsonstore/types.go @@ -0,0 +1,63 @@ +package jsonstore + +import ( + "time" + + "github.com/tendermint/abci/types" + "gopkg.in/mgo.v2/bson" +) + +// Post ... +type Post struct { + ID bson.ObjectId `bson:"_id" json:"_id"` + Title string `bson:"title" json:"title"` + URL string `bson:"url" json:"url"` + Text string `bson:"text" json:"text"` + Author bson.ObjectId `bson:"author" json:"author"` + Upvotes int `bson:"upvotes" json:"upvotes"` + Date time.Time `bson:"date" json:"date"` + Score float64 `bson:"score" json:"score"` + NumComments int `bson:"numComments" json:"numComments"` + AskUH bool `bson:"askUH" json:"askUH"` + ShowUH bool `bson:"showUH" json:"showUH"` + Spam bool `bson:"spam" json:"spam"` +} + +// Comment ... +type Comment struct { + ID bson.ObjectId `bson:"_id" json:"_id"` + Content string `bson:"content" json:"content"` + Author bson.ObjectId `bson:"author" json:"author"` + Upvotes int `bson:"upvotes" json:"upvotes"` + Score float64 `bson:"score" json:"score"` + Date time.Time + PostID bson.ObjectId `bson:"postID" json:"postID"` + ParentCommentID bson.ObjectId `bson:"parentCommentId,omitempty" json:"parentCommentId"` +} + +// User ... +type User struct { + ID bson.ObjectId `bson:"_id" json:"_id"` + Name string `bson:"name" json:"name"` + Username string `bson:"username" json:"username"` + PublicKey string `bson:"publicKey" json:"publicKey"` +} + +// UserPostVote ... +type UserPostVote struct { + ID bson.ObjectId `bson:"_id" json:"_id"` + UserID bson.ObjectId `bson:"userID" json:"userID"` + PostID bson.ObjectId `bson:"postID" json:"postID"` +} + +// UserCommentVote ... +type UserCommentVote struct { + ID bson.ObjectId `bson:"_id" json:"_id"` + UserID bson.ObjectId `bson:"userID" json:"userID"` + CommentID bson.ObjectId `bson:"commentID" json:"commentID"` +} + +// JSONStoreApplication ... +type JSONStoreApplication struct { + types.BaseApplication +} diff --git a/mint.go b/mint.go index 115fdf8..e4a14a6 100644 --- a/mint.go +++ b/mint.go @@ -1,9 +1,10 @@ package main import ( - "mint/jsonstore" "os" + "github.com/Hashnode/mint/jsonstore" + "github.com/tendermint/abci/server" "github.com/tendermint/abci/types" mgo "gopkg.in/mgo.v2" @@ -38,7 +39,7 @@ func initJSONStore() error { app = jsonstore.NewJSONStoreApplication(db) // Start the listener - srv, err := server.NewServer("tcp://0.0.0.0:46658", "socket", app) + srv, err := server.NewServer("tcp://0.0.0.0:26658", "socket", app) if err != nil { return err }