feat(api): projects, sections, tasks, quick add, views and live sync
Completes the MVP surface: project and section CRUD, task CRUD with sub-trees, drag-and-drop ordering, the quick-add endpoint over the natural-language parser, Today and Upcoming, search, sharing with roles, an activity log and a WebSocket for live updates. Permission checks are route middleware rather than a first line in each handler, so an endpoint is guarded by where it sits instead of by the author remembering. Anything the caller may not see answers 404, never 403 — a 403 confirms the thing exists. Visibility is enforced inside the list query, so another user's project cannot be reached by filtering or searching at it. Ordering is a float, so a drop writes one row instead of renumbering the list. When repeated drops into one gap exhaust the precision, the section is spread out again and the move retried — in a separate transaction, since the attempt that discovered the problem rolls back. The connection pool is not 1: a single connection makes the pool a lock, and a query run while an earlier result set is still open then waits for a connection it is itself holding. The store now closes its rows before querying again, and the pool leaves that mistake slow rather than fatal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1bb644f4d1
commit
dff86dd23d
+15
-5
@@ -9,8 +9,10 @@ import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "modernc.org/sqlite" // pure-Go driver: no cgo, so the binary stays static
|
||||
)
|
||||
@@ -46,11 +48,19 @@ func Open(path string) (*DB, error) {
|
||||
return nil, fmt.Errorf("open sqlite: %w", err)
|
||||
}
|
||||
|
||||
// One writer. SQLite serialises writes anyway, and letting the pool open several
|
||||
// write connections only converts that into SQLITE_BUSY churn. Reads still run
|
||||
// concurrently against the WAL through this same pool.
|
||||
sqlDB.SetMaxOpenConns(1)
|
||||
sqlDB.SetMaxIdleConns(1)
|
||||
// SQLite serialises writes whatever the pool does, so this is sized for readers.
|
||||
// In WAL mode they run concurrently with a write in flight, and busy_timeout
|
||||
// absorbs the contention between writers.
|
||||
//
|
||||
// Not 1. A single connection makes the pool a lock, and any code that runs a
|
||||
// query while an earlier *sql.Rows is still open then waits forever for a
|
||||
// connection it is itself holding — a deadlock rather than a slow query, and
|
||||
// one that only appears once a list has rows in it. The store closes its result
|
||||
// sets before querying again, and this leaves room for the mistake to be slow
|
||||
// instead of fatal if it is ever made again.
|
||||
sqlDB.SetMaxOpenConns(max(4, runtime.NumCPU()))
|
||||
sqlDB.SetMaxIdleConns(4)
|
||||
sqlDB.SetConnMaxLifetime(time.Hour)
|
||||
|
||||
if err := sqlDB.PingContext(context.Background()); err != nil {
|
||||
sqlDB.Close()
|
||||
|
||||
Reference in New Issue
Block a user