This commit is contained in:
283
internal/infrastructure/database/postgres/counter_repository.go
Normal file
283
internal/infrastructure/database/postgres/counter_repository.go
Normal file
@@ -0,0 +1,283 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"counter/internal/domain/entities"
|
||||
"counter/internal/domain/repositories"
|
||||
)
|
||||
|
||||
// CounterRepository implements the CounterRepository interface for PostgreSQL
|
||||
type CounterRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewCounterRepository creates a new counter repository
|
||||
func NewCounterRepository(db *sql.DB) repositories.CounterRepository {
|
||||
return &CounterRepository{db: db}
|
||||
}
|
||||
|
||||
// Create creates a new counter
|
||||
func (r *CounterRepository) Create(ctx context.Context, counter *entities.Counter) error {
|
||||
query := `
|
||||
INSERT INTO counters (user_id, name, description)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, created_at, updated_at
|
||||
`
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, counter.UserID, counter.Name, counter.Description).
|
||||
Scan(&counter.ID, &counter.CreatedAt, &counter.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindByID finds a counter by ID with stats
|
||||
func (r *CounterRepository) FindByID(ctx context.Context, id, userID int) (*entities.CounterWithStats, error) {
|
||||
query := `
|
||||
SELECT c.id, c.user_id, c.name, c.description, c.created_at, c.updated_at,
|
||||
COALESCE(SUM(ce.value), 0) as total_value,
|
||||
COALESCE(SUM(CASE WHEN ce.date = CURRENT_DATE THEN ce.value ELSE 0 END), 0) as today_value,
|
||||
COALESCE(SUM(CASE WHEN ce.date >= CURRENT_DATE - INTERVAL '7 days' THEN ce.value ELSE 0 END), 0) as week_value,
|
||||
COALESCE(SUM(CASE WHEN ce.date >= DATE_TRUNC('month', CURRENT_DATE) THEN ce.value ELSE 0 END), 0) as month_value,
|
||||
COUNT(ce.id) as entry_count
|
||||
FROM counters c
|
||||
LEFT JOIN counter_entries ce ON c.id = ce.counter_id
|
||||
WHERE c.id = $1 AND c.user_id = $2
|
||||
GROUP BY c.id, c.user_id, c.name, c.description, c.created_at, c.updated_at
|
||||
`
|
||||
|
||||
counter := &entities.CounterWithStats{}
|
||||
err := r.db.QueryRowContext(ctx, query, id, userID).
|
||||
Scan(&counter.ID, &counter.UserID, &counter.Name, &counter.Description,
|
||||
&counter.CreatedAt, &counter.UpdatedAt, &counter.TotalValue,
|
||||
&counter.TodayValue, &counter.WeekValue, &counter.MonthValue, &counter.EntryCount)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, entities.ErrCounterNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return counter, nil
|
||||
}
|
||||
|
||||
// FindByUserID finds all counters for a user with stats
|
||||
func (r *CounterRepository) FindByUserID(ctx context.Context, userID int, search string) ([]*entities.CounterWithStats, error) {
|
||||
query := `
|
||||
SELECT c.id, c.user_id, c.name, c.description, c.created_at, c.updated_at,
|
||||
COALESCE(SUM(ce.value), 0) as total_value,
|
||||
COALESCE(SUM(CASE WHEN ce.date = CURRENT_DATE THEN ce.value ELSE 0 END), 0) as today_value,
|
||||
COALESCE(SUM(CASE WHEN ce.date >= CURRENT_DATE - INTERVAL '7 days' THEN ce.value ELSE 0 END), 0) as week_value,
|
||||
COALESCE(SUM(CASE WHEN ce.date >= DATE_TRUNC('month', CURRENT_DATE) THEN ce.value ELSE 0 END), 0) as month_value,
|
||||
COUNT(ce.id) as entry_count
|
||||
FROM counters c
|
||||
LEFT JOIN counter_entries ce ON c.id = ce.counter_id
|
||||
WHERE c.user_id = $1
|
||||
`
|
||||
|
||||
args := []interface{}{userID}
|
||||
if search != "" {
|
||||
query += " AND (c.name ILIKE $2 OR c.description ILIKE $2)"
|
||||
args = append(args, "%"+search+"%")
|
||||
}
|
||||
|
||||
query += " GROUP BY c.id, c.user_id, c.name, c.description, c.created_at, c.updated_at ORDER BY c.updated_at DESC"
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var counters []*entities.CounterWithStats
|
||||
for rows.Next() {
|
||||
counter := &entities.CounterWithStats{}
|
||||
err := rows.Scan(&counter.ID, &counter.UserID, &counter.Name, &counter.Description,
|
||||
&counter.CreatedAt, &counter.UpdatedAt, &counter.TotalValue,
|
||||
&counter.TodayValue, &counter.WeekValue, &counter.MonthValue, &counter.EntryCount)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
counters = append(counters, counter)
|
||||
}
|
||||
|
||||
return counters, nil
|
||||
}
|
||||
|
||||
// Update updates a counter
|
||||
func (r *CounterRepository) Update(ctx context.Context, counter *entities.Counter) error {
|
||||
query := `
|
||||
UPDATE counters
|
||||
SET name = $1, description = $2, updated_at = CURRENT_TIMESTAMP
|
||||
WHERE id = $3 AND user_id = $4
|
||||
RETURNING updated_at
|
||||
`
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, counter.Name, counter.Description, counter.ID, counter.UserID).
|
||||
Scan(&counter.UpdatedAt)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return entities.ErrCounterNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes a counter
|
||||
func (r *CounterRepository) Delete(ctx context.Context, id, userID int) error {
|
||||
query := `DELETE FROM counters WHERE id = $1 AND user_id = $2`
|
||||
|
||||
result, err := r.db.ExecContext(ctx, query, id, userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return entities.ErrCounterNotFound
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddEntry adds a new counter entry
|
||||
func (r *CounterRepository) AddEntry(ctx context.Context, entry *entities.CounterEntry) error {
|
||||
query := `
|
||||
INSERT INTO counter_entries (counter_id, value, date)
|
||||
VALUES ($1, $2, $3)
|
||||
RETURNING id, created_at
|
||||
`
|
||||
|
||||
err := r.db.QueryRowContext(ctx, query, entry.CounterID, entry.Value, entry.Date).
|
||||
Scan(&entry.ID, &entry.CreatedAt)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update counter's updated_at timestamp
|
||||
_, err = r.db.ExecContext(ctx, "UPDATE counters SET updated_at = CURRENT_TIMESTAMP WHERE id = $1", entry.CounterID)
|
||||
if err != nil {
|
||||
// Log error but don't fail the request
|
||||
// This could be improved with proper logging
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEntries retrieves entries for a specific counter
|
||||
func (r *CounterRepository) GetEntries(ctx context.Context, counterID, userID int, startDate, endDate *time.Time) ([]*entities.CounterEntry, error) {
|
||||
// First verify counter belongs to user
|
||||
exists, err := r.Exists(ctx, counterID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, entities.ErrCounterNotFound
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT id, counter_id, value, date, created_at
|
||||
FROM counter_entries
|
||||
WHERE counter_id = $1
|
||||
`
|
||||
args := []interface{}{counterID}
|
||||
|
||||
if startDate != nil {
|
||||
query += fmt.Sprintf(" AND date >= $%d", len(args)+1)
|
||||
args = append(args, *startDate)
|
||||
if endDate != nil {
|
||||
query += fmt.Sprintf(" AND date <= $%d", len(args)+1)
|
||||
args = append(args, *endDate)
|
||||
}
|
||||
} else if endDate != nil {
|
||||
query += fmt.Sprintf(" AND date <= $%d", len(args)+1)
|
||||
args = append(args, *endDate)
|
||||
}
|
||||
|
||||
query += " ORDER BY date DESC, created_at DESC"
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []*entities.CounterEntry
|
||||
for rows.Next() {
|
||||
entry := &entities.CounterEntry{}
|
||||
err := rows.Scan(&entry.ID, &entry.CounterID, &entry.Value, &entry.Date, &entry.CreatedAt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// GetDailyStats retrieves daily statistics for a counter
|
||||
func (r *CounterRepository) GetDailyStats(ctx context.Context, counterID, userID int, days int) ([]*entities.DailyStat, error) {
|
||||
// First verify counter belongs to user
|
||||
exists, err := r.Exists(ctx, counterID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, entities.ErrCounterNotFound
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT date, SUM(value) as daily_total
|
||||
FROM counter_entries
|
||||
WHERE counter_id = $1 AND date >= CURRENT_DATE - INTERVAL '%d days'
|
||||
GROUP BY date
|
||||
ORDER BY date DESC
|
||||
`
|
||||
|
||||
rows, err := r.db.QueryContext(ctx, fmt.Sprintf(query, days), counterID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var stats []*entities.DailyStat
|
||||
for rows.Next() {
|
||||
stat := &entities.DailyStat{}
|
||||
err := rows.Scan(&stat.Date, &stat.Total)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats = append(stats, stat)
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// Exists checks if a counter exists and belongs to the user
|
||||
func (r *CounterRepository) Exists(ctx context.Context, id, userID int) (bool, error) {
|
||||
query := `SELECT EXISTS(SELECT 1 FROM counters WHERE id = $1 AND user_id = $2)`
|
||||
|
||||
var exists bool
|
||||
err := r.db.QueryRowContext(ctx, query, id, userID).Scan(&exists)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return exists, nil
|
||||
}
|
||||
Reference in New Issue
Block a user