diff --git a/http.go b/http.go index 809aea1..0ded7ab 100644 --- a/http.go +++ b/http.go @@ -26,10 +26,10 @@ var PREAMBLE_DOCS = func() lextwt.Comments { c := add(nil, iAmTheWatcher) c = add(c, "") c = add(c,"Usage:") - c = add(c," %s/api/plain/users View list of users and latest twt date.", hostname) - c = add(c," %s/api/plain/twt View all twts in decending order.", hostname) - c = add(c," %s/api/plain/conv/:hash View all twts for a conversation subject.", hostname) - + c = add(c," %s/api/plain/users View list of users and latest twt date.", hostname) + c = add(c," %s/api/plain/twt View all twts in decending order.", hostname) + c = add(c," %s/api/plain/mentions?uri=:uri View all mentions for uri in decending order.", hostname) + c = add(c," %s/api/plain/conv/:hash View all twts for a conversation subject.", hostname) c = add(c,"") c = add(c,"Options:") c = add(c," uri Filter to show a specific users twts.") @@ -132,19 +132,24 @@ func httpServer(ctx context.Context, app *appState) error { reg.WriteTo(w) }) - http.HandleFunc("/api/plain/twt", func(w http.ResponseWriter, r *http.Request) { + http.HandleFunc("/api/plain/mentions", func(w http.ResponseWriter, r *http.Request) { ctx, span := otel.Span(r.Context()) defer span.End() - args := make([]any, 0, 3) - where := `where feed_id in (select feed_id from feeds where state != 'permanantly-dead')` + w.Header().Set("Content-Type", "text/plain; charset=utf-8") uri := r.URL.Query().Get("uri") - if uri != "" { - feed_id := urlNS.UUID5(uri) - where = "where feed_id = ?" - args = append(args, feed_id) + if uri == ""{ + reg := lextwt.NewTwtRegistry(PREAMBLE_DOCS, nil) + reg.WriteTo(w) + + return } + mention := urlNS.UUID5(uri).MarshalText() + + args := make([]any, 0, 3) + args = append(args, mention) + limit := 100 if v, ok := strconv.Atoi(r.URL.Query().Get("limit")); ok == nil { @@ -158,8 +163,9 @@ func httpServer(ctx context.Context, app *appState) error { var end int64 err = db.QueryRowContext(ctx, ` - select count(*) n from twts `+where+``, args...).Scan(&end) + select count(*) n from twts, json_each(mentions) where value = ?`, args...).Scan(&end) span.RecordError(err) + fmt.Println(mention, end, err) if offset < 1 { offset += end @@ -169,12 +175,6 @@ func httpServer(ctx context.Context, app *appState) error { offset = max(1, offset) args = append(args, limit, offset-int64(limit)) - span.AddEvent("twts", trace.WithAttributes( - attribute.Int("limit", limit), - attribute.Int64("offset-end", offset), - attribute.Int64("offset-start", offset-int64(limit)), - attribute.Int64("max", end), - )) qry := ` SELECT @@ -191,15 +191,16 @@ func httpServer(ctx context.Context, app *appState) error { ) using (feed_id) where rowid in ( select rowid from twts - ` + where +` + where ulid in (select ulid from twts, json_each(mentions) where value = ?) order by ulid asc limit ? offset ? ) - order by ulid asc` - + order by ulid asc + ` fmt.Println(qry, args) - w.Header().Set("Content-Type", "text/plain; charset=utf-8") + + rows, err := db.QueryContext( ctx, qry, args..., ) @@ -226,9 +227,128 @@ func httpServer(ctx context.Context, app *appState) error { return } twter := types.NewTwter(o.Nick, o.URI) + o.Text = strings.ReplaceAll(o.Text, "\n", "\u2028") twt, _ := lextwt.ParseLine(o.Text, &twter) twts = append(twts, twt) } + preamble := add(PREAMBLE_DOCS, "twt range = 1 %d", end) + preamble = add(preamble, "self = %s/mentions%s", hostname, mkqry(uri, limit, offset)) + if next := offset + int64(len(twts)); next < end { + preamble = add(preamble, "next = %s/mentions%s", hostname, mkqry(uri, limit, next)) + } + if prev := offset - int64(limit); prev > 0 { + preamble = add(preamble, "prev = %s/mentions%s", hostname, mkqry(uri, limit, prev)) + } + + reg := lextwt.NewTwtRegistry(preamble, twts) + reg.WriteTo(w) + }) + + + http.HandleFunc("/api/plain/twt", func(w http.ResponseWriter, r *http.Request) { + ctx, span := otel.Span(r.Context()) + defer span.End() + + uri := r.URL.Query().Get("uri") + + limit := 100 + if v, ok := strconv.Atoi(r.URL.Query().Get("limit")); ok == nil { + limit = v + } + + var offset int64 = 0 + if v, ok := strconv.ParseInt(r.URL.Query().Get("offset"), 10, 64); ok == nil { + offset = v + } + + twts, end, err := func(uri string, limit int, offset int64, ) ([]types.Twt, int64, error) { + args := make([]any, 0, 3) + where := `where feed_id in (select feed_id from feeds where state != 'permanantly-dead')` + + if uri != "" { + feed_id := urlNS.UUID5(uri) + where = "where feed_id = ?" + args = append(args, feed_id) + } + + var end int64 + err = db.QueryRowContext(ctx, ` + select count(*) n from twts `+where+``, args...).Scan(&end) + span.RecordError(err) + + if offset < 1 { + offset += end + } + + limit = min(100, max(1, limit)) + offset = max(1, offset) + + args = append(args, limit, offset-int64(limit)) + span.AddEvent("twts", trace.WithAttributes( + attribute.Int("limit", limit), + attribute.Int64("offset-end", offset), + attribute.Int64("offset-start", offset-int64(limit)), + attribute.Int64("max", end), + )) + + qry := ` + SELECT + feed_id, + hash, + conv, + coalesce(nick, 'nobody') nick, + coalesce(uri, 'https://empty.txt') uri, + text + FROM twts + join ( + select feed_id, nick, uri + from feeds + ) using (feed_id) + where rowid in ( + select rowid from twts + ` + where +` + order by ulid asc + limit ? + offset ? + ) + order by ulid asc` + + fmt.Println(qry, args) + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + rows, err := db.QueryContext( + ctx, qry, args..., + ) + if err != nil { + span.RecordError(err) + return nil, 0, err + } + defer rows.Close() + + var twts []types.Twt + for rows.Next() { + var o struct { + FeedID string + Hash string + Conv string + Dt string + Nick string + URI string + Text string + } + err = rows.Scan(&o.FeedID, &o.Hash, &o.Conv, &o.Nick, &o.URI, &o.Text) + if err != nil { + span.RecordError(err) + return nil, 0, err + } + twter := types.NewTwter(o.Nick, o.URI) + twt, _ := lextwt.ParseLine(o.Text, &twter) + twts = append(twts, twt) + } + + return twts, end, err + } (uri, limit, offset) + span.RecordError(err) + preamble := add(PREAMBLE_DOCS, "twt range = 1 %d", end) preamble = add(preamble, "self = %s/api/plain/twt%s", hostname, mkqry(uri, limit, offset)) if next := offset + int64(len(twts)); next < end {