···11+PORT=8181
22+BASE_URL=http://localhost:8181
33+CLIENT_NAME=AT Todo App
+32
.gitignore
···11+# Binaries
22+attodo
33+*.exe
44+*.exe~
55+*.dll
66+*.so
77+*.dylib
88+99+# Test binary, built with `go test -c`
1010+*.test
1111+1212+# Output of the go coverage tool
1313+*.out
1414+1515+# Dependency directories
1616+vendor/
1717+1818+# Environment variables
1919+.env
2020+2121+# IDE
2222+.vscode/
2323+.idea/
2424+*.swp
2525+*.swo
2626+*~
2727+2828+# OS
2929+.DS_Store
3030+Thumbs.db
3131+3232+attodo
+68
cmd/server/main.go
···11+package main
22+33+import (
44+ "log"
55+ "net/http"
66+77+ "github.com/shindakun/attodo/internal/config"
88+ "github.com/shindakun/attodo/internal/handlers"
99+ "github.com/shindakun/attodo/internal/middleware"
1010+)
1111+1212+func main() {
1313+ // Load config
1414+ cfg, err := config.Load()
1515+ if err != nil {
1616+ log.Fatal(err)
1717+ }
1818+1919+ // Initialize handlers
2020+ authHandler := handlers.NewAuthHandler(cfg)
2121+ authMiddleware := middleware.NewAuthMiddleware(authHandler)
2222+ taskHandler := handlers.NewTaskHandler(authHandler.Client())
2323+2424+ // Initialize templates
2525+ handlers.InitTemplates()
2626+2727+ // Setup routes
2828+ mux := http.NewServeMux()
2929+3030+ // Public routes
3131+ mux.HandleFunc("/", handleLanding(authHandler))
3232+ mux.HandleFunc("/client-metadata.json", authHandler.Client().ClientMetadataHandler())
3333+ mux.HandleFunc("/login", authHandler.HandleLogin)
3434+ mux.HandleFunc("/callback", authHandler.Client().CallbackHandler(authHandler.CallbackSuccess))
3535+ mux.HandleFunc("/logout", authHandler.Logout)
3636+3737+ // Protected routes
3838+ mux.Handle("/app", authMiddleware.RequireAuth(http.HandlerFunc(handleDashboard)))
3939+ mux.Handle("/app/tasks", authMiddleware.RequireAuth(http.HandlerFunc(taskHandler.HandleTasks)))
4040+4141+ // Start server
4242+ log.Printf("Starting server on :%s", cfg.Port)
4343+ log.Printf("Visit %s to get started", cfg.BaseURL)
4444+ log.Fatal(http.ListenAndServe(":"+cfg.Port, mux))
4545+}
4646+4747+func handleLanding(authHandler *handlers.AuthHandler) http.HandlerFunc {
4848+ return func(w http.ResponseWriter, r *http.Request) {
4949+ // Check if user has a session
5050+ sessionCookie, err := r.Cookie("session_id")
5151+ if err == nil {
5252+ // Try to get session
5353+ session, err := authHandler.Client().GetSession(sessionCookie.Value)
5454+ if err == nil && session != nil {
5555+ // User is logged in, redirect to dashboard
5656+ http.Redirect(w, r, "/app", http.StatusSeeOther)
5757+ return
5858+ }
5959+ }
6060+6161+ // Not logged in, show landing page
6262+ handlers.Render(w, "landing.html", nil)
6363+ }
6464+}
6565+6666+func handleDashboard(w http.ResponseWriter, r *http.Request) {
6767+ handlers.Render(w, "dashboard.html", nil)
6868+}
···11+package models
22+33+import "time"
44+55+// Task represents a todo item stored in AT Protocol
66+type Task struct {
77+ Title string `json:"title"`
88+ Description string `json:"description,omitempty"`
99+ Completed bool `json:"completed"`
1010+ CreatedAt time.Time `json:"createdAt"`
1111+ CompletedAt *time.Time `json:"completedAt,omitempty"` // Pointer so it can be nil/omitted
1212+1313+ // Metadata from AT Protocol (populated after creation)
1414+ RKey string `json:"-"` // Record key (extracted from URI)
1515+ URI string `json:"-"` // Full AT URI
1616+}