Files
mira/backend/internal/calendar/items.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

330 lines
8.5 KiB
Go

package calendar
import (
"context"
"errors"
"time"
"github.com/jackc/pgx/v5"
)
var ErrCalendarItemNotFound = errors.New("calendar item not found")
type CalendarItem struct {
ID string `json:"id"`
ClientID string `json:"client_id"`
ClientName string `json:"client_name"`
Title string `json:"title"`
Description string `json:"description"`
ContentType string `json:"content_type"`
Status string `json:"status"`
OwnerID *string `json:"owner_id"`
ScheduledDate string `json:"scheduled_date"`
ScheduledAt *time.Time `json:"scheduled_at"`
CopyText string `json:"copy_text"`
InternalNotes string `json:"internal_notes"`
ClientNotes string `json:"client_notes"`
CreatedBy *string `json:"created_by"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ListCalendarItemsFilter struct {
ClientID string
ViewerUserID string
From string
To string
}
func (s Store) ListCalendarItems(ctx context.Context, filter ListCalendarItemsFilter) ([]CalendarItem, error) {
rows, err := s.db.Query(ctx, `
SELECT
calendar_items.id::text,
calendar_items.client_id::text,
clients.name,
calendar_items.title,
calendar_items.description,
calendar_items.content_type,
calendar_items.status,
calendar_items.owner_id::text,
calendar_items.scheduled_date::text,
calendar_items.scheduled_at,
calendar_items.copy_text,
calendar_items.internal_notes,
calendar_items.client_notes,
calendar_items.created_by::text,
calendar_items.created_at,
calendar_items.updated_at
FROM calendar_items
INNER JOIN clients ON clients.id = calendar_items.client_id
WHERE calendar_items.scheduled_date >= $1::date
AND calendar_items.scheduled_date <= $2::date
AND ($3 = '' OR calendar_items.client_id::text = $3)
AND (
$4 = ''
OR EXISTS (
SELECT 1
FROM client_users
WHERE client_users.client_id = calendar_items.client_id
AND client_users.user_id = $4::uuid
)
)
ORDER BY calendar_items.scheduled_date ASC, calendar_items.created_at ASC
`, filter.From, filter.To, filter.ClientID, filter.ViewerUserID)
if err != nil {
return nil, err
}
defer rows.Close()
items := []CalendarItem{}
for rows.Next() {
item, err := scanCalendarItem(rows)
if err != nil {
return nil, err
}
items = append(items, item)
}
return items, rows.Err()
}
func (s Store) FindCalendarItem(ctx context.Context, id string, viewerUserID string) (CalendarItem, error) {
row := s.db.QueryRow(ctx, `
SELECT
calendar_items.id::text,
calendar_items.client_id::text,
clients.name,
calendar_items.title,
calendar_items.description,
calendar_items.content_type,
calendar_items.status,
calendar_items.owner_id::text,
calendar_items.scheduled_date::text,
calendar_items.scheduled_at,
calendar_items.copy_text,
calendar_items.internal_notes,
calendar_items.client_notes,
calendar_items.created_by::text,
calendar_items.created_at,
calendar_items.updated_at
FROM calendar_items
INNER JOIN clients ON clients.id = calendar_items.client_id
WHERE calendar_items.id = $1::uuid
AND (
$2 = ''
OR EXISTS (
SELECT 1
FROM client_users
WHERE client_users.client_id = calendar_items.client_id
AND client_users.user_id = $2::uuid
)
)
`, id, viewerUserID)
return scanCalendarItem(row)
}
type CreateCalendarItemInput struct {
ClientID string
Title string
Description string
ContentType string
Status string
ScheduledDate string
CopyText string
InternalNotes string
ClientNotes string
CreatedBy string
}
func (s Store) CreateCalendarItem(ctx context.Context, input CreateCalendarItemInput) (CalendarItem, error) {
row := s.db.QueryRow(ctx, `
INSERT INTO calendar_items (
client_id,
title,
description,
content_type,
status,
scheduled_date,
copy_text,
internal_notes,
client_notes,
created_by
)
VALUES ($1::uuid, $2, $3, $4, $5, $6::date, $7, $8, $9, NULLIF($10, '')::uuid)
RETURNING
calendar_items.id::text,
calendar_items.client_id::text,
(SELECT name FROM clients WHERE clients.id = calendar_items.client_id),
calendar_items.title,
calendar_items.description,
calendar_items.content_type,
calendar_items.status,
calendar_items.owner_id::text,
calendar_items.scheduled_date::text,
calendar_items.scheduled_at,
calendar_items.copy_text,
calendar_items.internal_notes,
calendar_items.client_notes,
calendar_items.created_by::text,
calendar_items.created_at,
calendar_items.updated_at
`,
input.ClientID,
input.Title,
input.Description,
input.ContentType,
input.Status,
input.ScheduledDate,
input.CopyText,
input.InternalNotes,
input.ClientNotes,
input.CreatedBy,
)
return scanCalendarItem(row)
}
type UpdateCalendarItemInput struct {
ID string
Title string
UpdateTitle bool
Description string
UpdateDescription bool
Status string
UpdateStatus bool
ScheduledDate string
UpdateScheduledDate bool
CopyText string
UpdateCopyText bool
InternalNotes string
UpdateInternalNotes bool
ClientNotes string
UpdateClientNotes bool
}
func (s Store) UpdateCalendarItem(ctx context.Context, input UpdateCalendarItemInput) (CalendarItem, error) {
row := s.db.QueryRow(ctx, `
UPDATE calendar_items
SET
title = CASE WHEN $2 THEN $3 ELSE title END,
description = CASE WHEN $4 THEN $5 ELSE description END,
status = CASE WHEN $6 THEN $7 ELSE status END,
scheduled_date = CASE WHEN $8 THEN NULLIF($9, '')::date ELSE scheduled_date END,
copy_text = CASE WHEN $10 THEN $11 ELSE copy_text END,
internal_notes = CASE WHEN $12 THEN $13 ELSE internal_notes END,
client_notes = CASE WHEN $14 THEN $15 ELSE client_notes END,
updated_at = now()
WHERE id = $1::uuid
RETURNING
calendar_items.id::text,
calendar_items.client_id::text,
(SELECT name FROM clients WHERE clients.id = calendar_items.client_id),
calendar_items.title,
calendar_items.description,
calendar_items.content_type,
calendar_items.status,
calendar_items.owner_id::text,
calendar_items.scheduled_date::text,
calendar_items.scheduled_at,
calendar_items.copy_text,
calendar_items.internal_notes,
calendar_items.client_notes,
calendar_items.created_by::text,
calendar_items.created_at,
calendar_items.updated_at
`,
input.ID,
input.UpdateTitle,
input.Title,
input.UpdateDescription,
input.Description,
input.UpdateStatus,
input.Status,
input.UpdateScheduledDate,
input.ScheduledDate,
input.UpdateCopyText,
input.CopyText,
input.UpdateInternalNotes,
input.InternalNotes,
input.UpdateClientNotes,
input.ClientNotes,
)
return scanCalendarItem(row)
}
func (s Store) CancelCalendarItem(ctx context.Context, id string) (CalendarItem, error) {
row := s.db.QueryRow(ctx, `
UPDATE calendar_items
SET status = 'cancelado', updated_at = now()
WHERE id = $1::uuid
RETURNING
calendar_items.id::text,
calendar_items.client_id::text,
(SELECT name FROM clients WHERE clients.id = calendar_items.client_id),
calendar_items.title,
calendar_items.description,
calendar_items.content_type,
calendar_items.status,
calendar_items.owner_id::text,
calendar_items.scheduled_date::text,
calendar_items.scheduled_at,
calendar_items.copy_text,
calendar_items.internal_notes,
calendar_items.client_notes,
calendar_items.created_by::text,
calendar_items.created_at,
calendar_items.updated_at
`, id)
return scanCalendarItem(row)
}
func (s Store) DeleteCalendarItem(ctx context.Context, id string) error {
command, err := s.db.Exec(ctx, `
DELETE FROM calendar_items
WHERE id = $1::uuid
`, id)
if err != nil {
return err
}
if command.RowsAffected() == 0 {
return ErrCalendarItemNotFound
}
return nil
}
type calendarItemScanner interface {
Scan(dest ...any) error
}
func scanCalendarItem(row calendarItemScanner) (CalendarItem, error) {
var item CalendarItem
err := row.Scan(
&item.ID,
&item.ClientID,
&item.ClientName,
&item.Title,
&item.Description,
&item.ContentType,
&item.Status,
&item.OwnerID,
&item.ScheduledDate,
&item.ScheduledAt,
&item.CopyText,
&item.InternalNotes,
&item.ClientNotes,
&item.CreatedBy,
&item.CreatedAt,
&item.UpdatedAt,
)
if errors.Is(err, pgx.ErrNoRows) {
return CalendarItem{}, ErrCalendarItemNotFound
}
if err != nil {
return CalendarItem{}, err
}
return item, nil
}