forked from TechBowl-japan/go-stations
-
Notifications
You must be signed in to change notification settings - Fork 0
/
todo.go
158 lines (135 loc) · 3.7 KB
/
todo.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package service
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/TechBowl-japan/go-stations/model"
)
// A TODOService implements CRUD of TODO entities.
type TODOService struct {
db *sql.DB
}
// NewTODOService returns new TODOService.
func NewTODOService(db *sql.DB) *TODOService {
return &TODOService{
db: db,
}
}
// CreateTODO creates a TODO on DB.
func (s *TODOService) CreateTODO(ctx context.Context, subject, description string) (*model.TODO, error) {
const (
insert = `INSERT INTO todos(subject, description) VALUES(?, ?)`
confirm = `SELECT subject, description, created_at, updated_at FROM todos WHERE id = ?`
)
var todo model.TODO
res, err := s.db.ExecContext(ctx, insert, subject, description)
if err != nil {
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
return nil, err
}
todo.ID = id
err = s.db.QueryRowContext(ctx, confirm, id).Scan(&todo.Subject, &todo.Description, &todo.CreatedAt, &todo.UpdatedAt)
if err != nil {
return nil, err
}
return &todo, err
}
// ReadTODO reads TODOs on DB.
func (s *TODOService) ReadTODO(ctx context.Context, prevID, size int64) ([]*model.TODO, error) {
const (
read = `SELECT id, subject, description, created_at, updated_at FROM todos ORDER BY id DESC LIMIT ?`
readWithID = `SELECT id, subject, description, created_at, updated_at FROM todos WHERE id < ? ORDER BY id DESC LIMIT ?`
)
var rows *sql.Rows
var err error
// Query the database for TODOs
if prevID == 0 && size == 0 {
rows, err = s.db.QueryContext(ctx, read, 3)
} else if prevID > 0 {
rows, err = s.db.QueryContext(ctx, readWithID, prevID, size)
} else {
rows, err = s.db.QueryContext(ctx, read, size)
}
if err != nil {
return nil, err
}
defer rows.Close()
// Scan the rows to TODO slice
todos := make([]*model.TODO, 0)
for rows.Next() {
t := &model.TODO{}
if err := rows.Scan(&t.ID, &t.Subject, &t.Description, &t.CreatedAt, &t.UpdatedAt); err != nil {
return nil, err
}
todos = append(todos, t)
}
if err := rows.Err(); err != nil {
return nil, err
}
return todos, nil
}
// UpdateTODO updates the TODO on DB.
func (s *TODOService) UpdateTODO(ctx context.Context, id int64, subject, description string) (*model.TODO, error) {
const (
update = `UPDATE todos SET subject = ?, description = ? WHERE id = ?`
confirm = `SELECT subject, description, created_at, updated_at FROM todos WHERE id = ?`
)
res, err := s.db.ExecContext(ctx, update, subject, description, id)
if err != nil {
return nil, err
}
affected, err := res.RowsAffected()
if err != nil {
return nil, err
}
if affected != 1 {
return nil, &model.ErrNotFound{
When: time.Now(),
What: "There is no updated TODO",
}
}
var todo model.TODO
todo.ID = id
err = s.db.QueryRowContext(ctx, confirm, id).Scan(&todo.Subject, &todo.Description, &todo.CreatedAt, &todo.UpdatedAt)
if err != nil {
return nil, err
}
return &todo, err
}
// DeleteTODO deletes TODOs on DB by ids.
func (s *TODOService) DeleteTODO(ctx context.Context, ids []int64) error {
const deleteFmt = `DELETE FROM todos WHERE id IN (?%s)`
if len(ids) == 0 {
return nil
}
// prepare SQL query with placeholders for ids
query := fmt.Sprintf(deleteFmt, strings.Repeat(",?", len(ids)-1))
query = strings.TrimLeft(query, ",")
// prepare arguments for query
args := make([]interface{}, len(ids))
for i, id := range ids {
args[i] = id
}
// execute query
result, err := s.db.ExecContext(ctx, query, args...)
if err != nil {
return err
}
// check if any row was affected
rowsAffected, err := result.RowsAffected()
if err != nil {
return err
}
if rowsAffected == 0 {
return &model.ErrNotFound{
When: time.Now(),
What: "There is no deleted TODO",
}
}
return nil
}