ultisuite-backend/internal/api/mail/signatures.go
2026-05-24 00:03:36 +02:00

162 lines
4.3 KiB
Go

package mail
import (
"context"
"errors"
"github.com/jackc/pgx/v5"
"github.com/ultisuite/ulti-backend/internal/api/query"
"github.com/ultisuite/ulti-backend/internal/securityaudit"
)
type SignaturesList struct {
Signatures []map[string]any `json:"signatures"`
Pagination query.PaginationMeta `json:"pagination,omitempty"`
}
func scanSignature(id, name, html string, sortOrder int, createdAt, updatedAt any) map[string]any {
return map[string]any{
"id": id,
"name": name,
"html": html,
"sort_order": sortOrder,
"created_at": createdAt,
"updated_at": updatedAt,
}
}
func (s *Service) verifySignatureOwnership(ctx context.Context, externalID, signatureID string) error {
var exists bool
err := s.db.QueryRow(ctx, `
SELECT EXISTS(
SELECT 1 FROM mail_signatures ms
JOIN users u ON ms.user_id = u.id
WHERE ms.id = $1 AND u.external_id = $2
)
`, signatureID, externalID).Scan(&exists)
if err != nil {
return err
}
if !exists {
return ErrNotFound
}
return nil
}
func (s *Service) ListSignatures(ctx context.Context, externalID string, params query.ListParams) (SignaturesList, error) {
var total int64
if err := s.db.QueryRow(ctx, `
SELECT COUNT(*) FROM mail_signatures ms
JOIN users u ON ms.user_id = u.id
WHERE u.external_id = $1
`, externalID).Scan(&total); err != nil {
return SignaturesList{}, err
}
rows, err := s.db.Query(ctx, `
SELECT ms.id, ms.name, ms.html, ms.sort_order, ms.created_at, ms.updated_at
FROM mail_signatures ms
JOIN users u ON ms.user_id = u.id
WHERE u.external_id = $1
ORDER BY ms.sort_order ASC, ms.created_at ASC
LIMIT $2 OFFSET $3
`, externalID, params.Limit(), params.Offset())
if err != nil {
return SignaturesList{}, err
}
defer rows.Close()
signatures := make([]map[string]any, 0)
for rows.Next() {
var id, name, html string
var sortOrder int
var createdAt, updatedAt any
if err := rows.Scan(&id, &name, &html, &sortOrder, &createdAt, &updatedAt); err != nil {
return SignaturesList{}, err
}
signatures = append(signatures, scanSignature(id, name, html, sortOrder, createdAt, updatedAt))
}
if err := rows.Err(); err != nil {
return SignaturesList{}, err
}
return SignaturesList{
Signatures: signatures,
Pagination: params.Meta(&total),
}, nil
}
func (s *Service) GetSignature(ctx context.Context, externalID, signatureID string) (map[string]any, error) {
var id, name, html string
var sortOrder int
var createdAt, updatedAt any
err := s.db.QueryRow(ctx, `
SELECT ms.id, ms.name, ms.html, ms.sort_order, ms.created_at, ms.updated_at
FROM mail_signatures ms
JOIN users u ON ms.user_id = u.id
WHERE ms.id = $1 AND u.external_id = $2
`, signatureID, externalID).Scan(&id, &name, &html, &sortOrder, &createdAt, &updatedAt)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrNotFound
}
return nil, err
}
return scanSignature(id, name, html, sortOrder, createdAt, updatedAt), nil
}
func (s *Service) CreateSignature(ctx context.Context, externalID string, req *createSignatureRequest) (string, error) {
userID, err := s.ResolveUserID(ctx, externalID)
if err != nil {
return "", err
}
var id string
err = s.db.QueryRow(ctx, `
INSERT INTO mail_signatures (user_id, name, html)
VALUES ($1, $2, $3)
RETURNING id
`, userID, req.Name, req.HTML).Scan(&id)
if err != nil {
return "", err
}
return id, nil
}
func (s *Service) UpdateSignature(ctx context.Context, externalID, signatureID string, req *updateSignatureRequest) error {
result, err := s.db.Exec(ctx, `
UPDATE mail_signatures ms SET
name = $1, html = $2, updated_at = NOW()
FROM users u
WHERE ms.id = $3 AND ms.user_id = u.id AND u.external_id = $4
`, req.Name, req.HTML, signatureID, externalID)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
func (s *Service) DeleteSignature(ctx context.Context, externalID, signatureID string) error {
result, err := s.db.Exec(ctx, `
DELETE FROM mail_signatures ms
USING users u
WHERE ms.id = $1 AND ms.user_id = u.id AND u.external_id = $2
`, signatureID, externalID)
if err != nil {
return err
}
if result.RowsAffected() == 0 {
return ErrNotFound
}
if s.audit != nil {
s.audit.Log(ctx, externalID, securityaudit.ActionCriticalDeletion, map[string]any{
"target": "mail_signature", "signature_id": signatureID,
})
}
return nil
}