feat: initial scaffold kanban_cpp v0.1.0
C++ ImGui kanban for steering LLM agents. Six panels (Board, Calendar, Dashboard, Agent runs, Worktrees, DoD inspector) wired to registry functions http_request, kpi_card, sparkline, agent_runs_timeline, dod_evidence_panel. Backend Go on :8403 (independent operations.db from kanban_web).
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"fn-registry/functions/infra"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Color string `json:"color"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
var (
|
||||
errUserNotFound = errors.New("user not found")
|
||||
errUserAlreadyExists = errors.New("username already exists")
|
||||
errInvalidCredentials = errors.New("invalid credentials")
|
||||
)
|
||||
|
||||
func (db *DB) CreateUser(username, password, displayName string) (*User, error) {
|
||||
username = strings.TrimSpace(strings.ToLower(username))
|
||||
if username == "" {
|
||||
return nil, fmt.Errorf("username required")
|
||||
}
|
||||
if len(password) < 4 {
|
||||
return nil, fmt.Errorf("password must be at least 4 characters")
|
||||
}
|
||||
hash, err := infra.PasswordHash(password, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("hash: %w", err)
|
||||
}
|
||||
u := User{ID: newID(), Username: username, DisplayName: displayName, CreatedAt: nowRFC3339()}
|
||||
_, err = db.conn.Exec(
|
||||
`INSERT INTO users (id, username, password_hash, display_name, created_at) VALUES (?, ?, ?, ?, ?)`,
|
||||
u.ID, u.Username, hash, u.DisplayName, u.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "UNIQUE") {
|
||||
return nil, errUserAlreadyExists
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetUserByID(id string) (*User, error) {
|
||||
var u User
|
||||
err := db.conn.QueryRow(
|
||||
`SELECT id, username, display_name, color, created_at FROM users WHERE id=?`, id,
|
||||
).Scan(&u.ID, &u.Username, &u.DisplayName, &u.Color, &u.CreatedAt)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, errUserNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &u, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetUserByUsername(username string) (*User, string, error) {
|
||||
username = strings.TrimSpace(strings.ToLower(username))
|
||||
var u User
|
||||
var hash string
|
||||
err := db.conn.QueryRow(
|
||||
`SELECT id, username, display_name, color, created_at, password_hash FROM users WHERE username=?`, username,
|
||||
).Scan(&u.ID, &u.Username, &u.DisplayName, &u.Color, &u.CreatedAt, &hash)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, "", errUserNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return &u, hash, nil
|
||||
}
|
||||
|
||||
func (db *DB) ListUsers() ([]User, error) {
|
||||
rows, err := db.conn.Query(`SELECT id, username, display_name, color, created_at FROM users ORDER BY username`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := []User{}
|
||||
for rows.Next() {
|
||||
var u User
|
||||
if err := rows.Scan(&u.ID, &u.Username, &u.DisplayName, &u.Color, &u.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, u)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (db *DB) Authenticate(username, password string) (*User, error) {
|
||||
u, hash, err := db.GetUserByUsername(username)
|
||||
if err != nil {
|
||||
if errors.Is(err, errUserNotFound) {
|
||||
return nil, errInvalidCredentials
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err := infra.PasswordVerify(password, hash); err != nil {
|
||||
return nil, errInvalidCredentials
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (db *DB) CountUsers() (int, error) {
|
||||
var n int
|
||||
if err := db.conn.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&n); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (db *DB) UpdateUserColor(id, color string) error {
|
||||
_, err := db.conn.Exec(`UPDATE users SET color=? WHERE id=?`, color, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) DeleteSessionByToken(token string) error {
|
||||
_, err := db.conn.Exec(`DELETE FROM sessions WHERE token=?`, token)
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user