diff --git a/_embed/templates/admin-search/index.html b/_embed/templates/admin-search/index.html new file mode 100644 index 00000000..80cbc11f --- /dev/null +++ b/_embed/templates/admin-search/index.html @@ -0,0 +1,11 @@ +{{- $parent := .QueryParam "parent" -}} +{{- $name := .QueryParam "name" -}} +{{- $stateID := .QueryParam "stateId" -}} + +
+ + {{template "menubar" .}} + + + +
diff --git a/_embed/templates/admin-search/template.hjson b/_embed/templates/admin-search/template.hjson new file mode 100644 index 00000000..4bbb144a --- /dev/null +++ b/_embed/templates/admin-search/template.hjson @@ -0,0 +1,13 @@ +{ + templateId:admin-search + templateRole:admin + model:search + extends: ["admin-common"] + containedBy:["admin"] + label:Search + description: Manage Search Engine Settings + + actions: { + index: {do:"view-html"} + } +} diff --git a/consumer/consumer.go b/consumer/consumer.go index 63bce22e..895ca399 100644 --- a/consumer/consumer.go +++ b/consumer/consumer.go @@ -28,6 +28,9 @@ func (consumer Consumer) Run(name string, args map[string]any) queue.Result { case "CreateWebSubFollower": return WithFactory(consumer.serverFactory, args, CreateWebSubFollower) + case "IndexAllStreams": + return WithFactory(consumer.serverFactory, args, IndexAllStreams) + case "MakeStreamArchive": return WithStream(consumer.serverFactory, args, MakeStreamArchive) diff --git a/consumer/indexAllStreams.go b/consumer/indexAllStreams.go new file mode 100644 index 00000000..2b64a8a5 --- /dev/null +++ b/consumer/indexAllStreams.go @@ -0,0 +1,37 @@ +package consumer + +import ( + "github.com/EmissarySocial/emissary/domain" + "github.com/benpate/derp" + "github.com/benpate/remote" + "github.com/benpate/rosetta/mapof" + "github.com/benpate/turbine/queue" + "github.com/rs/zerolog/log" +) + +func IndexAllStreams(factory *domain.Factory, args mapof.Any) queue.Result { + + const location = "consumer.IndexAllStreams" + + streamService := factory.Stream() + + allStreams, err := streamService.RangeAll() + + if err != nil { + return queue.Error(derp.Wrap(err, location, "Error retrieving Streams")) + } + + for summary := range allStreams { + + log.Debug().Str("url", summary.URL).Msg("Indexing Stream") + transaction := remote.Post(summary.URL + "/search-index") + + if err := transaction.Send(); err != nil { + if !derp.IsClientError(err) { + return queue.Error(derp.Wrap(err, location, "Error sending request")) + } + } + } + + return queue.Success() +} diff --git a/handler/admin.go b/handler/admin.go index 17c74dc4..49075b26 100644 --- a/handler/admin.go +++ b/handler/admin.go @@ -92,6 +92,9 @@ func buildAdmin_GetBuilder(factory *domain.Factory, ctx *steranko.Context, templ case "domain": return build.NewDomain(factory, ctx.Request(), ctx.Response(), template, actionID) + case "search": + return build.NewDomain(factory, ctx.Request(), ctx.Response(), template, actionID) + case "syndication": return build.NewSyndication(factory, ctx.Request(), ctx.Response(), template, actionID) diff --git a/handler/search.go b/handler/search.go new file mode 100644 index 00000000..6d8c735e --- /dev/null +++ b/handler/search.go @@ -0,0 +1,36 @@ +package handler + +import ( + "net/http" + + "github.com/EmissarySocial/emissary/domain" + "github.com/benpate/derp" + "github.com/benpate/rosetta/mapof" + "github.com/benpate/steranko" + "github.com/benpate/turbine/queue" +) + +// IndexAllStreams is a handler function that triggers the IndexAllStreams queue task. +// It can only be called by an authenticated administrator. +func IndexAllStreams(ctx *steranko.Context, factory *domain.Factory) error { + + // Verify that this is an Administrator + authorization := getAuthorization(ctx) + + if !authorization.DomainOwner { + return derp.NewForbiddenError("handler.IndexAllStreams", "Only administrators can call this method") + } + + // Create the Index task + task := queue.NewTask("IndexAllStreams", mapof.Any{ + "host": ctx.Request().Host, + }) + + // Execute the task in the background + if err := factory.Queue().Publish(task); err != nil { + return derp.Wrap(err, "handler.IndexAllStreams", "Error publishing task") + } + + // Success. + return ctx.NoContent(http.StatusOK) +} diff --git a/server.go b/server.go index 2735da87..d417528c 100644 --- a/server.go +++ b/server.go @@ -344,6 +344,7 @@ func makeStandardRoutes(factory *server.Factory, e *echo.Echo) { e.POST("/admin/:param1/:param2", handler.PostAdmin(factory), mw.Owner) e.GET("/admin/:param1/:param2/:param3", handler.GetAdmin(factory), mw.Owner) e.POST("/admin/:param1/:param2/:param3", handler.PostAdmin(factory), mw.Owner) + e.POST("/admin/index-all-streams", handler.WithFactory(factory, handler.IndexAllStreams), mw.Owner) // OAuth Client Connections e.GET("/oauth/clients/:provider", handler.GetOAuth(factory), mw.Owner) diff --git a/service/follower.go b/service/follower.go index c86badd6..f0e2e94c 100644 --- a/service/follower.go +++ b/service/follower.go @@ -77,6 +77,7 @@ func (service *Follower) List(criteria exp.Expression, options ...option.Option) return service.collection.Iterator(notDeleted(criteria), options...) } +// Range returns a Go 1.23 RangeFunc that iterates over the Followers who match the provided criteria func (service *Follower) Range(criteria exp.Expression, options ...option.Option) (iter.Seq[model.Follower], error) { iter, err := service.List(criteria, options...) diff --git a/service/stream.go b/service/stream.go index 586b0bb3..506b94b0 100644 --- a/service/stream.go +++ b/service/stream.go @@ -2,6 +2,7 @@ package service import ( "context" + "iter" "net/url" "strings" "time" @@ -177,6 +178,30 @@ func (service *Stream) QuerySummary(criteria exp.Expression, options ...option.O return result, err } +// Range returns a Go 1.23 RangeFunc that iterates over the Streams that match the provided criteria +func (service *Stream) Range(criteria exp.Expression, options ...option.Option) (iter.Seq[model.Stream], error) { + + iter, err := service.List(criteria, options...) + + if err != nil { + return nil, derp.Wrap(err, "service.Stream.Range", "Error creating iterator", criteria) + } + + return RangeFunc(iter, model.NewStream), nil +} + +// RangeSummary returns a Go 1.23 RangeFunc that iterates over the Stream Summaries that match the provided criteria +func (service *Stream) RangeSummary(criteria exp.Expression, options ...option.Option) (iter.Seq[model.StreamSummary], error) { + + iter, err := service.List(criteria, options...) + + if err != nil { + return nil, derp.Wrap(err, "service.Stream.Range", "Error creating iterator", criteria) + } + + return RangeFunc(iter, model.NewStreamSummary), nil +} + // List returns an iterator containing all of the Streams that match the provided criteria func (service *Stream) List(criteria exp.Expression, options ...option.Option) (data.Iterator, error) { return service.collection.Iterator(notDeleted(criteria), options...) @@ -422,6 +447,10 @@ func (service *Stream) Schema() schema.Schema { * Custom Queries ******************************************/ +func (service *Stream) RangeAll() (iter.Seq[model.StreamSummary], error) { + return service.RangeSummary(exp.All()) +} + // ListNavigation returns all Streams of type FOLDER at the top of the hierarchy func (service *Stream) ListNavigation() (data.Iterator, error) { return service.List(