mirror of
https://github.com/3ybactuk/marketplace-go-service-project.git
synced 2025-10-30 14:03:45 +03:00
[hw-8] add: comment service
This commit is contained in:
59
comments/infra/config/config.go
Normal file
59
comments/infra/config/config.go
Normal file
@@ -0,0 +1,59 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App struct {
|
||||
EditInterval time.Duration `yaml:"edit_interval"`
|
||||
}
|
||||
|
||||
Service struct {
|
||||
Host string `yaml:"host"`
|
||||
GRPCPort string `yaml:"grpc_port"`
|
||||
HTTPPort string `yaml:"http_port"`
|
||||
LogLevel string `yaml:"log_level"`
|
||||
} `yaml:"service"`
|
||||
|
||||
DbShards []struct {
|
||||
Host string `yaml:"host"`
|
||||
Port string `yaml:"port"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
DBName string `yaml:"db_name"`
|
||||
} `yaml:"db_shards"`
|
||||
}
|
||||
|
||||
func LoadConfig(filename string) (*Config, error) {
|
||||
workDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfgRoot := filepath.Join(workDir, "configs")
|
||||
absCfgRoot, _ := filepath.Abs(cfgRoot)
|
||||
|
||||
filePath := filepath.Join(absCfgRoot, filepath.Clean(filename))
|
||||
if !strings.HasPrefix(filePath, absCfgRoot) {
|
||||
return nil, fmt.Errorf("invalid path")
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
config := &Config{}
|
||||
if err := yaml.NewDecoder(f).Decode(config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
40
comments/infra/db/postgres/postgres.go
Normal file
40
comments/infra/db/postgres/postgres.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func NewPools(ctx context.Context, DSNs ...string) ([]*pgxpool.Pool, error) {
|
||||
pools := make([]*pgxpool.Pool, len(DSNs))
|
||||
|
||||
for i, dsn := range DSNs {
|
||||
cfg, err := pgxpool.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
closeOpened(pools[:i])
|
||||
|
||||
return nil, errors.Wrap(err, "pgxpool.ParseConfig")
|
||||
}
|
||||
|
||||
pool, err := pgxpool.NewWithConfig(ctx, cfg)
|
||||
if err != nil {
|
||||
closeOpened(pools[:i])
|
||||
|
||||
return nil, errors.Wrap(err, "pgxpool.NewWithConfig")
|
||||
}
|
||||
|
||||
pools[i] = pool
|
||||
}
|
||||
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
func closeOpened(pools []*pgxpool.Pool) {
|
||||
for _, p := range pools {
|
||||
if p != nil {
|
||||
p.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
24
comments/infra/grpc/middleware/logging.go
Normal file
24
comments/infra/grpc/middleware/logging.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package mw
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
func Logging(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||
raw, _ := protojson.Marshal((req).(proto.Message))
|
||||
log.Debug().Msgf("request: method: %v, req: %s", info.FullMethod, string(raw))
|
||||
|
||||
if resp, err = handler(ctx, req); err != nil {
|
||||
log.Debug().Msgf("response: method: %v, err: %s", info.FullMethod, err.Error())
|
||||
return
|
||||
}
|
||||
rawResp, _ := protojson.Marshal((resp).(proto.Message))
|
||||
log.Debug().Msgf("response: method: %v, resp: %s", info.FullMethod, string(rawResp))
|
||||
|
||||
return
|
||||
}
|
||||
18
comments/infra/grpc/middleware/validate.go
Normal file
18
comments/infra/grpc/middleware/validate.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package mw
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func Validate(ctx context.Context, req any, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
|
||||
if v, ok := req.(interface{ Validate() error }); ok {
|
||||
if err := v.Validate(); err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
}
|
||||
return handler(ctx, req)
|
||||
}
|
||||
@@ -3,14 +3,13 @@ package sqlc
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sort"
|
||||
|
||||
"route256/comments/internal/domain/entity"
|
||||
"route256/comments/internal/domain/model"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"route256/comments/internal/domain/entity"
|
||||
"route256/comments/internal/domain/model"
|
||||
)
|
||||
|
||||
type commentsRepo struct {
|
||||
@@ -33,12 +32,22 @@ func (r *commentsRepo) pickShard(sku int64) *pgxpool.Pool {
|
||||
return r.shard2
|
||||
}
|
||||
|
||||
func (r *commentsRepo) GetCommentByID(ctx context.Context, id int64) (*Comment, error) {
|
||||
func mapComment(c *Comment) *entity.Comment {
|
||||
return &entity.Comment{
|
||||
ID: c.ID,
|
||||
UserID: c.UserID,
|
||||
SKU: c.Sku,
|
||||
CreatedAt: c.CreatedAt.Time,
|
||||
Text: c.Text,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *commentsRepo) GetCommentByID(ctx context.Context, id int64) (*entity.Comment, error) {
|
||||
q1 := New(r.shard1)
|
||||
c, err := q1.GetCommentByID(ctx, id)
|
||||
switch {
|
||||
case err == nil:
|
||||
return c, nil
|
||||
return mapComment(c), nil
|
||||
case errors.Is(err, pgx.ErrNoRows):
|
||||
log.Trace().Msgf("comment with id %d not found in shard 1", id)
|
||||
default:
|
||||
@@ -49,7 +58,7 @@ func (r *commentsRepo) GetCommentByID(ctx context.Context, id int64) (*Comment,
|
||||
c2, err2 := q2.GetCommentByID(ctx, id)
|
||||
switch {
|
||||
case err2 == nil:
|
||||
return c2, nil
|
||||
return mapComment(c2), nil
|
||||
case errors.Is(err2, pgx.ErrNoRows):
|
||||
return nil, model.ErrCommentNotFound
|
||||
default:
|
||||
@@ -57,7 +66,7 @@ func (r *commentsRepo) GetCommentByID(ctx context.Context, id int64) (*Comment,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *commentsRepo) InsertComment(ctx context.Context, comment *entity.Comment) (*Comment, error) {
|
||||
func (r *commentsRepo) InsertComment(ctx context.Context, comment *entity.Comment) (*entity.Comment, error) {
|
||||
shard := r.pickShard(comment.SKU)
|
||||
q := New(shard)
|
||||
|
||||
@@ -72,10 +81,10 @@ func (r *commentsRepo) InsertComment(ctx context.Context, comment *entity.Commen
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
return mapComment(c), nil
|
||||
}
|
||||
|
||||
func (r *commentsRepo) ListCommentsBySku(ctx context.Context, sku int64) ([]*Comment, error) {
|
||||
func (r *commentsRepo) ListCommentsBySku(ctx context.Context, sku int64) ([]*entity.Comment, error) {
|
||||
shard := r.pickShard(sku)
|
||||
q := New(shard)
|
||||
|
||||
@@ -84,13 +93,15 @@ func (r *commentsRepo) ListCommentsBySku(ctx context.Context, sku int64) ([]*Com
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := make([]*Comment, len(list))
|
||||
copy(out, list)
|
||||
out := make([]*entity.Comment, 0, len(list))
|
||||
for _, com := range list {
|
||||
out = append(out, mapComment(com))
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r *commentsRepo) ListCommentsByUser(ctx context.Context, userID int64) ([]*Comment, error) {
|
||||
func (r *commentsRepo) ListCommentsByUser(ctx context.Context, userID int64) ([]*entity.Comment, error) {
|
||||
q1 := New(r.shard1)
|
||||
l1, err1 := q1.ListCommentsByUser(ctx, userID)
|
||||
if err1 != nil {
|
||||
@@ -103,22 +114,18 @@ func (r *commentsRepo) ListCommentsByUser(ctx context.Context, userID int64) ([]
|
||||
return nil, err2
|
||||
}
|
||||
|
||||
merged := make([]*Comment, 0, len(l1)+len(l2))
|
||||
merged = append(merged, l1...)
|
||||
merged = append(merged, l2...)
|
||||
|
||||
sort.Slice(merged, func(i, j int) bool {
|
||||
if merged[i].CreatedAt.Time.Equal(merged[j].CreatedAt.Time) {
|
||||
return merged[i].UserID < merged[j].UserID
|
||||
}
|
||||
|
||||
return merged[i].CreatedAt.Time.After(merged[j].CreatedAt.Time)
|
||||
})
|
||||
merged := make([]*entity.Comment, 0, len(l1)+len(l2))
|
||||
for _, com := range l1 {
|
||||
merged = append(merged, mapComment(com))
|
||||
}
|
||||
for _, com := range l2 {
|
||||
merged = append(merged, mapComment(com))
|
||||
}
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
func (r *commentsRepo) UpdateComment(ctx context.Context, comment *entity.Comment) (*Comment, error) {
|
||||
func (r *commentsRepo) UpdateComment(ctx context.Context, comment *entity.Comment) (*entity.Comment, error) {
|
||||
req := &UpdateCommentParams{
|
||||
ID: comment.ID,
|
||||
Text: comment.Text,
|
||||
@@ -128,7 +135,7 @@ func (r *commentsRepo) UpdateComment(ctx context.Context, comment *entity.Commen
|
||||
c, err := q1.UpdateComment(ctx, req)
|
||||
switch {
|
||||
case err == nil:
|
||||
return c, nil
|
||||
return mapComment(c), nil
|
||||
case errors.Is(err, pgx.ErrNoRows):
|
||||
log.Trace().Msgf("comment with id %d not found in shard 1", req.ID)
|
||||
default:
|
||||
@@ -139,7 +146,7 @@ func (r *commentsRepo) UpdateComment(ctx context.Context, comment *entity.Commen
|
||||
c2, err2 := q2.UpdateComment(ctx, req)
|
||||
switch {
|
||||
case err2 == nil:
|
||||
return c2, nil
|
||||
return mapComment(c2), nil
|
||||
case errors.Is(err2, pgx.ErrNoRows):
|
||||
return nil, model.ErrCommentNotFound
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user