Skip to content

Commit

Permalink
chore: getFriends api complete
Browse files Browse the repository at this point in the history
  • Loading branch information
aryanA101a committed Dec 16, 2023
1 parent 8fca2d1 commit 5f7ada1
Show file tree
Hide file tree
Showing 6 changed files with 133 additions and 14 deletions.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@

Welcome to the legoshichat API, the official interface for the legoshichat. Unlock seamless integration and access the unique features of legoshichat through this official API.

This API employs JSON Web Tokens (JWT) for authentication. Please note that all endpoints, with the exception of the login and create account functionalities, necessitate the inclusion of an authorized JWT bearer token for access.

## Documentation
Check out documentation [here](https://aryana101a.github.io/legoshichat-backend/).

Also checkout the **postman collection** in the [docs](https://github.com/aryanA101a/legoshichat-backend/tree/main/docs) folder.

## Roadmap
- [x] API Spec
- [ ] API Implementation **WIP**
- [ ] Unit Testing
- [x] API Implementation
- [ ] Unit Testing **30%**
- [ ] CI
- [ ] CD

Expand All @@ -23,4 +27,5 @@ Check out documentation [here](https://aryana101a.github.io/legoshichat-backend/
`docker exec -it gofr-pgsql psql --username=legoshiuser --dbname=legoshichat`


**Note:** In the context of this project, the .env file is intentionally exposed publicly; however, it is crucial to emphasize that this practice is strongly discouraged in a production environment

6 changes: 3 additions & 3 deletions handler/creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ func (authCreator) NewJWT(ctx *gofr.Context, userId string) (string, error) {
return token.SignedString([]byte(secret))
}

func NewMessage(from, to, content string) model.Message {
func NewMessage(senderId, recipientId, content string) model.Message {

return model.Message{
ID: uuid.New().String(),
From: from,
To: to,
From: senderId,
To: recipientId,
Content: content,
Timestamp: time.Now(),
}
Expand Down
23 changes: 21 additions & 2 deletions handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import (
type Handler struct {
Auth store.AuthStore
Message store.MessageStore
Friend store.FriendStore
AuthCreator Creator
}

func New(a store.AuthStore, m store.MessageStore, c Creator) Handler {
return Handler{Auth: a, Message: m, AuthCreator: c}
func New(a store.AuthStore, m store.MessageStore, f store.FriendStore, c Creator) Handler {
return Handler{Auth: a, Message: m, Friend: f, AuthCreator: c}
}

func (h Handler) HandleCreateAccount(ctx *gofr.Context) (interface{}, error) {
Expand Down Expand Up @@ -99,6 +100,8 @@ func (h Handler) HandleSendMessageByID(ctx *gofr.Context) (interface{}, error) {

message := NewMessage(ctx.Value("userId").(string), messageRequest.RecipientID, messageRequest.Content)

h.Friend.AddFriend(ctx, ctx.Value("userId").(string), messageRequest.RecipientID)

err = h.Message.AddMessage(ctx, message)
if err != nil {
ctx.Logger.Error(err)
Expand All @@ -117,6 +120,7 @@ func (h Handler) HandleSendMessageByPhoneNumber(ctx *gofr.Context) (interface{},
ctx.Logger.Error(err)
return nil, e.HttpStatusError(400, "Invalid inputs or missing required fields -"+err.Error())
}

recipientId, err := h.Auth.GetUserIdByPhoneNumber(ctx, messageRequest.RecipientPhoneNumber)
if err != nil {
ctx.Logger.Info("err: ", err.Error())
Expand All @@ -125,8 +129,11 @@ func (h Handler) HandleSendMessageByPhoneNumber(ctx *gofr.Context) (interface{},
}
return nil, e.HttpStatusError(500, "")
}

message := NewMessage(ctx.Value("userId").(string), *recipientId, messageRequest.Content)

h.Friend.AddFriend(ctx, ctx.Value("userId").(string), *recipientId)

err = h.Message.AddMessage(ctx, message)
if err != nil {
ctx.Logger.Error(err)
Expand Down Expand Up @@ -235,6 +242,18 @@ func (h Handler) HandleGetMessages(ctx *gofr.Context) (interface{}, error) {

}

func (h Handler) GetFriends(ctx *gofr.Context) (interface{}, error) {
friends, err := h.Friend.GetFriends(ctx, ctx.Value("userId").(string))
if err != nil {
ctx.Logger.Info("err: ", err.Error())
if err == sql.ErrNoRows {
return nil, e.HttpStatusError(404, "No Friends")
}
return nil, e.HttpStatusError(500, "")
}
return types.Raw{Data: friends}, nil
}

func WithJWTAuth(handlerFunc gofr.Handler, authStore store.AuthStore) gofr.Handler {
return func(ctx *gofr.Context) (interface{}, error) {

Expand Down
18 changes: 13 additions & 5 deletions handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ func TestHandleLogin(t *testing.T) {
err: e.NewError(""),
}


runLoginTest(t, successfulLoginTC, app, Handler{Auth: newMockAuthStore(), AuthCreator: NewCreator()})
runLoginTest(t, invalidRequestBodyTC, app, Handler{Auth: newMockAuthStore(), AuthCreator: NewCreator()})
runLoginTest(t, fetchAccountErrorTC, app, Handler{Auth: fetchAccountErrorTcmockAuthStore{}, AuthCreator: NewCreator()})
Expand Down Expand Up @@ -299,6 +298,16 @@ func (addMessageErrorTCMessageStore) GetMessages(ctx *gofr.Context, senderId, re
return nil, nil
}

type mockFriendStore struct{}

func (f mockFriendStore) AddFriend(ctx *gofr.Context, senderId, recieverId string) error {
return nil
}

func (f mockFriendStore) GetFriends(ctx *gofr.Context, userId string) (*[]model.User, error) {
return nil, nil
}

func TestHandleSendMessageByID(t *testing.T) {
app := gofr.New()

Expand All @@ -323,10 +332,9 @@ func TestHandleSendMessageByID(t *testing.T) {
err: e.HttpStatusError(500, "message store error"),
}


runSendMessageTest(t, validInputTC, app, Handler{Message: successfulTCMessageStore{}})
runSendMessageTest(t, invalidInputTC, app, Handler{Message: successfulTCMessageStore{}})
runSendMessageTest(t, messageStoreErrorTC, app, Handler{Message: addMessageErrorTCMessageStore{}})
runSendMessageTest(t, validInputTC, app, Handler{Message: successfulTCMessageStore{}, Friend: mockFriendStore{}})
runSendMessageTest(t, invalidInputTC, app, Handler{Message: successfulTCMessageStore{}, Friend: mockFriendStore{}})
runSendMessageTest(t, messageStoreErrorTC, app, Handler{Message: addMessageErrorTCMessageStore{}, Friend: mockFriendStore{}})
}

type testCaseSendMessage struct {
Expand Down
7 changes: 5 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,22 @@ func main() {

authStore := store.NewAuthStore(app.DB())
messageStore := store.NewMessageStore(app.DB())
friendStore := store.NewFriendStore(app.DB())
authCreator := handler.NewCreator()
h := handler.Handler{Auth: authStore, Message: messageStore, AuthCreator: authCreator}
h := handler.Handler{Auth: authStore, Message: messageStore, Friend: friendStore, AuthCreator: authCreator}

app.POST("/create-account", h.HandleCreateAccount)
app.POST("/login", h.HandleLogin)

app.GET("/message/{id}", handler.WithJWTAuth(h.HandleGetMessage, authStore))
app.PUT("/message/{id}", handler.WithJWTAuth(h.HandlePutMessage, authStore))
app.DELETE("/message/{id}", handler.WithJWTAuth(h.HandleDeleteMessage, authStore))
app.POST("/message/sendById", handler.WithJWTAuth(h.HandleSendMessageByID, authStore))
app.POST("/message/sendByPhoneNumber", handler.WithJWTAuth(h.HandleSendMessageByPhoneNumber, authStore))
app.POST("/messages", handler.WithJWTAuth(h.HandleGetMessages, authStore))

app.GET("/friends", handler.WithJWTAuth(h.GetFriends, authStore))

port, err := strconv.Atoi(app.Config.Get("HTTP_PORT"))
if err == nil {
app.Server.HTTP.Port = port
Expand Down
84 changes: 84 additions & 0 deletions store/friendStore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package store

import (
"github.com/aryanA101a/legoshichat-backend/model"
"gofr.dev/pkg/datastore"
"gofr.dev/pkg/gofr"
)

type friend struct {
}

type FriendStore interface {
AddFriend(ctx *gofr.Context,senderId,recieverId string)error
GetFriends(ctx *gofr.Context,userId string)(*[]model.User,error)

}

func NewFriendStore(db *datastore.SQLClient) FriendStore {
f:= friend{}
f.init(db)
return f
}

func (f friend) init(db *datastore.SQLClient) {
createFriendsTable(db)
}

func (f friend)AddFriend(ctx *gofr.Context,senderId,recieverId string)error{
_, err := ctx.DB().ExecContext(ctx, `INSERT INTO friends (account_id1, account_id2)
VALUES (LEAST($1, $2)::UUID, GREATEST($1, $2)::UUID)
ON CONFLICT DO NOTHING`, senderId,recieverId)
return err
}

func (f friend)GetFriends(ctx *gofr.Context,userId string)(*[]model.User,error){
query:=`SELECT u.id, u.name,u.phoneNumber
FROM friends f
JOIN accounts u ON u.id = f.account_id2
WHERE f.account_id1 = $1
UNION
SELECT u.id, u.name,u.phoneNumber
FROM friends f
JOIN accounts u ON u.id = f.account_id1
WHERE f.account_id2 = $1`

rows, err := ctx.DB().QueryContext(ctx, query, userId)
if err != nil {
return nil, err
}

defer rows.Close()

users := make([]model.User, 0)

for rows.Next() {
var user model.User

err = rows.Scan(&user.ID, &user.Name, &user.PhoneNumber,)
if err != nil {
return nil, err
}

users = append(users, user)
}

err = rows.Err()
if err != nil {
return nil, err
}

return &users, nil
}

func createFriendsTable(db *datastore.SQLClient) error {
query := `CREATE TABLE IF NOT EXISTS friends (
account_id1 UUID NOT NULL,
account_id2 UUID NOT NULL,
FOREIGN KEY (account_id1) REFERENCES accounts(id),
FOREIGN KEY (account_id2) REFERENCES accounts(id),
PRIMARY KEY (account_id1, account_id2)
);`
_, err := db.Exec(query)
return err
}

0 comments on commit 5f7ada1

Please sign in to comment.