Files
mira/backend/internal/calendar/custom_dates.go
Cauê Faleiros 8c7e5fbbe4
All checks were successful
CI / backend (push) Successful in 13m19s
CI / frontend (push) Successful in 11m3s
CI / docker (push) Successful in 1m58s
first commit
2026-06-03 16:31:42 -03:00

249 lines
5.3 KiB
Go

package calendar
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
)
var ErrCustomDateNotFound = errors.New("custom date not found")
type CustomDate struct {
ID string `json:"id"`
ClientID *string `json:"client_id"`
Name string `json:"name"`
Description string `json:"description"`
Date string `json:"date"`
RecursAnnually bool `json:"recurs_annually"`
Visibility string `json:"visibility"`
Category string `json:"category"`
CreatedBy *string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (s Store) ListCustomDatesByYear(ctx context.Context, year int, clientID string, viewerUserID string) ([]CustomDate, error) {
rows, err := s.db.Query(ctx, `
SELECT
id::text,
client_id::text,
name,
description,
CASE
WHEN recurs_annually THEN make_date(
$1,
EXTRACT(MONTH FROM date)::int,
LEAST(
EXTRACT(DAY FROM date)::int,
EXTRACT(DAY FROM (date_trunc('month', make_date($1, EXTRACT(MONTH FROM date)::int, 1)) + interval '1 month - 1 day'))::int
)
)::text
ELSE date::text
END,
recurs_annually,
visibility,
category,
created_by::text,
created_at,
updated_at
FROM commemorative_dates
WHERE (
(date >= make_date($1, 1, 1) AND date < make_date($1 + 1, 1, 1))
OR (recurs_annually AND date < make_date($1 + 1, 1, 1))
)
AND ($2 = '' OR client_id IS NULL OR client_id::text = $2)
AND (
$3 = ''
OR (
visibility = 'client'
AND (
client_id IS NULL
OR EXISTS (
SELECT 1
FROM client_users
WHERE client_users.client_id = commemorative_dates.client_id
AND client_users.user_id = $3::uuid
)
)
)
)
ORDER BY date ASC, name ASC
`, year, clientID, viewerUserID)
if err != nil {
return nil, err
}
defer rows.Close()
dates := []CustomDate{}
for rows.Next() {
date, err := scanCustomDate(rows)
if err != nil {
return nil, err
}
dates = append(dates, date)
}
return dates, rows.Err()
}
func (s Store) CreateCustomDate(ctx context.Context, input CreateCustomDateInput) (CustomDate, error) {
row := s.db.QueryRow(ctx, `
INSERT INTO commemorative_dates (
client_id,
name,
description,
date,
recurs_annually,
visibility,
category,
created_by
)
VALUES (NULLIF($1, '')::uuid, $2, $3, $4, $5, $6, $7, NULLIF($8, '')::uuid)
RETURNING
id::text,
client_id::text,
name,
description,
date::text,
recurs_annually,
visibility,
category,
created_by::text,
created_at,
updated_at
`,
input.ClientID,
input.Name,
input.Description,
input.Date,
input.RecursAnnually,
input.Visibility,
input.Category,
input.CreatedBy,
)
return scanCustomDate(row)
}
type UpdateCustomDateInput struct {
ID string
ClientID string
Name string
Description string
Date string
RecursAnnually bool
Visibility string
}
func (s Store) UpdateCustomDate(ctx context.Context, input UpdateCustomDateInput) (CustomDate, error) {
row := s.db.QueryRow(ctx, `
UPDATE commemorative_dates
SET
client_id = NULLIF($2, '')::uuid,
name = $3,
description = $4,
date = $5::date,
recurs_annually = $6,
visibility = $7,
updated_at = now()
WHERE id = $1::uuid
AND category != 'feriados-brasil'
RETURNING
id::text,
client_id::text,
name,
description,
date::text,
recurs_annually,
visibility,
category,
created_by::text,
created_at,
updated_at
`, input.ID, input.ClientID, input.Name, input.Description, input.Date, input.RecursAnnually, input.Visibility)
return scanCustomDate(row)
}
func (s Store) DeleteCustomDate(ctx context.Context, id string) error {
command, err := s.db.Exec(ctx, `
DELETE FROM commemorative_dates
WHERE id = $1::uuid
AND category != 'feriados-brasil'
`, id)
if err != nil {
return err
}
if command.RowsAffected() == 0 {
return ErrCustomDateNotFound
}
return nil
}
func (s Store) UpsertExternalCommemorativeDates(ctx context.Context, dates []ExternalCommemorativeDate) error {
for _, date := range dates {
if date.Name == "" || date.Date == "" {
continue
}
if _, err := s.db.Exec(ctx, `
INSERT INTO commemorative_dates (
client_id,
name,
description,
date,
recurs_annually,
visibility,
category
)
VALUES (NULL, $1, $2, $3::date, false, 'client', 'feriados-brasil')
ON CONFLICT DO NOTHING
`, date.Name, date.Description, date.Date); err != nil {
return err
}
}
return nil
}
type CreateCustomDateInput struct {
ClientID string
Name string
Description string
Date string
RecursAnnually bool
Visibility string
Category string
CreatedBy string
}
type customDateScanner interface {
Scan(dest ...any) error
}
func scanCustomDate(row customDateScanner) (CustomDate, error) {
var date CustomDate
err := row.Scan(
&date.ID,
&date.ClientID,
&date.Name,
&date.Description,
&date.Date,
&date.RecursAnnually,
&date.Visibility,
&date.Category,
&date.CreatedBy,
&date.CreatedAt,
&date.UpdatedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return CustomDate{}, ErrCustomDateNotFound
}
if err != nil {
return CustomDate{}, err
}
return date, nil
}