claude go reimplement
This commit is contained in:
1 parent
4bcb924158
commit
f523c79618
83 files changed
+3714
-3454
No files matched your search
@@ -0,0 +1,163 @@
|
||||
package kjv
|
||||
|
||||
import (
|
||||
"crypto/subtle"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Optional endpoints backed by the separate sermon database (KJV_SERMON_DB)
|
||||
// and the separate semantic search service (KJV_SEMANTIC_ADDR, default
|
||||
// localhost:1613). Neither is part of the distribution; without them these
|
||||
// endpoints answer with an error.
|
||||
|
||||
func rowsToMaps(rows *sql.Rows) ([]map[string]any, error) {
|
||||
defer rows.Close()
|
||||
cols, err := rows.Columns()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := []map[string]any{}
|
||||
for rows.Next() {
|
||||
vals := make([]any, len(cols))
|
||||
ptrs := make([]any, len(cols))
|
||||
for i := range vals {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
if err := rows.Scan(ptrs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := map[string]any{}
|
||||
for i, c := range cols {
|
||||
if b, ok := vals[i].([]byte); ok {
|
||||
vals[i] = string(b)
|
||||
}
|
||||
m[c] = vals[i]
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (a *App) sermonQuery(query string, args ...any) Resp {
|
||||
if a.sermonDB == nil {
|
||||
return jsonStatus(map[string]string{"error": "Sermon database not found"}, http.StatusServiceUnavailable)
|
||||
}
|
||||
rows, err := a.sermonDB.Query(query, args...)
|
||||
if err != nil {
|
||||
lg.Error("sermon query: %v", err)
|
||||
return errJSON(http.StatusInternalServerError, "Sermon database error")
|
||||
}
|
||||
out, err := rowsToMaps(rows)
|
||||
if err != nil {
|
||||
return errJSON(http.StatusInternalServerError, "Sermon database error")
|
||||
}
|
||||
return jsonR(out)
|
||||
}
|
||||
|
||||
const sermonCols = "select distinct title,url,denomination,preacher,church_name from verse_refs where "
|
||||
|
||||
// handleSermons serves everything under /sermon/ and /sermons/ ... by hand,
|
||||
// since the paths overlap in ways the router would reject.
|
||||
func (a *App) handleSermons(path string) Resp {
|
||||
parts := strings.Split(strings.Trim(path, "/"), "/")
|
||||
switch {
|
||||
case len(parts) == 1 && parts[0] == "sermons":
|
||||
return a.sermonQuery("select distinct title,url,denomination,preacher,church_name,filename from verse_refs")
|
||||
case len(parts) == 3 && parts[0] == "sermons" && parts[1] == "preacher":
|
||||
return a.sermonQuery(sermonCols+"preacher=?", parts[2])
|
||||
case len(parts) == 3 && parts[0] == "sermons" && parts[1] == "church":
|
||||
return a.sermonQuery(sermonCols+"church_name=?", parts[2])
|
||||
case len(parts) == 3 && parts[0] == "sermons" && parts[1] == "denomination":
|
||||
return a.sermonQuery(sermonCols+"denomination=?", parts[2])
|
||||
case len(parts) == 3 && parts[0] == "sermon" && parts[1] == "search":
|
||||
return a.sermonsByRef(parts[2])
|
||||
case len(parts) == 3 && parts[0] == "sermon" && parts[1] == "url":
|
||||
return a.sermonQuery(sermonCols+"url=?", parts[2])
|
||||
case len(parts) == 3 && parts[0] == "sermon" && parts[2] == "verses":
|
||||
return a.sermonQuery("select distinct verse_id,text from verse_refs where filename=?", parts[1])
|
||||
case len(parts) == 2 && parts[0] == "sermon":
|
||||
return a.sermonQuery(sermonCols+"filename=?", parts[1])
|
||||
}
|
||||
return a.notFound()
|
||||
}
|
||||
|
||||
func (a *App) sermonsByRef(ref string) Resp {
|
||||
id, ok := a.refVerseID(ref)
|
||||
if !ok {
|
||||
return a.notFound()
|
||||
}
|
||||
if a.sermonDB == nil {
|
||||
return jsonR(map[string]string{"error": "Sermon database not found"})
|
||||
}
|
||||
rows, err := a.sermonDB.Query("select * from verse_refs where verse_id=?", id)
|
||||
if err != nil {
|
||||
return errJSON(http.StatusInternalServerError, "Sermon database error")
|
||||
}
|
||||
out, err := rowsToMaps(rows)
|
||||
if err != nil {
|
||||
return errJSON(http.StatusInternalServerError, "Sermon database error")
|
||||
}
|
||||
for _, m := range out {
|
||||
if vid, ok := m["verse_id"].(int64); ok {
|
||||
if v := a.store.Range(vid, 0); len(v) == 1 {
|
||||
m["kjv"] = v[0].Text
|
||||
}
|
||||
}
|
||||
}
|
||||
return jsonR(out)
|
||||
}
|
||||
|
||||
// refVerseID: the verse id of the first reference in "1John5:7" style text.
|
||||
func (a *App) refVerseID(ref string) (int64, bool) {
|
||||
resolved := a.res.Resolve(ref, true)
|
||||
if resolved == nil {
|
||||
return 0, false
|
||||
}
|
||||
return resolved.Refs[0].Start, true
|
||||
}
|
||||
|
||||
func (a *App) handleRef(ref string) Resp {
|
||||
id, ok := a.refVerseID(ref)
|
||||
if !ok {
|
||||
return a.notFound()
|
||||
}
|
||||
return htmlR(itoa64(id))
|
||||
}
|
||||
|
||||
func (a *App) handleNaturalPost(body io.Reader) Resp {
|
||||
var req struct {
|
||||
Token *string `json:"token"`
|
||||
Query string `json:"query"`
|
||||
}
|
||||
if err := json.NewDecoder(io.LimitReader(body, 1<<16)).Decode(&req); err != nil {
|
||||
return errJSON(http.StatusBadRequest, "Invalid request format")
|
||||
}
|
||||
if req.Token == nil {
|
||||
return errJSON(http.StatusUnauthorized, "Missing token in request body")
|
||||
}
|
||||
if a.secretKey == "" || subtle.ConstantTimeCompare([]byte(*req.Token), []byte(a.secretKey)) != 1 {
|
||||
return errJSON(http.StatusUnauthorized, "Invalid Authorization")
|
||||
}
|
||||
conn, err := net.DialTimeout("tcp", a.semantic, 3*time.Second)
|
||||
if err != nil {
|
||||
return errJSON(http.StatusBadGateway, "Semantic search is not available")
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.SetDeadline(time.Now().Add(30 * time.Second))
|
||||
payload, _ := json.Marshal(map[string]string{"query": req.Query})
|
||||
if _, err := conn.Write(payload); err != nil {
|
||||
return errJSON(http.StatusBadGateway, "Semantic search is not available")
|
||||
}
|
||||
data, err := io.ReadAll(io.LimitReader(conn, 8<<20))
|
||||
var out any
|
||||
if err != nil || json.Unmarshal(data, &out) != nil {
|
||||
return errJSON(http.StatusBadGateway, "Semantic search gave an invalid answer")
|
||||
}
|
||||
return jsonR(out)
|
||||
}
|
||||
Reference in new issue
Block a user