[hw-8] add: comment service

This commit is contained in:
3ybacTuK
2025-07-26 23:47:18 +03:00
parent 6420eaf3d7
commit 6e0d90a6d5
29 changed files with 1249 additions and 725 deletions

View 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
}

View 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()
}
}
}

View 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
}

View 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)
}

View File

@@ -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: