repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

1.7 KB · 92 lines · Go Raw History
 1package main
 2
 3import (
 4	"crypto/rand"
 5	"encoding/hex"
 6	"sync"
 7	"time"
 8)
 9
10// Turn is one exchange. Follow-ups need the previous question and answer to be
11// rewritten into something a search engine can take.
12type Turn struct {
13	Question string
14	Answer   string
15}
16
17// Sessions keeps conversations in memory and never on disk.
18//
19// This is the one place a question exists in readable form, and it is the
20// reason it stays in RAM: the database holds pages and HMACed search keys
21// precisely so that restarting the process is enough to forget what was asked.
22type Sessions struct {
23	mu   sync.Mutex
24	data map[string]*conversation
25}
26
27type conversation struct {
28	turns []Turn
29	seen  time.Time
30}
31
32const (
33	sessionTTL   = 2 * time.Hour
34	maxTurnsKept = 4
35)
36
37func NewSessions() *Sessions {
38	s := &Sessions{data: map[string]*conversation{}}
39	go s.expire()
40	return s
41}
42
43func NewSessionID() string {
44	b := make([]byte, 16)
45	rand.Read(b)
46	return hex.EncodeToString(b)
47}
48
49func (s *Sessions) History(id string) []Turn {
50	s.mu.Lock()
51	defer s.mu.Unlock()
52	c, ok := s.data[id]
53	if !ok {
54		return nil
55	}
56	c.seen = time.Now()
57	return append([]Turn(nil), c.turns...)
58}
59
60func (s *Sessions) Append(id string, t Turn) {
61	s.mu.Lock()
62	defer s.mu.Unlock()
63	c, ok := s.data[id]
64	if !ok {
65		c = &conversation{}
66		s.data[id] = c
67	}
68	c.turns = append(c.turns, t)
69	if len(c.turns) > maxTurnsKept {
70		c.turns = c.turns[len(c.turns)-maxTurnsKept:]
71	}
72	c.seen = time.Now()
73}
74
75func (s *Sessions) Reset(id string) {
76	s.mu.Lock()
77	defer s.mu.Unlock()
78	delete(s.data, id)
79}
80
81func (s *Sessions) expire() {
82	for range time.Tick(10 * time.Minute) {
83		s.mu.Lock()
84		for id, c := range s.data {
85			if time.Since(c.seen) > sessionTTL {
86				delete(s.data, id)
87			}
88		}
89		s.mu.Unlock()
90	}
91}