1
This commit is contained in:
325
internal/feed/service.go
Normal file
325
internal/feed/service.go
Normal file
@@ -0,0 +1,325 @@
|
||||
package feed
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
|
||||
"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/database"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/google/uuid"
|
||||
"github.com/lib/pq"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
db *sql.DB
|
||||
schemaName string
|
||||
logger *slog.Logger
|
||||
dbClient database.Client //nolint:unused // TODO: переписать на этого клиента в рамках https://tracker.yandex.ru/MOLVARAZRABOTKA-363
|
||||
}
|
||||
|
||||
func NewService(dbUrl, schemaName string, logger *slog.Logger, dbClient database.Client) (*Service, error) {
|
||||
db, err := sql.Open("postgres", dbUrl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error opening database connection: %w", err)
|
||||
}
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
return nil, fmt.Errorf("error pinging database: %w", err)
|
||||
}
|
||||
|
||||
return &Service{
|
||||
db: db,
|
||||
schemaName: schemaName,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetUserEvents(ctx context.Context, filter Filter, userType string) ([]Event, error) {
|
||||
companyIDs, err := s.GetCompanyIdsByUid(ctx, filter.OwnerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
|
||||
baseQuery := psql.Select(
|
||||
"id", "owner_id", "owner_type", "message", "event_type", "visibility", "company_id",
|
||||
"payload", "is_cancelled", "cancellation_reason", "created_at", "updated_at",
|
||||
).From(fmt.Sprintf("%s.%s", pq.QuoteIdentifier(s.schemaName), constants.FeedEventsTableName))
|
||||
|
||||
conditions := []sq.Sqlizer{
|
||||
sq.Eq{"owner_id": filter.OwnerID},
|
||||
sq.And{
|
||||
sq.Eq{"visibility": VisibilityCompanyWide},
|
||||
sq.Expr("company_id = ANY(?)", pq.Array(companyIDs)),
|
||||
},
|
||||
sq.And{
|
||||
sq.Expr("payload->>'additional_receiver' IS NOT NULL"),
|
||||
sq.Or{
|
||||
sq.Expr("(payload->>'additional_receiver')::uuid = ?::uuid", filter.OwnerID),
|
||||
sq.Expr("(payload->>'additional_receiver')::text = ANY(?)", pq.Array(companyIDs)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if userType == RoleAgent.String() {
|
||||
conditions = append(conditions, sq.Eq{"visibility": VisibilityPublic})
|
||||
}
|
||||
|
||||
query := baseQuery.Where(sq.Or(conditions))
|
||||
|
||||
if len(filter.EventTypes) > 0 {
|
||||
query = query.Where(sq.Expr("event_type = ANY(?)", pq.Array(filter.EventTypes)))
|
||||
}
|
||||
|
||||
if !filter.ShowCancelled {
|
||||
query = query.Where(sq.Eq{"is_cancelled": false})
|
||||
}
|
||||
|
||||
query = query.OrderBy("created_at DESC").
|
||||
Limit(filter.Limit).
|
||||
Offset(filter.Offset)
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build query: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Debug("Executing GetUserEvents query", "query", sqlQuery, "args", args)
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to query events: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var events []Event
|
||||
|
||||
for rows.Next() {
|
||||
event, err := s.scanEventRow(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events = append(events, event)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating event rows: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Debug("GetUserEvents completed successfully",
|
||||
slog.Int("count", len(events)),
|
||||
slog.String("owner_id", filter.OwnerID),
|
||||
slog.String("user_type", userType),
|
||||
slog.String("company_ids", fmt.Sprintf("%v", companyIDs)),
|
||||
slog.Any("filter", filter),
|
||||
)
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (s *Service) GetCompanyIdsByUid(ctx context.Context, ownerID string) ([]string, error) {
|
||||
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
query := psql.Select("company_id").
|
||||
From(fmt.Sprintf("%s.%s", pq.QuoteIdentifier(s.schemaName), constants.ClientTableName)).
|
||||
Where(sq.Eq{"uid": ownerID})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build query: %w", err)
|
||||
}
|
||||
|
||||
var companyID sql.NullString
|
||||
|
||||
if err = s.db.QueryRowContext(ctx, sqlQuery, args...).Scan(&companyID); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to get company Id: %w", err)
|
||||
}
|
||||
|
||||
if companyID.Valid {
|
||||
return []string{companyID.String}, nil
|
||||
}
|
||||
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
func (s *Service) scanEventRow(rows *sql.Rows) (Event, error) {
|
||||
var (
|
||||
event Event
|
||||
payload []byte
|
||||
companyIDScan sql.NullString
|
||||
cancellationReasonScan sql.NullString
|
||||
)
|
||||
|
||||
if err := rows.Scan(
|
||||
&event.Id,
|
||||
&event.OwnerId,
|
||||
&event.OwnerType,
|
||||
&event.Message,
|
||||
&event.EventType,
|
||||
&event.Visibility,
|
||||
&companyIDScan,
|
||||
&payload,
|
||||
&event.IsCancelled,
|
||||
&cancellationReasonScan,
|
||||
&event.CreatedAt,
|
||||
&event.UpdatedAt,
|
||||
); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return Event{}, nil
|
||||
}
|
||||
|
||||
return Event{}, fmt.Errorf("failed to scan event: %w", err)
|
||||
}
|
||||
|
||||
event.processNullableFields(companyIDScan, cancellationReasonScan)
|
||||
event.processPayload(payload)
|
||||
|
||||
return event, nil
|
||||
}
|
||||
|
||||
func (e *Event) processPayload(payload []byte) {
|
||||
if len(payload) == 0 {
|
||||
e.Payload = EventPayload{}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(payload, &e.Payload); err != nil {
|
||||
e.Payload = EventPayload{}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) AddUserEvent(ctx context.Context, event *Event) error {
|
||||
if event == nil {
|
||||
return ErrCreationInvalidData
|
||||
}
|
||||
|
||||
if event.Id == "" {
|
||||
event.Id = "MSG_" + uuid.New().String()
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
event.CreatedAt = now
|
||||
event.UpdatedAt = now
|
||||
|
||||
payloadBytes, err := json.Marshal(event.Payload)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal payload: %w", err)
|
||||
}
|
||||
|
||||
var payloadArg interface{} = payloadBytes
|
||||
if string(payloadBytes) == "null" || string(payloadBytes) == "{}" {
|
||||
payloadArg = nil
|
||||
}
|
||||
|
||||
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
query := psql.Insert(fmt.Sprintf("%s.%s", pq.QuoteIdentifier(s.schemaName), constants.FeedEventsTableName)).
|
||||
Columns(
|
||||
"id", "owner_id", "owner_type", "message", "event_type", "visibility", "company_id",
|
||||
"payload", "is_cancelled", "cancellation_reason", "created_at", "updated_at",
|
||||
).
|
||||
Values(
|
||||
event.Id,
|
||||
event.OwnerId,
|
||||
event.OwnerType,
|
||||
event.Message,
|
||||
event.EventType,
|
||||
event.Visibility,
|
||||
event.CompanyID,
|
||||
payloadArg,
|
||||
event.IsCancelled,
|
||||
event.CancellationReason,
|
||||
event.CreatedAt,
|
||||
event.UpdatedAt,
|
||||
)
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build query: %w", err)
|
||||
}
|
||||
|
||||
_, err = s.db.ExecContext(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to insert event: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Debug("Event added successfully", "event_id", event.Id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) CancelEvents(ctx context.Context, attachmentID, cancellationReason string) error {
|
||||
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
query := psql.Update(fmt.Sprintf("%s.%s", pq.QuoteIdentifier(s.schemaName), constants.FeedEventsTableName)).
|
||||
Set("is_cancelled", true).
|
||||
Set("cancellation_reason", cancellationReason).
|
||||
Set("updated_at", sq.Expr("NOW()")).
|
||||
Where(sq.Expr("payload ->> 'attachment_id' = ?", attachmentID)).
|
||||
Where(sq.Eq{"is_cancelled": false})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to build query: %w", err)
|
||||
}
|
||||
|
||||
result, err := s.db.ExecContext(ctx, sqlQuery, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to execute cancel events query for attachmentID %s: %w", attachmentID, err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get affected rows for attachmentID %s: %w", attachmentID, err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
s.logger.Debug("No active events found or updated for cancellation", "attachment_id", attachmentID)
|
||||
} else {
|
||||
s.logger.Debug("Events cancelled successfully", "attachment_id", attachmentID, "count", rowsAffected)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) GetAgentIdBySubmissionId(ctx context.Context, submissionId string) (string, error) {
|
||||
psql := sq.StatementBuilder.PlaceholderFormat(sq.Dollar)
|
||||
query := psql.Select("uid").
|
||||
From(fmt.Sprintf("%s.%s", pq.QuoteIdentifier(s.schemaName), constants.SubmissionTableName)).
|
||||
Where(sq.Eq{"id": submissionId})
|
||||
|
||||
sqlQuery, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to build query: %w", err)
|
||||
}
|
||||
|
||||
var agentId string
|
||||
|
||||
if err = s.db.QueryRowContext(ctx, sqlQuery, args...).Scan(&agentId); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("failed to get agent Id: %w", err)
|
||||
}
|
||||
|
||||
if agentId == "" {
|
||||
s.logger.Debug("Agent Id not found", "submission_id", submissionId)
|
||||
|
||||
return "", ErrNotFound
|
||||
}
|
||||
|
||||
return agentId, nil
|
||||
}
|
Reference in New Issue
Block a user