From c389a90ec750fa312917a3e847035756a1f107ff Mon Sep 17 00:00:00 2001 From: NamanArora Date: Sat, 8 Apr 2023 14:17:34 +0530 Subject: [PATCH 1/3] Add barebones Posts collection --- db/posts/models/post.go | 23 +++++++++++++++++++++++ db/posts/posts.go | 13 +++++++++++++ db/posts/posts_repository.go | 29 +++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 db/posts/models/post.go create mode 100644 db/posts/posts.go create mode 100644 db/posts/posts_repository.go diff --git a/db/posts/models/post.go b/db/posts/models/post.go new file mode 100644 index 0000000..050dd0c --- /dev/null +++ b/db/posts/models/post.go @@ -0,0 +1,23 @@ +package models + +import "time" + +// this struct will be used in internal packages +// for calling Post interface methods. +type Post struct { + Id string + UserId string + AuthorName string + CreatedAt time.Time + CityId int + CountryId int + Coordinates Coordinates + IsActive bool + Likes int +} + +//TODO: create mongodb schema which will be private to this pkg + +type Coordinates struct { + latitude, longitude float64 +} diff --git a/db/posts/posts.go b/db/posts/posts.go new file mode 100644 index 0000000..59eb9f2 --- /dev/null +++ b/db/posts/posts.go @@ -0,0 +1,13 @@ +package posts + +import ( + "context" + + "github.com/TheDevExperiment/server/db/posts/models" +) + +type Posts interface { + Create(context.Context, models.Post) error + FindNearby(context.Context, models.Coordinates) ([]models.Post, error) + Like(context.Context, string) error +} diff --git a/db/posts/posts_repository.go b/db/posts/posts_repository.go new file mode 100644 index 0000000..34294ee --- /dev/null +++ b/db/posts/posts_repository.go @@ -0,0 +1,29 @@ +package posts + +import ( + "context" + + "github.com/TheDevExperiment/server/db/posts/models" + "go.mongodb.org/mongo-driver/mongo" +) + +type PostsRepository struct { + collection *mongo.Collection +} + +func NewPostsRepository() *PostsRepository { + // use mongodb to find collection here + return nil +} + +func (pr *PostsRepository) Create(context.Context, models.Post) error { + return nil +} + +func (pr *PostsRepository) FindNearby(context.Context, models.Coordinates) ([]models.Post, error) { + return []models.Post{}, nil +} + +func (pr *PostsRepository) Like(context.Context, string) error { + return nil +} From 595711b13d05c187431fdd4faf46879c97df39c7 Mon Sep 17 00:00:00 2001 From: NamanArora Date: Mon, 10 Apr 2023 15:04:03 +0530 Subject: [PATCH 2/3] Update post models --- internal/db/posts/models/post.go | 39 ++++++++++++++++++++++ mongo-seed/posts.json | 57 ++++++++++++-------------------- router/models/post.go | 23 +++++++++++++ 3 files changed, 83 insertions(+), 36 deletions(-) create mode 100644 internal/db/posts/models/post.go create mode 100644 router/models/post.go diff --git a/internal/db/posts/models/post.go b/internal/db/posts/models/post.go new file mode 100644 index 0000000..f8e0f2d --- /dev/null +++ b/internal/db/posts/models/post.go @@ -0,0 +1,39 @@ +package models + +import "time" + +// this struct will be used in internal packages +// for calling Post interface methods. +type Post struct { + Id string `bson:"_id,omitempty"` + UserId int `bson:"userId,omitempty"` + AuthorName string `bson:"authorName,omitempty"` + CreatedAt time.Time `bson:"createdAt,omitempty"` + CityId int `bson:"cityId,omitempty"` + CountryId int `bson:"countryId,omitempty"` + Coordinates Point `bson:"coordinates,omitempty"` + IsActive bool `bson:"isActive,omitempty"` + LikesCount int `bson:"likesCount,omitempty"` + CommentsCount int `bson:"commentsCount,omitempty"` + Content Content `bson:"content,omitempty,inline"` +} + +type Point struct { + Type string `bson:"type"` //needs to be set to Point + Coordinates []float64 `bson:"coordinates"` //[long,lat] +} + +type Content struct { + Text string `bson:"text,omitempty"` + BackgroundUrl string `bson:"backgroundUrl,omitempty"` + BackgroundType BackgroundType `bson:"backgroundType"` +} + +type BackgroundType int + +const ( + PreloadedImage BackgroundType = iota + PreloadedVideo + CustomImage + CustomVideo +) diff --git a/mongo-seed/posts.json b/mongo-seed/posts.json index 6497c30..6eb2ef3 100644 --- a/mongo-seed/posts.json +++ b/mongo-seed/posts.json @@ -1,41 +1,26 @@ [ - { - "_id": {"$oid": "6170c708b210f8e6e31d6c94"}, - "userId": {"$oid": "6170c6d9b210f8e6e31d6c91"}, - "authorName": "John Doe", - "createdAt": {"$date": "2022-10-22T14:30:00.000Z"}, - "content": { - "message": "hello world", - "fetchUrl": null, - "backgroundType": "Image" - }, - "cityId": "New York", - "countryId": "USA", - "coordinate": { + { + "_id": "6061f072c1760a272b4bb49a", + "userId": 12345, + "authorName": "John Doe", + "createdAt": "2023-04-10T10:30:00Z", + "cityId": 1, + "countryId": 2, + "coordinates": { "type": "Point", - "coordinates": [-73.935242, 40.730610] - }, - "isActive": true, - "likes": 5 + "coordinates": [ + -122.4194, + 37.7749 + ] }, - { - "_id": {"$oid": "6170c708b210f8e6e31d6c95"}, - "userId": {"$oid": "6170c6d9b210f8e6e31d6c92"}, - "authorName": "Jane Smith", - "createdAt": {"$date": "2022-10-23T16:30:00.000Z"}, - "content": { - "message": "bye world", - "fetchUrl": null, - "backgroundType": "Image" - }, - "cityId": "Toronto", - "countryId": "Canada", - "coordinate": { - "type": "Point", - "coordinates": [-79.3832, 43.6532] - }, - "isActive": true, - "likes": 10 + "isActive": true, + "likesCount": 10, + "commentsCount": 5, + "content": { + "text": "Lorem ipsum dolor sit amet", + "backgroundUrl": "https://example.com/image.jpg", + "backgroundType": 0 } - ] + } +] diff --git a/router/models/post.go b/router/models/post.go new file mode 100644 index 0000000..7fd5874 --- /dev/null +++ b/router/models/post.go @@ -0,0 +1,23 @@ +package models + +import "github.com/TheDevExperiment/server/internal/db/posts/models" + +type CreatePostRequest struct { + Post models.Post `json:"post" binding:"required"` +} + +type CreatePostResponse struct { + Message string `form:"message" json:"message" binding:"required"` +} + +type NearbyPostRequest struct { + Latitude float64 `json:"latitude" binding:"required"` + Longitude float64 `json:"longitude" binding:"required"` + Page int ` json:"page"` + // Radius int -> if app wants to support tweakable filter +} + +type NearbyPostResponse struct { + Posts []models.Post `json:"posts" binding:"required"` + Message string +} From be45a1ca1c97cb0187876816a557c96c0539527c Mon Sep 17 00:00:00 2001 From: NamanArora Date: Mon, 10 Apr 2023 15:04:26 +0530 Subject: [PATCH 3/3] Add support for post creation and nearby search --- db/posts/models/post.go | 23 ------- db/posts/posts_repository.go | 29 -------- {db => internal/db}/posts/posts.go | 4 +- internal/db/posts/posts_repository.go | 96 +++++++++++++++++++++++++++ internal/post/post.go | 54 +++++++++++++++ router/router.go | 3 + 6 files changed, 155 insertions(+), 54 deletions(-) delete mode 100644 db/posts/models/post.go delete mode 100644 db/posts/posts_repository.go rename {db => internal/db}/posts/posts.go (52%) create mode 100644 internal/db/posts/posts_repository.go create mode 100644 internal/post/post.go diff --git a/db/posts/models/post.go b/db/posts/models/post.go deleted file mode 100644 index 050dd0c..0000000 --- a/db/posts/models/post.go +++ /dev/null @@ -1,23 +0,0 @@ -package models - -import "time" - -// this struct will be used in internal packages -// for calling Post interface methods. -type Post struct { - Id string - UserId string - AuthorName string - CreatedAt time.Time - CityId int - CountryId int - Coordinates Coordinates - IsActive bool - Likes int -} - -//TODO: create mongodb schema which will be private to this pkg - -type Coordinates struct { - latitude, longitude float64 -} diff --git a/db/posts/posts_repository.go b/db/posts/posts_repository.go deleted file mode 100644 index 34294ee..0000000 --- a/db/posts/posts_repository.go +++ /dev/null @@ -1,29 +0,0 @@ -package posts - -import ( - "context" - - "github.com/TheDevExperiment/server/db/posts/models" - "go.mongodb.org/mongo-driver/mongo" -) - -type PostsRepository struct { - collection *mongo.Collection -} - -func NewPostsRepository() *PostsRepository { - // use mongodb to find collection here - return nil -} - -func (pr *PostsRepository) Create(context.Context, models.Post) error { - return nil -} - -func (pr *PostsRepository) FindNearby(context.Context, models.Coordinates) ([]models.Post, error) { - return []models.Post{}, nil -} - -func (pr *PostsRepository) Like(context.Context, string) error { - return nil -} diff --git a/db/posts/posts.go b/internal/db/posts/posts.go similarity index 52% rename from db/posts/posts.go rename to internal/db/posts/posts.go index 59eb9f2..c44deac 100644 --- a/db/posts/posts.go +++ b/internal/db/posts/posts.go @@ -3,11 +3,11 @@ package posts import ( "context" - "github.com/TheDevExperiment/server/db/posts/models" + "github.com/TheDevExperiment/server/internal/db/posts/models" ) type Posts interface { Create(context.Context, models.Post) error - FindNearby(context.Context, models.Coordinates) ([]models.Post, error) + FindNearby(context.Context, float64, float64) ([]models.Post, error) Like(context.Context, string) error } diff --git a/internal/db/posts/posts_repository.go b/internal/db/posts/posts_repository.go new file mode 100644 index 0000000..8f2716a --- /dev/null +++ b/internal/db/posts/posts_repository.go @@ -0,0 +1,96 @@ +package posts + +import ( + "context" + + "github.com/TheDevExperiment/server/internal/db" + "github.com/TheDevExperiment/server/internal/db/posts/models" + "github.com/TheDevExperiment/server/internal/log" + "github.com/spf13/viper" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +type PostsRepository struct { + collection *mongo.Collection +} + +var singletonPostCollection *mongo.Collection + +const defaultNearbySearchRadius = 1000 // 1km +const defaultPageLimit = 10 + +func NewPostsRepository() *PostsRepository { + // use mongodb to find collection here + if singletonPostCollection == nil { + log.Debug("Created connection to post collection") + singletonPostCollection = db.GetCollection(viper.GetString("mongodb.db_name"), "posts") + } + return &PostsRepository{collection: singletonPostCollection} +} + +func (pr *PostsRepository) Create(ctx context.Context, post models.Post) error { + log.Debugf("Received new post create request data: %+v", post) + res, err := pr.collection.InsertOne(ctx, post) + log.Debugf("Response received from mongo: %+v", res) + return err +} + +func prepareNearbySortQuery(latitude, longitude float64) primitive.M { + query := bson.M{ + "coordinates": bson.M{ + "$near": bson.M{ + "$geometry": bson.M{ + "type": "Point", + "coordinates": []float64{longitude, latitude}, + }, + "$maxDistance": defaultNearbySearchRadius, + }, + }, + } + return query +} + +func preparePaginatedNearbySortOpts(page int) *options.FindOptions { + findOptions := options.Find() + findOptions.SetLimit(int64(defaultPageLimit)) // Set the limit to the specified value + findOptions.SetSkip(int64((page - 1) * defaultPageLimit)) // Skip records based on the page number and limit + findOptions.SetSort(bson.D{{Key: "_id", Value: -1}}) // Sort by "_id" field in descending order + findOptions.SetProjection(bson.M{"coordinates": 0}) // Exclude "coordinates" field from the result + return findOptions +} + +func (pr *PostsRepository) FindNearby(ctx context.Context, lat, long float64, page int) ([]models.Post, error) { + log.Debugf("Received new nearby req data: %f %f", lat, long) + query := prepareNearbySortQuery(lat, long) + optsWithPagination := preparePaginatedNearbySortOpts(page) + + cur, err := pr.collection.Find(ctx, query, optsWithPagination) + if err != nil { + return nil, err + } + defer cur.Close(ctx) + + var posts []models.Post + for cur.Next(ctx) { + var post models.Post + err := cur.Decode(&post) + if err != nil { + log.Errorf("Error while parsing doc: %s", err.Error()) + continue + } + posts = append(posts, post) + } + + if err := cur.Err(); err != nil { + return nil, err + } + + return posts, nil +} + +func (pr *PostsRepository) Like(context.Context, string) error { + return nil +} diff --git a/internal/post/post.go b/internal/post/post.go new file mode 100644 index 0000000..f3364ef --- /dev/null +++ b/internal/post/post.go @@ -0,0 +1,54 @@ +package post + +import ( + "net/http" + + "github.com/TheDevExperiment/server/internal/db/posts" + "github.com/TheDevExperiment/server/router/models" + "github.com/gin-gonic/gin" +) + +func CreateV1(c *gin.Context) { + // first bind the req to our model + var req models.CreatePostRequest + var res models.CreatePostResponse + pr := posts.NewPostsRepository() + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + err := pr.Create(c, req.Post) + if err != nil { + httpCode := http.StatusInternalServerError + res.Message = err.Error() + c.JSON(httpCode, res) + return + } + res.Message = "Created new post" + c.JSON(http.StatusOK, res) +} + +func NearbyV1(c *gin.Context) { + // first bind the req to our model + var req models.NearbyPostRequest + var res models.NearbyPostResponse + pr := posts.NewPostsRepository() + + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + posts, err := pr.FindNearby(c, req.Latitude, req.Longitude, req.Page) + if err != nil { + httpCode := http.StatusInternalServerError + res.Message = err.Error() + c.JSON(httpCode, res) + return + } + + res.Posts = posts + c.JSON(http.StatusOK, res) +} diff --git a/router/router.go b/router/router.go index 7f04809..8283fae 100644 --- a/router/router.go +++ b/router/router.go @@ -3,6 +3,7 @@ package router import ( "github.com/TheDevExperiment/server/internal/auth" "github.com/TheDevExperiment/server/internal/db/repositories" + "github.com/TheDevExperiment/server/internal/post" "github.com/gin-gonic/gin" ) @@ -27,5 +28,7 @@ func SetupRouter() *gin.Engine { // define all the routes r.POST("/auth/v1/guest-validate", auth.GuestValidateV1) + r.POST("/post/v1/create", post.CreateV1) + r.POST("/post/v1/nearby", post.NearbyV1) return r }