lol
This commit is contained in:
parent
91d308485b
commit
0535cd1aad
1 changed files with 132 additions and 27 deletions
159
main.go
159
main.go
|
@ -46,7 +46,6 @@ type ItemLock struct {
|
||||||
|
|
||||||
// ==== Globals ====
|
// ==== Globals ====
|
||||||
var (
|
var (
|
||||||
db *sql.DB
|
|
||||||
sseClients = make(map[string]map[chan string]bool) // checklist uuid → set of client channels
|
sseClients = make(map[string]map[chan string]bool) // checklist uuid → set of client channels
|
||||||
sseClientsMutex sync.Mutex
|
sseClientsMutex sync.Mutex
|
||||||
itemLocks = make(map[int]*ItemLock) // item ID → lock
|
itemLocks = make(map[int]*ItemLock) // item ID → lock
|
||||||
|
@ -55,14 +54,21 @@ var (
|
||||||
|
|
||||||
// ==== Database ====
|
// ==== Database ====
|
||||||
|
|
||||||
func setupDatabase() error {
|
func getChecklistDB(uuid string) (*sql.DB, error) {
|
||||||
var err error
|
// Ensure data directory exists
|
||||||
db, err = sql.Open("sqlite3", "data/checklists.db")
|
if err := os.MkdirAll("data", 0755); err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dbPath := fmt.Sprintf("data/%s.db", uuid)
|
||||||
|
db, err := sql.Open("sqlite3", dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup schema for this checklist
|
||||||
queries := []string{
|
queries := []string{
|
||||||
`CREATE TABLE IF NOT EXISTS checklists (
|
`CREATE TABLE IF NOT EXISTS checklist_info (
|
||||||
uuid TEXT PRIMARY KEY,
|
uuid TEXT PRIMARY KEY,
|
||||||
name TEXT NOT NULL
|
name TEXT NOT NULL
|
||||||
);`,
|
);`,
|
||||||
|
@ -71,23 +77,52 @@ func setupDatabase() error {
|
||||||
content TEXT NOT NULL,
|
content TEXT NOT NULL,
|
||||||
checked INTEGER NOT NULL,
|
checked INTEGER NOT NULL,
|
||||||
parent_id INTEGER,
|
parent_id INTEGER,
|
||||||
checklist_uuid TEXT NOT NULL,
|
FOREIGN KEY(parent_id) REFERENCES items(id)
|
||||||
FOREIGN KEY(parent_id) REFERENCES items(id),
|
|
||||||
FOREIGN KEY(checklist_uuid) REFERENCES checklists(uuid)
|
|
||||||
);`,
|
);`,
|
||||||
}
|
}
|
||||||
for _, q := range queries {
|
for _, q := range queries {
|
||||||
if _, err := db.Exec(q); err != nil {
|
if _, err := db.Exec(q); err != nil {
|
||||||
|
db.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureChecklistExists creates a checklist if it doesn't exist
|
||||||
|
func ensureChecklistExists(uuid string) error {
|
||||||
|
db, err := getChecklistDB(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
// Check if checklist_info table has any data
|
||||||
|
var count int
|
||||||
|
err = db.QueryRow(`SELECT COUNT(*) FROM checklist_info`).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no checklist exists, create one with a default name
|
||||||
|
if count == 0 {
|
||||||
|
_, err = db.Exec(`INSERT INTO checklist_info (uuid, name) VALUES (?, ?)`, uuid, "Untitled Checklist")
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadChecklistName(uuid string) (string, error) {
|
func loadChecklistName(uuid string) (string, error) {
|
||||||
rows, err := db.Query(
|
db, err := getChecklistDB(uuid)
|
||||||
`SELECT name FROM checklists WHERE uuid = ?`,
|
if err != nil {
|
||||||
uuid)
|
return "", err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT name FROM checklist_info WHERE uuid = ?`, uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -104,9 +139,13 @@ func loadChecklistName(uuid string) (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadChecklistItems(uuid string) ([]ChecklistItem, error) {
|
func loadChecklistItems(uuid string) ([]ChecklistItem, error) {
|
||||||
rows, err := db.Query(
|
db, err := getChecklistDB(uuid)
|
||||||
`SELECT id, content, checked, parent_id FROM items WHERE checklist_uuid = ?`,
|
if err != nil {
|
||||||
uuid)
|
return nil, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
rows, err := db.Query(`SELECT id, content, checked, parent_id FROM items`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -143,13 +182,25 @@ func loadChecklistItems(uuid string) ([]ChecklistItem, error) {
|
||||||
|
|
||||||
func addChecklist(name string) (string, error) {
|
func addChecklist(name string) (string, error) {
|
||||||
uuidStr := uuid.New().String()
|
uuidStr := uuid.New().String()
|
||||||
_, err := db.Exec(`INSERT INTO checklists (uuid, name) VALUES (?, ?)`, uuidStr, name)
|
db, err := getChecklistDB(uuidStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
_, err = db.Exec(`INSERT INTO checklist_info (uuid, name) VALUES (?, ?)`, uuidStr, name)
|
||||||
return uuidStr, err
|
return uuidStr, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func addItem(uuid, content string, parentID *int) (ChecklistItem, error) {
|
func addItem(uuid, content string, parentID *int) (ChecklistItem, error) {
|
||||||
res, err := db.Exec(`INSERT INTO items (content, checked, parent_id, checklist_uuid) VALUES (?, 0, ?, ?)`,
|
db, err := getChecklistDB(uuid)
|
||||||
content, parentID, uuid)
|
if err != nil {
|
||||||
|
return ChecklistItem{}, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
res, err := db.Exec(`INSERT INTO items (content, checked, parent_id) VALUES (?, 0, ?)`,
|
||||||
|
content, parentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ChecklistItem{}, err
|
return ChecklistItem{}, err
|
||||||
}
|
}
|
||||||
|
@ -164,6 +215,12 @@ func addItem(uuid, content string, parentID *int) (ChecklistItem, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateItem(uuid string, id int, content *string, checked *bool, parentID *int) (ChecklistItem, error) {
|
func updateItem(uuid string, id int, content *string, checked *bool, parentID *int) (ChecklistItem, error) {
|
||||||
|
db, err := getChecklistDB(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return ChecklistItem{}, err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
q := "UPDATE items SET "
|
q := "UPDATE items SET "
|
||||||
args := []interface{}{}
|
args := []interface{}{}
|
||||||
set := []string{}
|
set := []string{}
|
||||||
|
@ -183,15 +240,14 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
|
||||||
set = append(set, "parent_id = ?")
|
set = append(set, "parent_id = ?")
|
||||||
args = append(args, *parentID)
|
args = append(args, *parentID)
|
||||||
}
|
}
|
||||||
q += strings.Join(set, ", ") + " WHERE id = ? AND checklist_uuid = ?"
|
q += strings.Join(set, ", ") + " WHERE id = ?"
|
||||||
args = append(args, id, uuid)
|
args = append(args, id)
|
||||||
_, err := db.Exec(q, args...)
|
_, err = db.Exec(q, args...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ChecklistItem{}, err
|
return ChecklistItem{}, err
|
||||||
}
|
}
|
||||||
// Return updated item
|
// Return updated item
|
||||||
rows, err := db.Query(
|
rows, err := db.Query(`SELECT id, content, checked, parent_id FROM items WHERE id = ?`, id)
|
||||||
`SELECT id, content, checked, parent_id FROM items WHERE id = ? AND checklist_uuid = ?`, id, uuid)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ChecklistItem{}, err
|
return ChecklistItem{}, err
|
||||||
}
|
}
|
||||||
|
@ -216,7 +272,13 @@ func updateItem(uuid string, id int, content *string, checked *bool, parentID *i
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteItem(uuid string, id int) error {
|
func deleteItem(uuid string, id int) error {
|
||||||
_, err := db.Exec(`DELETE FROM items WHERE id = ? AND checklist_uuid = ?`, id, uuid)
|
db, err := getChecklistDB(uuid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
_, err = db.Exec(`DELETE FROM items WHERE id = ?`, id)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +302,13 @@ func broadcast(uuid string, msg interface{}) {
|
||||||
func handleGetItems(w http.ResponseWriter, r *http.Request) {
|
func handleGetItems(w http.ResponseWriter, r *http.Request) {
|
||||||
uuid := strings.TrimPrefix(r.URL.Path, "/api/checklists/")
|
uuid := strings.TrimPrefix(r.URL.Path, "/api/checklists/")
|
||||||
uuid = uuid[:36]
|
uuid = uuid[:36]
|
||||||
|
|
||||||
|
// Ensure checklist exists
|
||||||
|
if err := ensureChecklistExists(uuid); err != nil {
|
||||||
|
http.Error(w, "Failed to ensure checklist exists", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
items, err := loadChecklistItems(uuid)
|
items, err := loadChecklistItems(uuid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to load items", 500)
|
http.Error(w, "Failed to load items", 500)
|
||||||
|
@ -279,6 +348,13 @@ func handleCreateChecklist(w http.ResponseWriter, r *http.Request) {
|
||||||
func handleAddItem(w http.ResponseWriter, r *http.Request) {
|
func handleAddItem(w http.ResponseWriter, r *http.Request) {
|
||||||
uuid := strings.TrimPrefix(r.URL.Path, "/api/checklists/")
|
uuid := strings.TrimPrefix(r.URL.Path, "/api/checklists/")
|
||||||
uuid = uuid[:36]
|
uuid = uuid[:36]
|
||||||
|
|
||||||
|
// Ensure checklist exists
|
||||||
|
if err := ensureChecklistExists(uuid); err != nil {
|
||||||
|
http.Error(w, "Failed to ensure checklist exists", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type Req struct {
|
type Req struct {
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
ParentID *int `json:"parent_id"`
|
ParentID *int `json:"parent_id"`
|
||||||
|
@ -309,6 +385,13 @@ func handleUpdateItem(w http.ResponseWriter, r *http.Request) {
|
||||||
uuid := parts[3]
|
uuid := parts[3]
|
||||||
id := 0
|
id := 0
|
||||||
fmt.Sscanf(parts[5], "%d", &id)
|
fmt.Sscanf(parts[5], "%d", &id)
|
||||||
|
|
||||||
|
// Ensure checklist exists
|
||||||
|
if err := ensureChecklistExists(uuid); err != nil {
|
||||||
|
http.Error(w, "Failed to ensure checklist exists", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type Req struct {
|
type Req struct {
|
||||||
Content *string `json:"content"`
|
Content *string `json:"content"`
|
||||||
Checked *bool `json:"checked"`
|
Checked *bool `json:"checked"`
|
||||||
|
@ -338,6 +421,13 @@ func handleDeleteItem(w http.ResponseWriter, r *http.Request) {
|
||||||
uuid := parts[3]
|
uuid := parts[3]
|
||||||
id := 0
|
id := 0
|
||||||
fmt.Sscanf(parts[5], "%d", &id)
|
fmt.Sscanf(parts[5], "%d", &id)
|
||||||
|
|
||||||
|
// Ensure checklist exists
|
||||||
|
if err := ensureChecklistExists(uuid); err != nil {
|
||||||
|
http.Error(w, "Failed to ensure checklist exists", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := deleteItem(uuid, id); err != nil {
|
if err := deleteItem(uuid, id); err != nil {
|
||||||
http.Error(w, "Delete failed", 500)
|
http.Error(w, "Delete failed", 500)
|
||||||
return
|
return
|
||||||
|
@ -356,6 +446,13 @@ func handleLockItem(w http.ResponseWriter, r *http.Request) {
|
||||||
uuid := parts[3]
|
uuid := parts[3]
|
||||||
id := 0
|
id := 0
|
||||||
fmt.Sscanf(parts[5], "%d", &id)
|
fmt.Sscanf(parts[5], "%d", &id)
|
||||||
|
|
||||||
|
// Ensure checklist exists
|
||||||
|
if err := ensureChecklistExists(uuid); err != nil {
|
||||||
|
http.Error(w, "Failed to ensure checklist exists", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
type Req struct {
|
type Req struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
}
|
}
|
||||||
|
@ -419,6 +516,12 @@ func handleSSE(w http.ResponseWriter, r *http.Request) {
|
||||||
uuid := strings.TrimPrefix(r.URL.Path, "/api/checklists/")
|
uuid := strings.TrimPrefix(r.URL.Path, "/api/checklists/")
|
||||||
uuid = uuid[:36]
|
uuid = uuid[:36]
|
||||||
|
|
||||||
|
// Ensure checklist exists
|
||||||
|
if err := ensureChecklistExists(uuid); err != nil {
|
||||||
|
http.Error(w, "Failed to ensure checklist exists", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
flusher, ok := w.(http.Flusher)
|
flusher, ok := w.(http.Flusher)
|
||||||
if !ok {
|
if !ok {
|
||||||
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
||||||
|
@ -477,9 +580,11 @@ func handleSSE(w http.ResponseWriter, r *http.Request) {
|
||||||
// ==== Main + Routing ====
|
// ==== Main + Routing ====
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := setupDatabase(); err != nil {
|
// Ensure data directory exists
|
||||||
log.Fatalf("DB setup: %v", err)
|
if err := os.MkdirAll("data", 0755); err != nil {
|
||||||
|
log.Fatalf("Failed to create data directory: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
go lockExpiryDaemon()
|
go lockExpiryDaemon()
|
||||||
|
|
||||||
// Serve static files from embedded filesystem
|
// Serve static files from embedded filesystem
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue