chore: refactor
This commit is contained in:
parent
cd2c9abd1b
commit
ef65b115b7
51
feed.go
51
feed.go
@ -21,13 +21,14 @@ import (
|
|||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"go.sour.is/xt/internal/otel"
|
"go.sour.is/xt/internal/otel"
|
||||||
|
"go.sour.is/xt/internal/uuid"
|
||||||
"go.yarn.social/lextwt"
|
"go.yarn.social/lextwt"
|
||||||
"go.yarn.social/types"
|
"go.yarn.social/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Feed struct {
|
type Feed struct {
|
||||||
FeedID uuid
|
FeedID uuid.UUID
|
||||||
ParentID uuid
|
ParentID uuid.UUID
|
||||||
HashURI string
|
HashURI string
|
||||||
URI string
|
URI string
|
||||||
Nick string
|
Nick string
|
||||||
@ -236,7 +237,7 @@ func storeFeed(ctx context.Context, db db, f types.TwtFile) error {
|
|||||||
loadTS := time.Now()
|
loadTS := time.Now()
|
||||||
refreshRate := 600
|
refreshRate := 600
|
||||||
|
|
||||||
feedID := urlNS.UUID5(cmp.Or(f.Twter().HashingURI, f.Twter().URI))
|
feedID := uuid.UrlNS.UUID5(cmp.Or(f.Twter().HashingURI, f.Twter().URI))
|
||||||
|
|
||||||
tx, err := db.BeginTx(ctx, nil)
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -269,13 +270,13 @@ func storeFeed(ctx context.Context, db db, f types.TwtFile) error {
|
|||||||
for _, twt := range twts {
|
for _, twt := range twts {
|
||||||
twtID := makeULID(twt)
|
twtID := makeULID(twt)
|
||||||
|
|
||||||
mentions := make(uuids, 0, len(twt.Mentions()))
|
mentions := make(uuid.UUIDs, 0, len(twt.Mentions()))
|
||||||
for _, mention := range twt.Mentions() {
|
for _, mention := range twt.Mentions() {
|
||||||
followMap[mention.Twter().URI] = mention.Twter().Nick
|
followMap[mention.Twter().URI] = mention.Twter().Nick
|
||||||
mentions = append(mentions, urlNS.UUID5(mention.Twter().URI))
|
mentions = append(mentions, uuid.UrlNS.UUID5(mention.Twter().URI))
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := make(strList, 0, len(twt.Tags()))
|
tags := make(uuid.List, 0, len(twt.Tags()))
|
||||||
for _, tag := range twt.Tags() {
|
for _, tag := range twt.Tags() {
|
||||||
tags = append(tags, tag.Text())
|
tags = append(tags, tag.Text())
|
||||||
}
|
}
|
||||||
@ -335,7 +336,7 @@ func storeFeed(ctx context.Context, db db, f types.TwtFile) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
part = uri[:strings.LastIndex(uri, "/")+1] + part
|
part = uri[:strings.LastIndex(uri, "/")+1] + part
|
||||||
childID := urlNS.UUID5(part)
|
childID := uuid.UrlNS.UUID5(part)
|
||||||
fmt.Println("found prev", uri, part)
|
fmt.Println("found prev", uri, part)
|
||||||
args = append(args,
|
args = append(args,
|
||||||
childID, // feed_id
|
childID, // feed_id
|
||||||
@ -351,13 +352,13 @@ func storeFeed(ctx context.Context, db db, f types.TwtFile) error {
|
|||||||
|
|
||||||
for uri, nick := range followMap {
|
for uri, nick := range followMap {
|
||||||
args = append(args,
|
args = append(args,
|
||||||
urlNS.UUID5(uri), // feed_id
|
uuid.UrlNS.UUID5(uri), // feed_id
|
||||||
nil, // parent_id
|
nil, // parent_id
|
||||||
nick, // nick
|
nick, // nick
|
||||||
uri, // uri
|
uri, // uri
|
||||||
"warm", // state
|
"warm", // state
|
||||||
nil, // last_scan_on
|
nil, // last_scan_on
|
||||||
refreshRate, // refresh_rate
|
refreshRate, // refresh_rate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
for query, args := range chunk(args, insertFeed, db.MaxVariableNumber) {
|
for query, args := range chunk(args, insertFeed, db.MaxVariableNumber) {
|
||||||
@ -389,7 +390,7 @@ func storeRegistry(ctx context.Context, db db, in io.Reader) error {
|
|||||||
|
|
||||||
nick := twt.Twter().DomainNick()
|
nick := twt.Twter().DomainNick()
|
||||||
uri := twt.Twter().URI
|
uri := twt.Twter().URI
|
||||||
feedID := urlNS.UUID5(uri)
|
feedID := uuid.UrlNS.UUID5(uri)
|
||||||
twtID := makeULID(twt)
|
twtID := makeULID(twt)
|
||||||
text := fmt.Sprintf("%+l", twt)
|
text := fmt.Sprintf("%+l", twt)
|
||||||
|
|
||||||
@ -405,13 +406,13 @@ func storeRegistry(ctx context.Context, db db, in io.Reader) error {
|
|||||||
|
|
||||||
twters[uri] = nick
|
twters[uri] = nick
|
||||||
|
|
||||||
mentions := make(uuids, 0, len(twt.Mentions()))
|
mentions := make(uuid.UUIDs, 0, len(twt.Mentions()))
|
||||||
for _, mention := range twt.Mentions() {
|
for _, mention := range twt.Mentions() {
|
||||||
twters[uri] = nick
|
twters[uri] = nick
|
||||||
mentions = append(mentions, urlNS.UUID5(mention.Twter().URI))
|
mentions = append(mentions, uuid.UrlNS.UUID5(mention.Twter().URI))
|
||||||
}
|
}
|
||||||
|
|
||||||
tags := make(strList, 0, len(twt.Tags()))
|
tags := make(uuid.List, 0, len(twt.Tags()))
|
||||||
for _, tag := range twt.Tags() {
|
for _, tag := range twt.Tags() {
|
||||||
tags = append(tags, tag.Text())
|
tags = append(tags, tag.Text())
|
||||||
}
|
}
|
||||||
@ -466,7 +467,7 @@ func storeRegistry(ctx context.Context, db db, in io.Reader) error {
|
|||||||
// continue
|
// continue
|
||||||
// }
|
// }
|
||||||
|
|
||||||
feedID := urlNS.UUID5(uri)
|
feedID := uuid.UrlNS.UUID5(uri)
|
||||||
|
|
||||||
args = append(args,
|
args = append(args,
|
||||||
feedID, // feed_id
|
feedID, // feed_id
|
||||||
@ -641,7 +642,7 @@ func fetchTwts(ctx context.Context, db db, uri string, limit int, offset int64)
|
|||||||
where := `where feed_id in (select feed_id from feeds where state != 'permanantly-dead')`
|
where := `where feed_id in (select feed_id from feeds where state != 'permanantly-dead')`
|
||||||
|
|
||||||
if uri != "" {
|
if uri != "" {
|
||||||
feed_id := urlNS.UUID5(uri)
|
feed_id := uuid.UrlNS.UUID5(uri)
|
||||||
where = "where feed_id = ?"
|
where = "where feed_id = ?"
|
||||||
args = append(args, feed_id)
|
args = append(args, feed_id)
|
||||||
}
|
}
|
||||||
@ -732,7 +733,7 @@ func fetchUsers(ctx context.Context, db db, uri, q string) ([]types.Twt, error)
|
|||||||
args := make([]any, 0)
|
args := make([]any, 0)
|
||||||
if uri != "" {
|
if uri != "" {
|
||||||
where = `where feed_id = ? or parent_id = ?`
|
where = `where feed_id = ? or parent_id = ?`
|
||||||
feed_id := urlNS.UUID5(uri)
|
feed_id := uuid.UrlNS.UUID5(uri)
|
||||||
args = append(args, feed_id, feed_id)
|
args = append(args, feed_id, feed_id)
|
||||||
} else if q != "" {
|
} else if q != "" {
|
||||||
where = `where nick like ?`
|
where = `where nick like ?`
|
||||||
@ -783,7 +784,7 @@ func fetchUsers(ctx context.Context, db db, uri, q string) ([]types.Twt, error)
|
|||||||
return twts, nil
|
return twts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchMentions(ctx context.Context, db db, mention uuid, limit int, offset int64) ([]types.Twt, int64, int64, error) {
|
func fetchMentions(ctx context.Context, db db, mention uuid.UUID, limit int, offset int64) ([]types.Twt, int64, int64, error) {
|
||||||
ctx, span := otel.Span(ctx)
|
ctx, span := otel.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
@ -871,7 +872,7 @@ func fetchMentions(ctx context.Context, db db, mention uuid, limit int, offset i
|
|||||||
return twts, offset, end, err
|
return twts, offset, end, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchConv(ctx context.Context, db db, hash string, limit int, offset int64) ([]types.Twt, int64, int64, error) {
|
func fetchConv(ctx context.Context, db db, hash string, _ int, offset int64) ([]types.Twt, int64, int64, error) {
|
||||||
ctx, span := otel.Span(ctx)
|
ctx, span := otel.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
@ -925,7 +926,7 @@ func fetchConv(ctx context.Context, db db, hash string, limit int, offset int64)
|
|||||||
err = rows.Scan(&o.FeedID, &o.Hash, &o.Conv, &o.Nick, &o.URI, &o.Text)
|
err = rows.Scan(&o.FeedID, &o.Hash, &o.Conv, &o.Nick, &o.URI, &o.Text)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
span.RecordError(err)
|
span.RecordError(err)
|
||||||
return nil, 0,0,err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
twter := types.NewTwter(o.Nick, o.URI)
|
twter := types.NewTwter(o.Nick, o.URI)
|
||||||
twt, _ := lextwt.ParseLine(o.Text, &twter)
|
twt, _ := lextwt.ParseLine(o.Text, &twter)
|
||||||
@ -934,4 +935,4 @@ func fetchConv(ctx context.Context, db db, hash string, limit int, offset int64)
|
|||||||
err = rows.Err()
|
err = rows.Err()
|
||||||
|
|
||||||
return twts, offset, end, err
|
return twts, offset, end, err
|
||||||
}
|
}
|
||||||
|
225
http-api.go
Normal file
225
http-api.go
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.sour.is/xt/internal/otel"
|
||||||
|
"go.sour.is/xt/internal/uuid"
|
||||||
|
"go.yarn.social/lextwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API struct {
|
||||||
|
app *appState
|
||||||
|
db db
|
||||||
|
hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) plain(w http.ResponseWriter, r *http.Request) {
|
||||||
|
reg := lextwt.NewTwtRegistry(mkPreambleDocs(a.hostname), nil)
|
||||||
|
reg.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) conv(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, span := otel.Span(r.Context())
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
hash := r.PathValue("hash")
|
||||||
|
if (len(hash) < 6 || len(hash) > 8) && !notAny(hash, "abcdefghijklmnopqrstuvwxyz234567") {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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, offset, end, err := fetchConv(ctx, a.db, hash, limit, offset)
|
||||||
|
span.RecordError(err)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "ERR", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preamble := mkPreamble(a.hostname, "", "/api/plain/conv/"+hash, limit, int64(len(twts)), offset, end)
|
||||||
|
reg := lextwt.NewTwtRegistry(preamble, twts)
|
||||||
|
reg.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) mentions(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, span := otel.Span(r.Context())
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
uri := r.URL.Query().Get("uri")
|
||||||
|
if uri == "" {
|
||||||
|
reg := lextwt.NewTwtRegistry(mkPreambleDocs(a.hostname), nil)
|
||||||
|
reg.WriteTo(w)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mention := uuid.UrlNS.UUID5(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, offset, end, err := fetchMentions(ctx, a.db, mention, limit, offset)
|
||||||
|
span.RecordError(err)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "ERR", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preamble := mkPreamble(a.hostname, uri, "/api/plain/mentions", limit, int64(len(twts)), offset, end)
|
||||||
|
reg := lextwt.NewTwtRegistry(preamble, twts)
|
||||||
|
reg.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) twt(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, span := otel.Span(r.Context())
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
uri := r.URL.Query().Get("uri")
|
||||||
|
|
||||||
|
limit := 100
|
||||||
|
if v, ok := strconv.Atoi(r.URL.Query().Get("limit")); ok == nil {
|
||||||
|
limit = v
|
||||||
|
}
|
||||||
|
limit = min(100, max(1, limit))
|
||||||
|
|
||||||
|
var offset int64 = 0
|
||||||
|
if v, ok := strconv.ParseInt(r.URL.Query().Get("offset"), 10, 64); ok == nil {
|
||||||
|
offset = v
|
||||||
|
}
|
||||||
|
|
||||||
|
twts, offset, end, err := fetchTwts(ctx, a.db, uri, limit, offset)
|
||||||
|
span.RecordError(err)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "ERR", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
preamble := mkPreamble(a.hostname, uri, "/api/plain/twt", limit, int64(len(twts)), offset, end)
|
||||||
|
|
||||||
|
reg := lextwt.NewTwtRegistry(preamble, twts)
|
||||||
|
reg.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) users(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx, span := otel.Span(r.Context())
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
|
||||||
|
uri := r.URL.Query().Get("uri")
|
||||||
|
q := r.URL.Query().Get("q")
|
||||||
|
|
||||||
|
twts, err := fetchUsers(ctx, a.db, uri, q)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "ERR", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
reg := lextwt.NewTwtRegistry(mkPreambleDocs(a.hostname), twts)
|
||||||
|
reg.WriteTo(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) queue(w http.ResponseWriter, r *http.Request) {
|
||||||
|
lis := slices.Collect(a.app.queue.Iter())
|
||||||
|
sort.Slice(lis, func(i, j int) bool {
|
||||||
|
return lis[i].NextScanOn.Time.Before(lis[j].LastScanOn.Time)
|
||||||
|
})
|
||||||
|
for _, feed := range lis {
|
||||||
|
fmt.Fprintln(w, feed.State, feed.NextScanOn.Time.Format(time.RFC3339), feed.Nick, feed.URI)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notAny(s string, chars string) bool {
|
||||||
|
for _, c := range s {
|
||||||
|
if !strings.ContainsRune(chars, c) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkqry(uri string, limit int, offset int64) string {
|
||||||
|
qry := make([]string, 0, 3)
|
||||||
|
|
||||||
|
if uri != "" {
|
||||||
|
qry = append(qry, "uri="+uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if limit != 100 {
|
||||||
|
qry = append(qry, fmt.Sprint("limit=", limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
if offset != 0 {
|
||||||
|
qry = append(qry, fmt.Sprint("offset=", offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(qry) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return "?" + strings.Join(qry, "&")
|
||||||
|
}
|
||||||
|
|
||||||
|
func add(preamble lextwt.Comments, text string, v ...any) lextwt.Comments {
|
||||||
|
if len(v) > 0 {
|
||||||
|
text = fmt.Sprintf(text, v...)
|
||||||
|
}
|
||||||
|
return append(preamble, lextwt.NewComment("# "+text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkPreamble(hostname, uri, path string, limit int, length, offset, end int64) lextwt.Comments {
|
||||||
|
uri += path
|
||||||
|
preamble := add(mkPreambleDocs(hostname), "twt range = 1 %d", end)
|
||||||
|
preamble = add(preamble, "self = %s%s", hostname, mkqry(uri, limit, offset))
|
||||||
|
if next := offset + length; next < end {
|
||||||
|
preamble = add(preamble, "next = %s%s", hostname, mkqry(uri, limit, next))
|
||||||
|
}
|
||||||
|
if prev := offset - int64(limit); prev > 0 {
|
||||||
|
preamble = add(preamble, "prev = %s%s", hostname, mkqry(uri, limit, prev))
|
||||||
|
}
|
||||||
|
return preamble
|
||||||
|
}
|
||||||
|
|
||||||
|
var mkPreambleDocs = func(hostname string) 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.", hostname)
|
||||||
|
c = add(c, " %s/api/plain/mentions?uri=:uri View all mentions for uri.", 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.")
|
||||||
|
c = add(c, " offset Start index for quey.")
|
||||||
|
c = add(c, " limit Count of items to return (going back in time).")
|
||||||
|
return add(c, "")
|
||||||
|
}
|
30
http-html.go
Normal file
30
http-html.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.sour.is/xt/internal/otel"
|
||||||
|
)
|
||||||
|
|
||||||
|
type HTML struct {
|
||||||
|
app *appState
|
||||||
|
db db
|
||||||
|
hostname string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *HTML) healthcheck(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, span := otel.Span(r.Context())
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *HTML) home(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_, span := otel.Span(r.Context())
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
}
|
||||||
|
|
230
http.go
230
http.go
@ -5,35 +5,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"go.yarn.social/lextwt"
|
|
||||||
|
|
||||||
"go.sour.is/xt/internal/otel"
|
"go.sour.is/xt/internal/otel"
|
||||||
)
|
)
|
||||||
|
|
||||||
const iAmTheWatcher = "I am the Watcher. I am your guide through this vast new twtiverse."
|
const iAmTheWatcher = "I am the Watcher. I am your guide through this vast new twtiverse."
|
||||||
|
|
||||||
var mkPreambleDocs = func(hostname string) 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.", hostname)
|
|
||||||
c = add(c, " %s/api/plain/mentions?uri=:uri View all mentions for uri.", 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.")
|
|
||||||
c = add(c, " offset Start index for quey.")
|
|
||||||
c = add(c, " limit Count of items to return (going back in time).")
|
|
||||||
return add(c, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func httpServer(ctx context.Context, app *appState) error {
|
func httpServer(ctx context.Context, app *appState) error {
|
||||||
ctx, span := otel.Span(ctx)
|
ctx, span := otel.Span(ctx)
|
||||||
defer span.End()
|
defer span.End()
|
||||||
@ -52,13 +29,14 @@ func httpServer(ctx context.Context, app *appState) error {
|
|||||||
hostname: app.args.Hostname,
|
hostname: app.args.Hostname,
|
||||||
}
|
}
|
||||||
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
html := HTML{
|
||||||
_, span := otel.Span(r.Context())
|
app: app,
|
||||||
defer span.End()
|
db: db,
|
||||||
|
hostname: app.args.Hostname,
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
http.HandleFunc("/", html.home)
|
||||||
w.Write([]byte("ok"))
|
http.HandleFunc("/health", html.healthcheck)
|
||||||
})
|
|
||||||
|
|
||||||
http.HandleFunc("/api/plain", api.plain)
|
http.HandleFunc("/api/plain", api.plain)
|
||||||
http.HandleFunc("/api/plain/conv/{hash}", api.conv)
|
http.HandleFunc("/api/plain/conv/{hash}", api.conv)
|
||||||
@ -82,197 +60,3 @@ func httpServer(ctx context.Context, app *appState) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func notAny(s string, chars string) bool {
|
|
||||||
for _, c := range s {
|
|
||||||
if !strings.ContainsRune(chars, c) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkqry(uri string, limit int, offset int64) string {
|
|
||||||
qry := make([]string, 0, 3)
|
|
||||||
|
|
||||||
if uri != "" {
|
|
||||||
qry = append(qry, "uri="+uri)
|
|
||||||
}
|
|
||||||
|
|
||||||
if limit != 100 {
|
|
||||||
qry = append(qry, fmt.Sprint("limit=", limit))
|
|
||||||
}
|
|
||||||
|
|
||||||
if offset != 0 {
|
|
||||||
qry = append(qry, fmt.Sprint("offset=", offset))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(qry) == 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return "?" + strings.Join(qry, "&")
|
|
||||||
}
|
|
||||||
|
|
||||||
func add(preamble lextwt.Comments, text string, v ...any) lextwt.Comments {
|
|
||||||
if len(v) > 0 {
|
|
||||||
text = fmt.Sprintf(text, v...)
|
|
||||||
}
|
|
||||||
return append(preamble, lextwt.NewComment("# "+text))
|
|
||||||
}
|
|
||||||
|
|
||||||
func mkPreamble(hostname, uri, path string, limit int, length, offset, end int64) lextwt.Comments {
|
|
||||||
uri += path
|
|
||||||
preamble := add(mkPreambleDocs(hostname), "twt range = 1 %d", end)
|
|
||||||
preamble = add(preamble, "self = %s%s", hostname, mkqry(uri, limit, offset))
|
|
||||||
if next := offset + length; next < end {
|
|
||||||
preamble = add(preamble, "next = %s%s", hostname, mkqry(uri, limit, next))
|
|
||||||
}
|
|
||||||
if prev := offset - int64(limit); prev > 0 {
|
|
||||||
preamble = add(preamble, "prev = %s%s", hostname, mkqry(uri, limit, prev))
|
|
||||||
}
|
|
||||||
return preamble
|
|
||||||
}
|
|
||||||
|
|
||||||
type API struct {
|
|
||||||
app *appState
|
|
||||||
db db
|
|
||||||
hostname string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) plain(w http.ResponseWriter, r *http.Request) {
|
|
||||||
reg := lextwt.NewTwtRegistry(mkPreambleDocs(a.hostname), nil)
|
|
||||||
reg.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) conv(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx, span := otel.Span(r.Context())
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
|
|
||||||
hash := r.PathValue("hash")
|
|
||||||
if (len(hash) < 6 || len(hash) > 8) && !notAny(hash, "abcdefghijklmnopqrstuvwxyz234567") {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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, offset, end, err := fetchConv(ctx, a.db, hash, limit, offset)
|
|
||||||
span.RecordError(err)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "ERR", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
preamble := mkPreamble(a.hostname, "", "/api/plain/conv/"+hash, limit, int64(len(twts)), offset, end)
|
|
||||||
reg := lextwt.NewTwtRegistry(preamble, twts)
|
|
||||||
reg.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) mentions(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx, span := otel.Span(r.Context())
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
|
|
||||||
uri := r.URL.Query().Get("uri")
|
|
||||||
if uri == "" {
|
|
||||||
reg := lextwt.NewTwtRegistry(mkPreambleDocs(a.hostname), nil)
|
|
||||||
reg.WriteTo(w)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
mention := urlNS.UUID5(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, offset, end, err := fetchMentions(ctx, a.db, mention, limit, offset)
|
|
||||||
span.RecordError(err)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "ERR", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
preamble := mkPreamble(a.hostname, uri, "/api/plain/mentions", limit, int64(len(twts)), offset, end)
|
|
||||||
reg := lextwt.NewTwtRegistry(preamble, twts)
|
|
||||||
reg.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) twt(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx, span := otel.Span(r.Context())
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
|
|
||||||
uri := r.URL.Query().Get("uri")
|
|
||||||
|
|
||||||
limit := 100
|
|
||||||
if v, ok := strconv.Atoi(r.URL.Query().Get("limit")); ok == nil {
|
|
||||||
limit = v
|
|
||||||
}
|
|
||||||
limit = min(100, max(1, limit))
|
|
||||||
|
|
||||||
var offset int64 = 0
|
|
||||||
if v, ok := strconv.ParseInt(r.URL.Query().Get("offset"), 10, 64); ok == nil {
|
|
||||||
offset = v
|
|
||||||
}
|
|
||||||
|
|
||||||
twts, offset, end, err := fetchTwts(ctx, a.db, uri, limit, offset)
|
|
||||||
span.RecordError(err)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "ERR", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
preamble := mkPreamble(a.hostname, uri, "/api/plain/twt", limit, int64(len(twts)), offset, end)
|
|
||||||
|
|
||||||
reg := lextwt.NewTwtRegistry(preamble, twts)
|
|
||||||
reg.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) users(w http.ResponseWriter, r *http.Request) {
|
|
||||||
ctx, span := otel.Span(r.Context())
|
|
||||||
defer span.End()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
|
||||||
|
|
||||||
uri := r.URL.Query().Get("uri")
|
|
||||||
q := r.URL.Query().Get("q")
|
|
||||||
|
|
||||||
twts, err := fetchUsers(ctx, a.db, uri, q)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "ERR", 500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
reg := lextwt.NewTwtRegistry(mkPreambleDocs(a.hostname), twts)
|
|
||||||
reg.WriteTo(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *API) queue(w http.ResponseWriter, r *http.Request) {
|
|
||||||
lis := slices.Collect(a.app.queue.Iter())
|
|
||||||
sort.Slice(lis, func(i, j int) bool {
|
|
||||||
return lis[i].NextScanOn.Time.Before(lis[j].LastScanOn.Time)
|
|
||||||
})
|
|
||||||
for _, feed := range lis {
|
|
||||||
fmt.Fprintln(w, feed.State, feed.NextScanOn.Time.Format(time.RFC3339), feed.Nick, feed.URI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package uuid
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
@ -8,54 +8,54 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type uuid [16]byte
|
type UUID [16]byte
|
||||||
|
|
||||||
var urlNS = uuid{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}
|
var UrlNS = UUID{0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8}
|
||||||
|
|
||||||
func (u uuid) UUID5(value string) uuid {
|
func (u UUID) UUID5(value string) UUID {
|
||||||
h := sha1.New()
|
h := sha1.New()
|
||||||
h.Write(u[:])
|
h.Write(u[:])
|
||||||
h.Write([]byte(value))
|
h.Write([]byte(value))
|
||||||
return uuid(h.Sum(nil))
|
return UUID(h.Sum(nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *uuid) UnmarshalText(data string) error {
|
func (u *UUID) UnmarshalText(data string) error {
|
||||||
data = strings.Trim(data, "{}")
|
data = strings.Trim(data, "{}")
|
||||||
data = strings.ReplaceAll(data, "-", "")
|
data = strings.ReplaceAll(data, "-", "")
|
||||||
|
|
||||||
_, err := hex.Decode(u[:], []byte(data))
|
_, err := hex.Decode(u[:], []byte(data))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
func (u uuid) MarshalText() string {
|
func (u UUID) MarshalText() string {
|
||||||
s := hex.EncodeToString(u[:])
|
s := hex.EncodeToString(u[:])
|
||||||
return fmt.Sprintf("{%s-%s-%s-%s-%s}", s[:8], s[8:12], s[12:16], s[16:20], s[20:])
|
return fmt.Sprintf("{%s-%s-%s-%s-%s}", s[:8], s[8:12], s[12:16], s[16:20], s[20:])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u uuid) Value() (driver.Value, error) {
|
func (u UUID) Value() (driver.Value, error) {
|
||||||
return u[:], nil
|
return u[:], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *uuid) Scan(value any) error {
|
func (u *UUID) Scan(value any) error {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
*u = uuid(value.([]byte))
|
*u = UUID(value.([]byte))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type uuids []uuid
|
type UUIDs []UUID
|
||||||
|
|
||||||
func (lis uuids) ToStrList() strList {
|
func (lis UUIDs) ToStrList() List {
|
||||||
arr := make(strList, len(lis))
|
arr := make(List, len(lis))
|
||||||
for i, v := range lis {
|
for i, v := range lis {
|
||||||
arr[i] = v.MarshalText()
|
arr[i] = v.MarshalText()
|
||||||
}
|
}
|
||||||
return arr
|
return arr
|
||||||
}
|
}
|
||||||
|
|
||||||
type strList []string
|
type List []string
|
||||||
|
|
||||||
func (l *strList) Scan(value any) error {
|
func (l *List) Scan(value any) error {
|
||||||
s := value.(string)
|
s := value.(string)
|
||||||
s = strings.Trim(s, "[]")
|
s = strings.Trim(s, "[]")
|
||||||
for _, v := range strings.Split(s, ",") {
|
for _, v := range strings.Split(s, ",") {
|
||||||
@ -67,7 +67,7 @@ func (l *strList) Scan(value any) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l strList) Value() (driver.Value, error) {
|
func (l List) Value() (driver.Value, error) {
|
||||||
arr := make([]string, len(l))
|
arr := make([]string, len(l))
|
||||||
for i, v := range l {
|
for i, v := range l {
|
||||||
arr[i] = "\"" + v + "\""
|
arr[i] = "\"" + v + "\""
|
Loading…
x
Reference in New Issue
Block a user