package posts import ( "context" "database/sql" "time" "backend/app/requests" "backend/database" "backend/database/models/qvyun_v2/public/model" "backend/database/models/qvyun_v2/public/table" "backend/providers/hashids" "backend/providers/otel" . "github.com/go-jet/jet/v2/postgres" "github.com/samber/lo" log "github.com/sirupsen/logrus" "go.opentelemetry.io/otel/attribute" semconv "go.opentelemetry.io/otel/semconv/v1.4.0" ) // @provider:except type Service struct { db *sql.DB hashIds *hashids.Hasher log *log.Entry `inject:"false"` } func (svc *Service) Prepare() error { svc.log = log.WithField("module", "posts.service") _ = Int(1) return nil } // GetBoughtPosts func (svc *Service) GetBoughtPosts(ctx context.Context, pagination *requests.Pagination, filter *UserPostFilter) ([]model.Posts, int64, error) { _, span := otel.Start(ctx, "users.service.GetBoughtPosts") defer span.End() span.SetAttributes( attribute.Int64("user.id", filter.UserID), attribute.Int64("page.page", pagination.Page), attribute.Int64("page.limit", pagination.Limit), ) tbl := table.Posts boughtIds, err := svc.GetUserBoughtIDs(ctx, filter.TenantID, filter.UserID) if err != nil { return nil, 0, err } if len(boughtIds) == 0 { return nil, 0, nil } idExprs := lo.Map(boughtIds, func(id int64, _ int) Expression { return Int64(id) }) cond := tbl.ID.IN( idExprs..., ).AND( tbl.TenantID.EQ(Int64(filter.TenantID)), ).AND( tbl.UserID.EQ(Int64(filter.UserID)), ) if filter.CreatedAt != nil { cond = cond.AND(tbl.CreatedAt.LT_EQ(TimestampT(*filter.CreatedAt))) } if filter.Keyword != nil { cond = cond.AND( tbl.Title. LIKE(String(database.WrapLike(*filter.Keyword))). OR( tbl.Description.LIKE(String(database.WrapLike(*filter.Keyword))), ). OR( tbl.Content.LIKE(String(database.WrapLike(*filter.Keyword))), ), ) } cntStmt := tbl.SELECT(COUNT(tbl.ID).AS("cnt")).WHERE(cond) span.SetAttributes(semconv.DBStatementKey.String(cntStmt.DebugSql())) var count struct { Cnt int64 } if err := cntStmt.QueryContext(ctx, svc.db, &count); err != nil { return nil, 0, err } stmt := tbl. SELECT(tbl.AllColumns). ORDER_BY(tbl.ID.DESC()). LIMIT(pagination.Limit). OFFSET(pagination.Offset()) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) var posts []model.Posts if err := stmt.QueryContext(ctx, svc.db, &posts); err != nil { return nil, 0, err } return posts, count.Cnt, nil } // GetPosts func (svc *Service) GetPosts(ctx context.Context, pagination *requests.Pagination, filter *UserPostFilter) ([]model.Posts, int64, error) { _, span := otel.Start(ctx, "users.service.GetPosts") defer span.End() span.SetAttributes( attribute.Int64("user.id", filter.UserID), attribute.Int64("page.page", pagination.Page), attribute.Int64("page.limit", pagination.Limit), ) tbl := table.Posts cond := tbl.DeletedAt.IS_NULL() if filter.ID != nil { cond = cond.AND(tbl.ID.EQ(Int64(*filter.ID))) } if filter.UserID != 0 { cond = cond.AND(tbl.UserID.EQ(Int64(filter.UserID))) } if filter.CreatedAt != nil { cond = cond.AND(tbl.CreatedAt.LT_EQ(TimestampT(*filter.CreatedAt))) } if filter.TenantID != 0 { cond = cond.AND(tbl.TenantID.EQ(Int64(filter.TenantID))) } if filter.Keyword != nil { cond = cond.AND( tbl.Title. LIKE(String(database.WrapLike(*filter.Keyword))). OR( tbl.Description.LIKE(String(database.WrapLike(*filter.Keyword))), ). OR( tbl.Content.LIKE(String(database.WrapLike(*filter.Keyword))), ), ) } cntStmt := tbl.SELECT(COUNT(tbl.ID).AS("cnt")).WHERE(cond) span.SetAttributes(semconv.DBStatementKey.String(cntStmt.DebugSql())) var count struct { Cnt int64 } if err := cntStmt.QueryContext(ctx, svc.db, &count); err != nil { return nil, 0, err } stmt := tbl. SELECT(tbl.AllColumns). ORDER_BY(tbl.ID.DESC()). LIMIT(pagination.Limit). OFFSET(pagination.Offset()) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) var posts []model.Posts if err := stmt.QueryContext(ctx, svc.db, &posts); err != nil { return nil, 0, err } return posts, count.Cnt, nil } // ForceGetPostByID func (svc *Service) ForceGetPostByID(ctx context.Context, id int64) (*model.Posts, error) { _, span := otel.Start(ctx, "users.service.ForceGetPostByID") defer span.End() span.SetAttributes(attribute.Int64("post.id", id)) tbl := table.Posts stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(id))) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) var post model.Posts if err := stmt.QueryContext(ctx, svc.db, &post); err != nil { return nil, err } return &post, nil } // GetPostByID func (svc *Service) GetPostByID(ctx context.Context, id int64) (*model.Posts, error) { _, span := otel.Start(ctx, "users.service.GetPostByID") defer span.End() span.SetAttributes(attribute.Int64("post.id", id)) tbl := table.Posts stmt := tbl.SELECT(tbl.AllColumns).WHERE(tbl.ID.EQ(Int64(id)).AND(tbl.DeletedAt.IS_NULL())) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) var post model.Posts if err := stmt.QueryContext(ctx, svc.db, &post); err != nil { return nil, err } return &post, nil } // GetUserBoughtPosts func (svc *Service) GetUserBoughtIDs(ctx context.Context, tenantID, userID int64) ([]int64, error) { _, span := otel.Start(ctx, "users.service.GetUserBoughtIDs") defer span.End() span.SetAttributes( attribute.Int64("tenant.id", tenantID), attribute.Int64("user.id", userID), ) tbl := table.UserBoughtPosts stmt := tbl. SELECT(tbl.PostID.AS("post_id")). WHERE( tbl.TenantID.EQ(Int64(tenantID)).AND( tbl.UserID.EQ(Int64(userID)), ), ) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) type tmp struct { PostID int64 } var results []tmp if err := stmt.QueryContext(ctx, svc.db, &results); err != nil { return nil, err } return lo.Map(results, func(item tmp, _ int) int64 { return item.PostID }), nil } // Create func (svc *Service) Create(ctx context.Context, tenant *model.Tenants, user *model.Users, post *model.Posts) error { _, span := otel.Start(ctx, "users.service.Create") defer span.End() tbl := table.Posts stmt := tbl.INSERT(tbl.MutableColumns).MODEL(post) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) if _, err := stmt.ExecContext(ctx, svc.db); err != nil { return err } return nil } // GetPostByHash func (svc *Service) GetPostByHash(ctx context.Context, tenantID int64, hash string) (*model.Posts, error) { _, span := otel.Start(ctx, "users.service.GetPostByHash") defer span.End() span.SetAttributes( attribute.Int64("tenant.id", tenantID), attribute.String("hash", hash), ) postId, err := svc.hashIds.DecodeOnlyInt64(hash) if err != nil { return nil, err } return svc.GetPostByID(ctx, postId) } // Delete func (svc *Service) Delete(ctx context.Context, tenantID, userID, postID int64) error { _, span := otel.Start(ctx, "users.service.Delete") defer span.End() span.SetAttributes( attribute.Int64("tenant.id", tenantID), attribute.Int64("user.id", userID), attribute.Int64("post.id", postID), ) tbl := table.Posts stmt := tbl. UPDATE(). SET( tbl.DeletedAt.SET(TimestampT(time.Now())), ). WHERE( tbl.ID.EQ(Int64(postID)).AND( tbl.TenantID.EQ(Int64(tenantID)).AND( tbl.UserID.EQ(Int64(userID)), ), ), ) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) if _, err := stmt.ExecContext(ctx, svc.db); err != nil { return err } return nil } // Update func (svc *Service) Update(ctx context.Context, tenantID, userID, postID int64, post *model.Posts) error { _, span := otel.Start(ctx, "users.service.Update") defer span.End() span.SetAttributes( attribute.Int64("tenant.id", tenantID), attribute.Int64("user.id", userID), attribute.Int64("post.id", postID), ) tbl := table.Posts stmt := tbl. UPDATE( tbl.MutableColumns.Except( tbl.TenantID, tbl.UserID, tbl.CreatedAt, tbl.DeletedAt, ), ). MODEL(post). WHERE( tbl.ID.EQ(Int64(postID)).AND( tbl.TenantID.EQ(Int64(tenantID)).AND( tbl.UserID.EQ(Int64(userID)), ), ), ) span.SetAttributes(semconv.DBStatementKey.String(stmt.DebugSql())) if _, err := stmt.ExecContext(ctx, svc.db); err != nil { return err } return nil }