179 lines
5.2 KiB
Go
179 lines
5.2 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"inbox/internal/domain/topic"
|
|
)
|
|
|
|
func (s *Store) CreateTopic(ctx context.Context, value topic.Record) (topic.Record, error) {
|
|
if err := value.Validate(); err != nil {
|
|
return topic.Record{}, err
|
|
}
|
|
if value.ID == "" {
|
|
id, err := s.newID("topic")
|
|
if err != nil {
|
|
return topic.Record{}, err
|
|
}
|
|
value.ID = id
|
|
}
|
|
now := s.now()
|
|
value.CreatedAt = coalesceString(value.CreatedAt, now)
|
|
value.UpdatedAt = now
|
|
|
|
if _, err := s.db.ExecContext(ctx, `
|
|
INSERT INTO topics(id, workspace_id, slug, title, space, status, summary, created_at, updated_at, closed_at)
|
|
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
`, value.ID, value.WorkspaceID, value.Slug, value.Title, string(value.Space), value.Status, value.Summary, value.CreatedAt, value.UpdatedAt, nullableString(value.ClosedAt)); err != nil {
|
|
return topic.Record{}, fmt.Errorf("create topic: %w", err)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func (s *Store) ListTopics(ctx context.Context, workspaceID string) ([]topic.Record, error) {
|
|
var (
|
|
rows *sql.Rows
|
|
err error
|
|
)
|
|
if strings.TrimSpace(workspaceID) == "" {
|
|
rows, err = s.db.QueryContext(ctx, `
|
|
SELECT id, workspace_id, slug, title, space, status, summary, created_at, updated_at, closed_at
|
|
FROM topics
|
|
ORDER BY updated_at DESC, created_at DESC, slug
|
|
`)
|
|
} else {
|
|
rows, err = s.db.QueryContext(ctx, `
|
|
SELECT id, workspace_id, slug, title, space, status, summary, created_at, updated_at, closed_at
|
|
FROM topics
|
|
WHERE workspace_id = ?
|
|
ORDER BY updated_at DESC, created_at DESC, slug
|
|
`, workspaceID)
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list topics: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []topic.Record
|
|
for rows.Next() {
|
|
item, err := scanTopic(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate topics: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Store) ListTopicsBySpace(ctx context.Context, workspaceID string, space topic.Space) ([]topic.Record, error) {
|
|
rows, err := s.db.QueryContext(ctx, `
|
|
SELECT id, workspace_id, slug, title, space, status, summary, created_at, updated_at, closed_at
|
|
FROM topics
|
|
WHERE workspace_id = ? AND space = ?
|
|
ORDER BY updated_at DESC, created_at DESC, slug
|
|
`, workspaceID, string(space))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("list topics by space: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var out []topic.Record
|
|
for rows.Next() {
|
|
item, err := scanTopic(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, item)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("iterate topics by space: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *Store) GetTopic(ctx context.Context, topicID string) (topic.Record, error) {
|
|
row := s.db.QueryRowContext(ctx, `
|
|
SELECT id, workspace_id, slug, title, space, status, summary, created_at, updated_at, closed_at
|
|
FROM topics
|
|
WHERE id = ?
|
|
`, topicID)
|
|
return scanTopic(row)
|
|
}
|
|
|
|
func (s *Store) GetTopicBySlugOrTitle(ctx context.Context, workspaceID, value string, spaces ...topic.Space) (topic.Record, error) {
|
|
args := []any{workspaceID, value, value}
|
|
query := `
|
|
SELECT id, workspace_id, slug, title, space, status, summary, created_at, updated_at, closed_at
|
|
FROM topics
|
|
WHERE workspace_id = ? AND (slug = ? OR title = ?)
|
|
`
|
|
if len(spaces) > 0 {
|
|
query += " AND space IN (" + placeholders(len(spaces)) + ")"
|
|
for _, space := range spaces {
|
|
args = append(args, string(space))
|
|
}
|
|
}
|
|
query += `
|
|
ORDER BY CASE WHEN slug = ? THEN 0 ELSE 1 END, updated_at DESC, created_at DESC
|
|
LIMIT 1
|
|
`
|
|
args = append(args, value)
|
|
row := s.db.QueryRowContext(ctx, query, args...)
|
|
return scanTopic(row)
|
|
}
|
|
|
|
func (s *Store) UpdateTopic(ctx context.Context, value topic.Record) (topic.Record, error) {
|
|
if value.ID == "" {
|
|
return topic.Record{}, fmt.Errorf("topic id is required")
|
|
}
|
|
current, err := s.GetTopic(ctx, value.ID)
|
|
if err != nil {
|
|
return topic.Record{}, err
|
|
}
|
|
value.CreatedAt = current.CreatedAt
|
|
value.UpdatedAt = s.now()
|
|
if err := value.Validate(); err != nil {
|
|
return topic.Record{}, err
|
|
}
|
|
result, err := s.db.ExecContext(ctx, `
|
|
UPDATE topics
|
|
SET workspace_id = ?, slug = ?, title = ?, space = ?, status = ?, summary = ?, updated_at = ?, closed_at = ?
|
|
WHERE id = ?
|
|
`, value.WorkspaceID, value.Slug, value.Title, string(value.Space), value.Status, value.Summary, value.UpdatedAt, nullableString(value.ClosedAt), value.ID)
|
|
if err != nil {
|
|
return topic.Record{}, fmt.Errorf("update topic: %w", err)
|
|
}
|
|
if err := ensureAffected(result, sql.ErrNoRows); err != nil {
|
|
return topic.Record{}, err
|
|
}
|
|
return s.GetTopic(ctx, value.ID)
|
|
}
|
|
|
|
func (s *Store) DeleteTopic(ctx context.Context, topicID string) error {
|
|
result, err := s.db.ExecContext(ctx, `DELETE FROM topics WHERE id = ?`, topicID)
|
|
if err != nil {
|
|
return fmt.Errorf("delete topic: %w", err)
|
|
}
|
|
return ensureAffected(result, sql.ErrNoRows)
|
|
}
|
|
|
|
func scanTopic(s scanner) (topic.Record, error) {
|
|
var item topic.Record
|
|
var space string
|
|
var closedAt sql.NullString
|
|
if err := s.Scan(&item.ID, &item.WorkspaceID, &item.Slug, &item.Title, &space, &item.Status, &item.Summary, &item.CreatedAt, &item.UpdatedAt, &closedAt); err != nil {
|
|
return topic.Record{}, err
|
|
}
|
|
item.Space = topic.Space(space)
|
|
if closedAt.Valid {
|
|
item.ClosedAt = closedAt.String
|
|
}
|
|
return item, nil
|
|
}
|