Skip to content
This repository has been archived by the owner on Jun 2, 2024. It is now read-only.

Commit

Permalink
Follow ups for UserHandler
Browse files Browse the repository at this point in the history
  • Loading branch information
Frecherenkel60 committed May 22, 2024
1 parent b802148 commit 6b964a2
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 118 deletions.
97 changes: 20 additions & 77 deletions internal/handlers/post_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ type PostHandler struct {
var hashtagRegex = regexp.MustCompile(`#\w+`)
var errTransaction = errors.New("error beginning transaction")
var bearerPrefix = "Bearer "
var psql = sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
var post_psql = sq.StatementBuilder.PlaceholderFormat(sq.Dollar)

// NewPostHandler returns a new PostHandler with the provided managers and validator.
func NewPostHandler(databaseManager *managers.DatabaseMgr, jwtManager *managers.JWTMgr) PostHdl {
Expand Down Expand Up @@ -79,7 +79,7 @@ func (handler *PostHandler) CreatePost(ctx *gin.Context) {
var accuracy pgtype.Int4
var createdAt pgtype.Timestamptz

repostedPost := psql.Select("content", "posts.created_at", "users.username", "users.nickname",
repostedPost := post_psql.Select("content", "posts.created_at", "users.username", "users.nickname",
"users.profile_picture_url", "longitude", "latitude", "accuracy", "repost_post_id").
From("alpha_schema.posts").
InnerJoin("alpha_schema.users ON author_id = user_id").
Expand Down Expand Up @@ -122,7 +122,7 @@ func (handler *PostHandler) CreatePost(ctx *gin.Context) {
postId := uuid.New()
createdAt := time.Now()
arguments := []interface{}{postId, userId, createPostRequest.Content, createdAt}
insertQuery := psql.Insert("alpha_schema.posts").Columns("post_id", "author_id", "content", "created_at")
insertQuery := post_psql.Insert("alpha_schema.posts").Columns("post_id", "author_id", "content", "created_at")

if createPostRequest.Location != (schemas.LocationDTO{}) {
insertQuery = insertQuery.Columns("longitude", "latitude", "accuracy")
Expand All @@ -147,15 +147,15 @@ func (handler *PostHandler) CreatePost(ctx *gin.Context) {
for _, hashtag := range hashtags {
hashtagId := uuid.New()

queryString, args, _ := psql.Insert("alpha_schema.hashtags").Columns("hashtag_id", "content").
queryString, args, _ := post_psql.Insert("alpha_schema.hashtags").Columns("hashtag_id", "content").
Values(hashtagId, hashtag).Suffix("ON CONFLICT (content) DO UPDATE SET content=alpha_schema.hashtags.content " +
"RETURNING hashtag_id").ToSql()
if err := tx.QueryRow(ctx, queryString, args...).Scan(&hashtagId); err != nil {
utils.WriteAndLogError(ctx, goerrors.DatabaseError, http.StatusInternalServerError, err)
return
}

queryString, args, _ = psql.Insert("alpha_schema.many_posts_has_many_hashtags").Columns("post_id_posts", "hashtag_id_hashtags").
queryString, args, _ = post_psql.Insert("alpha_schema.many_posts_has_many_hashtags").Columns("post_id_posts", "hashtag_id_hashtags").
Values(postId, hashtagId).ToSql()
if _, err = tx.Exec(ctx, queryString, args...); err != nil {
utils.WriteAndLogError(ctx, goerrors.DatabaseError, http.StatusInternalServerError, err)
Expand All @@ -164,7 +164,7 @@ func (handler *PostHandler) CreatePost(ctx *gin.Context) {
}

// Get the author
queryString, args, _ := psql.Select("username", "nickname", "profile_picture_url").
queryString, args, _ := post_psql.Select("username", "nickname", "profile_picture_url").
From("alpha_schema.users").Where("user_id = ?", userId).ToSql()
row := tx.QueryRow(ctx, queryString, args...)

Expand Down Expand Up @@ -273,15 +273,15 @@ func (handler *PostHandler) DeletePost(ctx *gin.Context) {
func (handler *PostHandler) QueryPosts(ctx *gin.Context) {
// Get the query parameters
q := ctx.Query(utils.QueryParamKey)
limit, lastPostId := parseLimitAndPostId(ctx.Query(utils.LimitParamKey), ctx.Query(utils.PostIdParamKey))
limit, lastPostId := utils.ParseLimitAndPostId(ctx.Query(utils.LimitParamKey), ctx.Query(utils.PostIdParamKey))
limitInt, _ := strconv.Atoi(limit)

// Get the user ID from the JWT token
claims := ctx.Value(utils.ClaimsKey.String()).(jwt.MapClaims)
userId := claims["sub"].(string)

// Build dataQueryString
dataQueryBuilder := psql.Select().
dataQueryBuilder := post_psql.Select().
Columns("posts.post_id", "posts.content", "posts.created_at", "posts.longitude", "posts.latitude", "posts.accuracy").
Columns("users.username", "users.nickname", "users.profile_picture_url").
Columns("repost.content", "repost.created_at", "repost.longitude", "repost.latitude", "repost.accuracy").
Expand All @@ -306,7 +306,7 @@ func (handler *PostHandler) QueryPosts(ctx *gin.Context) {
"repost.longitude", "repost.latitude", "repost.accuracy", "repost_author.username", "repost_author.nickname", "repost_author.profile_picture_url").
OrderBy("posts.created_at DESC").Limit(uint64(limitInt))

countQueryBuilder := psql.
countQueryBuilder := post_psql.
Select("COUNT(DISTINCT posts.post_id)").
From("alpha_schema.posts").
InnerJoin("alpha_schema.users ON author_id = user_id").
Expand Down Expand Up @@ -412,11 +412,11 @@ func determineFeedType(c *gin.Context, handler *PostHandler) (bool, jwt.Claims,
// and returns the posts along with pagination details.
func (handler *PostHandler) retrieveFeed(ctx *gin.Context, publicFeedWanted bool,
claims jwt.Claims) ([]*schemas.PostDTO, int, string, string, error) {
limit, lastPostId := parseLimitAndPostId(ctx.Query(utils.LimitParamKey), ctx.Query(utils.PostIdParamKey))
limit, lastPostId := utils.ParseLimitAndPostId(ctx.Query(utils.LimitParamKey), ctx.Query(utils.PostIdParamKey))
var userId string

countQueryBuilder := psql.Select("COUNT(*)").From("alpha_schema.posts")
dataQueryBuilder := psql.Select().
countQueryBuilder := post_psql.Select("COUNT(*)").From("alpha_schema.posts")
dataQueryBuilder := post_psql.Select().
Columns("posts.post_id", "posts.content", "posts.created_at", "posts.longitude", "posts.latitude", "posts.accuracy").
Columns("users.username", "users.nickname", "users.profile_picture_url").
Columns("repost.content", "repost.created_at", "repost.longitude", "repost.latitude", "repost.accuracy").
Expand Down Expand Up @@ -495,76 +495,14 @@ func (handler *PostHandler) retrieveCountAndRecords(ctx *gin.Context, countQuery
}

// Iterate over the rows and create the post DTOs
posts := make([]*schemas.PostDTO, 0)

for rows.Next() {
post := &schemas.PostDTO{}
var createdAt time.Time
var longitude, latitude, repostLongitude, repostLatitude pgtype.Float8
var accuracy, repostAccuracy pgtype.Int4
var repostContent, repostAuthorUsername, repostAuthorNickname, repostAuthorProfilePictureURL pgtype.Text
var repostCreatedAt pgtype.Timestamptz

if err := rows.Scan(&post.PostId, &post.Content, &createdAt, &longitude, &latitude, &accuracy,
&post.Author.Username, &post.Author.Nickname, &post.Author.ProfilePictureURL,
&repostContent, &repostCreatedAt, &repostLongitude, &repostLatitude, &repostAccuracy,
&repostAuthorUsername, &repostAuthorNickname, &repostAuthorProfilePictureURL,
&post.Likes, &post.Liked); err != nil {
return 0, nil, goerrors.DatabaseError, http.StatusInternalServerError, err
}

if longitude.Valid && latitude.Valid && accuracy.Valid {
post.Location = &schemas.LocationDTO{
Longitude: longitude.Float64,
Latitude: latitude.Float64,
Accuracy: accuracy.Int32,
}
}

if repostContent.Valid && repostCreatedAt.Valid && repostAuthorNickname.Valid &&
repostAuthorProfilePictureURL.Valid && repostAuthorUsername.Valid {
// Set the repost DTO
post.Repost = &schemas.RepostDTO{
Content: repostContent.String,
CreationDate: repostCreatedAt.Time.Format(time.RFC3339),
Author: schemas.AuthorDTO{
Username: repostAuthorUsername.String,
Nickname: repostAuthorNickname.String,
ProfilePictureURL: repostAuthorProfilePictureURL.String,
},
}

if repostLongitude.Valid && repostLatitude.Valid && repostAccuracy.Valid {
post.Repost.Location = &schemas.LocationDTO{
Longitude: repostLongitude.Float64,
Latitude: repostLatitude.Float64,
Accuracy: repostAccuracy.Int32,
}
}
}

post.CreationDate = createdAt.Format(time.RFC3339)
posts = append(posts, post)
posts, err := utils.CreatePostDtoFromRows(rows)
if err != nil {
return 0, nil, goerrors.DatabaseError, http.StatusInternalServerError, err
}

return count, posts, nil, 0, nil
}

// parseLimitAndPostId parses the 'limit' and 'lastPostId' from the query parameters and provides default values if necessary.
func parseLimitAndPostId(limit, lastPostId string) (string, string) {
intLimit, err := strconv.Atoi(limit)
if err != nil || intLimit > 10 || intLimit < 1 {
limit = "10"
}

postId, err := uuid.Parse(lastPostId)
if err != nil || postId == uuid.Nil {
lastPostId = ""
}

return limit, lastPostId
}

func (handler *PostHandler) CreateLike(ctx *gin.Context) {
var err error
tx := utils.BeginTransaction(ctx, handler.DatabaseManager.GetPool())
Expand All @@ -576,6 +514,11 @@ func (handler *PostHandler) CreateLike(ctx *gin.Context) {

// Get the post ID from the URL
postId := ctx.Param(utils.PostIdParamKey)
if _, err := uuid.Parse(postId); err != nil {
utils.WriteAndLogError(ctx, goerrors.PostNotFound, http.StatusBadRequest, err)
return
}

// Get the user ID from the JWT token
claims := ctx.Value(utils.ClaimsKey.String()).(jwt.MapClaims)
userId := claims["sub"].(string)
Expand Down
69 changes: 28 additions & 41 deletions internal/handlers/user_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strconv"
"time"

sq "github.com/Masterminds/squirrel"
"github.com/gin-gonic/gin"

"github.com/golang-jwt/jwt/v5"
Expand Down Expand Up @@ -52,6 +53,7 @@ func NewUserHandler(databaseManager *managers.DatabaseMgr, jwtManager *managers.
}

var errInvalidToken = errors.New("invalid token")
var user_psql = sq.StatementBuilder.PlaceholderFormat(sq.Dollar)

// RegisterUser handles the registration of a new user by validating the request, checking for username or email availability,
// hashing the password, inserting the user into the database, generating and sending an activation token, and committing the transaction.
Expand Down Expand Up @@ -499,7 +501,6 @@ func (handler *UserHandler) RefreshToken(ctx *gin.Context) {
utils.WriteAndLogResponse(ctx, tokenDto, http.StatusOK)
}

// RetrieveUserPosts handles fetching posts of a user based on the username in the path parameter and sends a paginated list of posts.
func retrieveUserIdAndEmail(ctx *gin.Context, tx pgx.Tx, username string) (string, uuid.UUID, bool) {
// Get the user ID
queryString := "SELECT email, user_id FROM alpha_schema.users WHERE username = $1"
Expand Down Expand Up @@ -696,6 +697,7 @@ func generateTokenPair(handler *UserHandler, userId, username string) (*schemas.
return tokenPair, nil
}

// RetrieveUserPosts handles fetching posts of a user based on the username in the path parameter and sends a paginated list of posts.
func (handler *UserHandler) RetrieveUserPosts(ctx *gin.Context) {
// Get the username from URL parameter
username := ctx.Param(utils.UsernameKey)
Expand All @@ -708,53 +710,38 @@ func (handler *UserHandler) RetrieveUserPosts(ctx *gin.Context) {
claims := ctx.Value(utils.ClaimsKey.String()).(jwt.MapClaims)
userId := claims["sub"].(string)

// Retrieve posts from database
queryString := `SELECT posts.post_id, posts.content, COUNT(likes.post_id) AS likes,
CASE WHEN EXISTS (
SELECT 1
FROM alpha_schema.likes
WHERE likes.user_id = $1
AND likes.post_id = posts.post_id
) THEN TRUE ELSE FALSE
END AS liked, posts.created_at, posts.longitude, posts.latitude, posts.accuracy
FROM alpha_schema.posts JOIN alpha_schema.users u on posts.author_id = u.user_id
LEFT OUTER JOIN alpha_schema.likes ON posts.post_id = likes.post_id
WHERE u.username = $2
GROUP BY posts.post_id, username, nickname, profile_picture_url
ORDER BY posts.created_at DESC `
rows, err := handler.DatabaseManager.GetPool().Query(ctx, queryString, userId, username)
queryStringBuider := user_psql.Select().
Columns("posts.post_id", "posts.content", "posts.created_at", "posts.longitude", "posts.latitude", "posts.accuracy").
Columns("users.username", "users.nickname", "users.profile_picture_url").
Columns("repost.content", "repost.created_at", "repost.longitude", "repost.latitude", "repost.accuracy").
Columns("repost_author.username", "repost_author.nickname", "repost_author.profile_picture_url").
Column("COUNT(likes.post_id) AS likes").
Column("CASE WHEN EXISTS (SELECT 1 FROM alpha_schema.likes WHERE likes.user_id = ? AND likes.post_id = posts.post_id) THEN TRUE ELSE FALSE END AS liked", userId).
From("alpha_schema.posts").
InnerJoin("alpha_schema.users ON posts.author_id = user_id").
InnerJoin("alpha_schema.many_posts_has_many_hashtags ON posts.post_id = post_id_posts").
InnerJoin("alpha_schema.hashtags ON hashtag_id = hashtag_id_hashtags").
LeftJoin("alpha_schema.likes ON posts.post_id = likes.post_id").
LeftJoin("alpha_schema.posts AS repost ON posts.repost_post_id = repost.post_id").
LeftJoin("alpha_schema.users AS repost_author ON repost.author_id = repost_author.user_id").
Where("users.username = ?", username).
GroupBy("posts.post_id", "users.username", "users.nickname", "users.profile_picture_url", "repost.content", "repost.created_at",
"repost.longitude", "repost.latitude", "repost.accuracy", "repost_author.username", "repost_author.nickname", "repost_author.profile_picture_url").
OrderBy("posts.created_at DESC").Limit(uint64(limit))

queryString, args, _ := queryStringBuider.ToSql()
rows, err := handler.DatabaseManager.GetPool().Query(ctx, queryString, args...)
if err != nil {
utils.WriteAndLogError(ctx, goerrors.DatabaseError, http.StatusInternalServerError, err)
return
}
defer rows.Close()

// Create a list of posts
posts := make([]schemas.PostDTO, 0)
var createdAt pgtype.Timestamptz
var longitude, latitude pgtype.Float8
var accuracy pgtype.Int4

for rows.Next() {
post := schemas.PostDTO{}
if err := rows.Scan(&post.PostId, &post.Content, &post.Likes, &post.Liked, &createdAt, &longitude, &latitude, &accuracy); err != nil {
utils.WriteAndLogError(ctx, goerrors.DatabaseError, http.StatusInternalServerError, err)
return
}

if longitude.Valid && latitude.Valid {
post.Location = &schemas.LocationDTO{
Longitude: longitude.Float64,
Latitude: latitude.Float64,
}
}

if accuracy.Valid {
post.Location.Accuracy = accuracy.Int32
}

post.CreationDate = createdAt.Time.Format(time.RFC3339)
posts = append(posts, post)
posts, err := utils.CreatePostDtoFromRows(rows)
if err != nil {
utils.WriteAndLogError(ctx, goerrors.DatabaseError, http.StatusInternalServerError, err)
return
}

utils.SendPaginatedResponse(ctx, posts, offset, limit, len(posts))
Expand Down
74 changes: 74 additions & 0 deletions internal/utils/post.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package utils

import (
"strconv"
"time"

"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"github.com/wwi21seb-projekt/server-alpha/internal/schemas"
)

func CreatePostDtoFromRows(rows pgx.Rows) ([]*schemas.PostDTO, error) {
var posts []*schemas.PostDTO

for rows.Next() {
post := &schemas.PostDTO{}
var createdAt time.Time
var longitude, latitude, repostLongitude, repostLatitude pgtype.Float8
var accuracy, repostAccuracy pgtype.Int4
var repostContent, repostAuthorUsername, repostAuthorNickname, repostAuthorProfilePictureURL pgtype.Text
var repostCreatedAt pgtype.Timestamptz

if err := rows.Scan(&post.PostId, &post.Content, &createdAt, &longitude, &latitude, &accuracy,
&post.Author.Username, &post.Author.Nickname, &post.Author.ProfilePictureURL,
&repostContent, &repostCreatedAt, &repostLongitude, &repostLatitude, &repostAccuracy,
&repostAuthorUsername, &repostAuthorNickname, &repostAuthorProfilePictureURL,
&post.Likes, &post.Liked); err != nil {
return nil, err
}

if repostContent.Valid && repostCreatedAt.Valid && repostAuthorNickname.Valid &&
repostAuthorProfilePictureURL.Valid && repostAuthorUsername.Valid {
// Set the repost DTO
post.Repost = &schemas.RepostDTO{
Content: repostContent.String,
CreationDate: repostCreatedAt.Time.Format(time.RFC3339),
Author: schemas.AuthorDTO{
Username: repostAuthorUsername.String,
Nickname: repostAuthorNickname.String,
ProfilePictureURL: repostAuthorProfilePictureURL.String,
},
}

if repostLongitude.Valid && repostLatitude.Valid && repostAccuracy.Valid {
post.Repost.Location = &schemas.LocationDTO{
Longitude: repostLongitude.Float64,
Latitude: repostLatitude.Float64,
Accuracy: repostAccuracy.Int32,
}
}
}

post.CreationDate = createdAt.Format(time.RFC3339)
posts = append(posts, post)
}

return posts, nil
}

// ParseLimitAndPostId parses the 'limit' and 'lastPostId' from the query parameters and provides default values if necessary.
func ParseLimitAndPostId(limit, lastPostId string) (string, string) {
intLimit, err := strconv.Atoi(limit)
if err != nil || intLimit > 10 || intLimit < 1 {
limit = "10"
}

postId, err := uuid.Parse(lastPostId)
if err != nil || postId == uuid.Nil {
lastPostId = ""
}

return limit, lastPostId
}

0 comments on commit 6b964a2

Please sign in to comment.