diff --git a/README Part II.md b/README Part II.md new file mode 100644 index 0000000..0cb00e9 --- /dev/null +++ b/README Part II.md @@ -0,0 +1,67 @@ +
+
+## Demo Video
+
+I implemented and tested the following functions of the reddit -
+
+1. User Registration, Authentication and Authorization
+2. Subreddit Creation and Subscribe, Unsubscribe
+3. Post Creation, Fetch Posts by Subreddit, by User (feed ordered by creation time), Upvote a post and changes the author karma
+4. Comment Creation
+
+Here is the link to the video of me running the server and client to demonstrate the functionality of the system -
+
+[Demo Video](https://www.loom.com/share/c1569cf0f72f4b4f8f6b0ab7dc0fc354?sid=b1c01f79-a547-4fca-8c6e-0745c54a6d5e)
+
+The video is also available as .mp4 file with the submission.
diff --git a/actors/auth.actor.go b/actors/auth.actor.go
index be54f5d..1ae09f1 100644
--- a/actors/auth.actor.go
+++ b/actors/auth.actor.go
@@ -65,10 +65,18 @@ func (auth *AuthActor) RegisterNewUser(context actor.Context, actorMsg *proto.Re
func (auth *AuthActor) LoginUser(context actor.Context, actorMsg *proto.LoginRequest) {
token, err := auth.service.Login(actorMsg.Username, actorMsg.Password)
+ response := &proto.LoginResponse{
+ Token: "",
+ Error: "",
+ }
if err != nil {
- context.Respond(&proto.LoginResponse{Error: err.Error()})
+ fmt.Printf("Login failed: %v\n", err)
+ response.Error = err.Error()
+ } else {
+ response.Token = token
}
- context.Respond(&proto.LoginResponse{Token: token})
+ fmt.Printf("Login response: %+v\n", response)
+ context.Respond(response)
}
func (auth *AuthActor) ValidateToken(context actor.Context, actorMsg *proto.TokenValidationRequest) {
diff --git a/actors/karma.actor.go b/actors/karma.actor.go
index 3475c05..0816887 100644
--- a/actors/karma.actor.go
+++ b/actors/karma.actor.go
@@ -15,7 +15,7 @@ type KarmaActor struct {
karmaService *services.KarmaService
}
-func NewKarmaActor(userRepo *repositories.SqliteUserRepository) *KarmaActor {
+func NewKarmaActor(userRepo repositories.UserRepository) *KarmaActor {
return &KarmaActor{
karmaService: services.NewKarmaService(userRepo),
}
@@ -52,7 +52,6 @@ func (karma *KarmaActor) UpdateKarma(context actor.Context, actorMsg *proto.Karm
context.Respond(&proto.KarmaResponse{Error: "Invalid token"})
} else {
fmt.Println("Token validated successfully")
- // update karma
if err := karma.karmaService.UpdateKarma(uint(actorMsg.UserId), int(actorMsg.Amount)); err != nil {
context.Respond(&proto.KarmaResponse{Error: err.Error()})
}
diff --git a/actors/user.actor.go b/actors/user.actor.go
index 0737789..679e05b 100644
--- a/actors/user.actor.go
+++ b/actors/user.actor.go
@@ -75,11 +75,13 @@ func (user *UserActor) GetMessages(context actor.Context, actorMsg *proto.GetMes
if !validationResponse.Valid || !ok {
context.Respond(&proto.SendMessageResponse{Error: "Invalid token"})
} else {
+ print("\nToken validated successfully\n")
if userMessages, err := user.messageService.GetMessages(validationResponse.Claims.UserId, actorMsg.ToId); err != nil {
fmt.Printf("Error getting messages: %v\n", err)
fmt.Printf("User ID: %v, To ID: %v\n", validationResponse.Claims.UserId, actorMsg.ToId)
context.Respond(&proto.GetMessagesResponse{Error: err.Error()})
} else {
+ print("Messages retrieved successfully\n")
protoMessages := make([]*proto.Message, len(userMessages))
for i, msg := range userMessages {
protoMessages[i] = &proto.Message{
diff --git a/demo.mp4 b/demo.mp4
new file mode 100644
index 0000000..790e63a
Binary files /dev/null and b/demo.mp4 differ
diff --git a/handlers/auth.handlers.go b/handlers/auth.handlers.go
index 9a3f574..1050b7c 100644
--- a/handlers/auth.handlers.go
+++ b/handlers/auth.handlers.go
@@ -57,14 +57,20 @@ func (h *Handler) LoginHandler(w http.ResponseWriter, r *http.Request, rootConte
http.Error(w, fmt.Sprintf("Error getting response: %v", err), http.StatusInternalServerError)
return
}
-
loginResponse, ok := result.(*proto.LoginResponse)
+
if !ok {
http.Error(w, "Invalid response from actor", http.StatusInternalServerError)
return
}
if loginResponse.Error != "" {
- http.Error(w, loginResponse.Error, http.StatusInternalServerError)
+ if loginResponse.Error == "user not found" {
+ http.Error(w, loginResponse.Error, http.StatusNotFound)
+ } else if loginResponse.Error == "invalid password" {
+ http.Error(w, loginResponse.Error, http.StatusUnauthorized)
+ } else {
+ http.Error(w, loginResponse.Error, http.StatusInternalServerError)
+ }
return
}
diff --git a/handlers/post.handlers.go b/handlers/post.handlers.go
index daae6d4..c5eb043 100644
--- a/handlers/post.handlers.go
+++ b/handlers/post.handlers.go
@@ -75,7 +75,7 @@ func (h *Handler) GetPostsBySubredditHandler(w http.ResponseWriter, r *http.Requ
postResponse, ok := res.(*proto.GetPostsBySubredditResponse)
if ok && postResponse == nil {
- http.Error(w, "Post creation failed.", http.StatusInternalServerError)
+ http.Error(w, "Failed to fetch posts.", http.StatusInternalServerError)
return
} else {
json.NewEncoder(w).Encode(postResponse)
diff --git a/handlers/user.handlers.go b/handlers/user.handlers.go
index 3b6877f..6e26385 100644
--- a/handlers/user.handlers.go
+++ b/handlers/user.handlers.go
@@ -46,6 +46,7 @@ func (h* Handler) GetMessagesHandler(w http.ResponseWriter, r *http.Request, roo
return
}
response, ok := res.(*proto.GetMessagesResponse)
+ // print the response
if ok && response.Error != "" {
http.Error(w, response.Error, http.StatusInternalServerError)
return
diff --git a/image-3.png b/image-3.png
new file mode 100644
index 0000000..3677888
Binary files /dev/null and b/image-3.png differ
diff --git a/main.go b/main.go
index 26df1d1..eb306f1 100644
--- a/main.go
+++ b/main.go
@@ -39,21 +39,21 @@ func main() {
commentRepo := repositories.NewCommentRepository(db)
// setup actor system
authProps := actor.PropsFromProducer(func() actor.Actor {
- return actors.NewAuthActor(userRepo, "chanduKeChacha")
- })
+ return actors.NewAuthActor(userRepo, "chanduKeChacha")
+ })
karmaProps := actor.PropsFromProducer(func() actor.Actor {
return actors.NewKarmaActor(userRepo)
})
- userProps := actor.PropsFromProducer(func()actor.Actor {
+ userProps := actor.PropsFromProducer(func() actor.Actor {
return actors.NewUserActor(msgRepo)
})
- subProps := actor.PropsFromProducer(func()actor.Actor {
+ subProps := actor.PropsFromProducer(func() actor.Actor {
return actors.NewSubredditActor(subRepo)
})
- postProps := actor.PropsFromProducer(func()actor.Actor {
+ postProps := actor.PropsFromProducer(func() actor.Actor {
return actors.NewPostActor(postRepo)
})
- commentProps := actor.PropsFromProducer(func()actor.Actor {
+ commentProps := actor.PropsFromProducer(func() actor.Actor {
return actors.NewCommentActor(commentRepo)
})
// .. add more actor props here
@@ -65,37 +65,42 @@ func main() {
commentKind := cluster.NewKind("Comment", commentProps)
// .. add more actor props here
- kinds := []*cluster.Kind{authKind, karmaKind, userKind, subKind, postKind, commentKind} // append more kinds here
+ kinds := []*cluster.Kind{authKind, karmaKind, userKind, subKind, postKind, commentKind} // append more kinds here
// Distributed hash lookup
lookup := disthash.New()
-
- // New cluster definition
+
+ // New cluster definition
config := remote.Configure("127.0.0.1", 8080)
provider := automanaged.NewWithConfig(1*time.Second, 6331, "localhost:6331")
clusterConfig := cluster.Configure("reddit-cluster", provider, lookup, config, cluster.WithKinds(kinds...))
system := actor.NewActorSystem()
cluster := cluster.New(system, clusterConfig)
- cluster.StartMember()
+ cluster.StartMember()
// shutdown later
- defer cluster.Shutdown(true)
+ defer cluster.Shutdown(true)
rootContext := system.Root
// declare HTTP handler to use cluster instead of actor system
- handler := handlers.NewHandler(cluster)
+ handler := handlers.NewHandler(cluster)
http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.RegisterHandler(w, r, rootContext)
})
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.LoginHandler(w, r, rootContext)
})
http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.LogoutHandler(w, r, rootContext)
})
http.HandleFunc("/user/karma", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.KarmaHandler(w, r, rootContext)
})
http.HandleFunc("/user/messages", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
fmt.Printf("Message request received: %s\n", r.Method)
if r.Method == http.MethodGet {
handler.GetMessagesHandler(w, r, rootContext)
@@ -106,41 +111,47 @@ func main() {
}
})
http.HandleFunc("/user/subreddits", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.CreateSubredditHandler(w, r, rootContext)
})
http.HandleFunc("/user/subreddits/subscribe", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.SubscribeSubredditHandler(w, r, rootContext)
})
http.HandleFunc("/user/subreddits/unsubscribe", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.UnsubscribeSubredditHandler(w, r, rootContext)
})
http.HandleFunc("/post/create", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.CreatePostHandler(w, r, rootContext)
})
http.HandleFunc("/post/get", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.GetPostHandler(w, r, rootContext)
})
http.HandleFunc("/post/get/user", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.GetPostsByUserHandler(w, r, rootContext)
})
http.HandleFunc("/post/get/subreddit", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.GetPostsBySubredditHandler(w, r, rootContext)
})
http.HandleFunc("/post/upvote", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.UpdatePostVoteHandler(w, r, rootContext)
})
http.HandleFunc("/comment/create", func(w http.ResponseWriter, r *http.Request) {
+ fmt.Printf("Request received: %s %s\n", r.Method, r.URL.Path)
handler.CreateCommentHandler(w, r, rootContext)
})
- http.HandleFunc("/comment/get", func(w http.ResponseWriter, r *http.Request) {
- handler.GetCommentHandler(w, r, rootContext)
- })
- http.ListenAndServe(":5678", nil)
+ http.ListenAndServe(":5678", nil)
// Run till a signal comes
finish := make(chan os.Signal, 1)
signal.Notify(finish, os.Interrupt, os.Kill)
<-finish
-}
\ No newline at end of file
+}
diff --git a/models/post.models.go b/models/post.models.go
index b8ae27a..2b9a50a 100644
--- a/models/post.models.go
+++ b/models/post.models.go
@@ -12,6 +12,7 @@ type Post struct {
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`
CommentCount int64 `gorm:"default:0"`
+ Votes int64 `gorm:"default:0"`
// Relationships
Author User `gorm:"foreignKey:AuthorID"`
diff --git a/models/user.models.go b/models/user.models.go
index f08303f..11521d0 100644
--- a/models/user.models.go
+++ b/models/user.models.go
@@ -81,9 +81,12 @@ func (u *User) HashPassword() error {
return nil
}
-func (u *User) CheckPassword(providedPassword string) bool {
+func (u *User) CheckPassword(providedPassword string) (bool, error) {
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(providedPassword))
- return err == nil
+ if err != nil {
+ return false, err
+ }
+ return true, nil
}
func (u *User) SafeUser() map[string]interface{} {
diff --git a/report_part_II.pdf b/report_part_II.pdf
new file mode 100644
index 0000000..fabf906
Binary files /dev/null and b/report_part_II.pdf differ
diff --git a/repositories/message.sqlite.repository.go b/repositories/message.sqlite.repository.go
index 6d80f73..5af057f 100644
--- a/repositories/message.sqlite.repository.go
+++ b/repositories/message.sqlite.repository.go
@@ -24,7 +24,7 @@ func (r *SqliteMessageRepository) SendMessage(text string, fromId, toId uint64)
func (r *SqliteMessageRepository) GetMessages(fromId, toId uint64) ([]*models.Message, error) {
var messages []*models.Message
- if err := r.db.Where("from_id = ? AND to_id = ?", fromId, toId).Find(&messages).Error; err != nil {
+ if err := r.db.Where("from_id = ? AND to_id = ? OR from_id = ? AND to_id = ?", fromId, toId, toId, fromId).Order("created_at desc").Find(&messages).Error; err != nil {
return nil, err
}
return messages, nil
diff --git a/repositories/user.sqlite.repository.go b/repositories/user.sqlite.repository.go
index b393bd8..fefa9e6 100644
--- a/repositories/user.sqlite.repository.go
+++ b/repositories/user.sqlite.repository.go
@@ -68,7 +68,8 @@ func (r *SqliteUserRepository) CheckPassword(username string, password string) (
user, err := r.GetUserByUsername(username); if err != nil {
return nil, errors.New("user not found")
}
- if !user.CheckPassword(password) {
+ isValid, err := user.CheckPassword(password)
+ if err != nil || !isValid {
return nil, errors.New("invalid password")
}
// save timestamp of last login