diff --git a/_embed/templates/admin-connections/index.html b/_embed/templates/admin-connections/index.html index c57b12865..b60db3987 100644 --- a/_embed/templates/admin-connections/index.html +++ b/_embed/templates/admin-connections/index.html @@ -11,9 +11,9 @@
- {{- $renderer := . -}} + {{- $builder := . -}} {{- range .Providers -}} - {{ $client := $renderer.Client .Value }} + {{ $client := $builder.Client .Value }} {{- if eq .Group "OAUTH" -}}
diff --git a/_embed/templates/stream-outbox-message/replies.html b/_embed/templates/stream-outbox-message/replies.html index d2f5c256d..e2d7b0f5c 100644 --- a/_embed/templates/stream-outbox-message/replies.html +++ b/_embed/templates/stream-outbox-message/replies.html @@ -1,7 +1,7 @@ {{- $publishedDate := .QueryParam "published" -}} {{- $pageSize := 12 -}} -{{- $renderer := . -}} +{{- $builder := . -}} {{- $replies := .RepliesBefore $publishedDate $pageSize -}} {{- if eq $replies.Length $pageSize -}} @@ -71,8 +71,8 @@
- {{- if $renderer.IsAuthenticated -}} - {{- if $renderer.NotMe $object.Actor.ID -}} + {{- if $builder.IsAuthenticated -}} + {{- if $builder.NotMe $object.Actor.ID -}}
+ {{- $postTo := .GetString "postTo" | addQueryParams "templateId=outbox-reply" -}}
{{- if not $following.IsNew -}} - {{- $subRenderer := .SubRenderer $following -}} - {{- template "actor-button-follow" $subRenderer -}} + {{- $subBuilder := .SubBuilder $following -}} + {{- template "actor-button-follow" $subBuilder -}} {{- else -}} {{- $rule := .HasRule "ACTOR" $url -}} @@ -13,12 +13,12 @@ {{- template "actor-button-none" . -}} {{- else if eq "MUTE" $rule.Action -}} - {{- $subRenderer := .SubRenderer $rule -}} - {{- template "actor-button-mute" $subRenderer -}} + {{- $subBuilder := .SubBuilder $rule -}} + {{- template "actor-button-mute" $subBuilder -}} {{- else if eq "BLOCK" $rule.Action -}} - {{- $subRenderer := .SubRenderer $rule -}} - {{- template "actor-button-block" $subRenderer -}} + {{- $subBuilder := .SubBuilder $rule -}} + {{- template "actor-button-block" $subBuilder -}} {{- else -}} {{- template "actor-button-none" . -}} diff --git a/_embed/templates/user-inbox/list-before.html b/_embed/templates/user-inbox/list-before.html index 751093da4..9b4be7bc6 100644 --- a/_embed/templates/user-inbox/list-before.html +++ b/_embed/templates/user-inbox/list-before.html @@ -9,7 +9,7 @@ {{- $internalID := .QueryParam "origin.followingId" -}} {{- $layout := $folder.Layout | lowerCase -}} - {{- $inboxRenderer := . -}} + {{- $inboxBuilder := . -}} {{- if eq 12 (len $inbox) -}}
{{- if eq "SOCIAL" $folder.Layout -}} - {{- template "list-social" (array $inboxRenderer $message) -}} + {{- template "list-social" (array $inboxBuilder $message) -}} {{- else if eq "NEWSPAPER" $folder.Layout -}} - {{- template "list-newspaper" (array $inboxRenderer $message) -}} + {{- template "list-newspaper" (array $inboxBuilder $message) -}} {{- else if eq "MAGAZINE" $folder.Layout -}} - {{- template "list-magazine" (array $inboxRenderer $message) -}} + {{- template "list-magazine" (array $inboxBuilder $message) -}} {{- else -}} - {{- template "list-social" (array $inboxRenderer $message) -}} + {{- template "list-social" (array $inboxBuilder $message) -}} {{- end -}}
diff --git a/_embed/templates/user-inbox/list-magazine.html b/_embed/templates/user-inbox/list-magazine.html index 4841c5bfd..6e9ec0ca0 100644 --- a/_embed/templates/user-inbox/list-magazine.html +++ b/_embed/templates/user-inbox/list-magazine.html @@ -1,6 +1,6 @@ -{{- $inboxRenderer := index . 0 -}} +{{- $inboxBuilder := index . 0 -}} {{- $message := index . 1 -}} -{{- $stream := $inboxRenderer.ActivityStream $message.URL -}} +{{- $stream := $inboxBuilder.ActivityStream $message.URL -}} {{- $image := $stream.ImageOrIcon -}}
diff --git a/_embed/templates/user-inbox/list-newspaper.html b/_embed/templates/user-inbox/list-newspaper.html index 8e0cc233a..7d5dc7f12 100644 --- a/_embed/templates/user-inbox/list-newspaper.html +++ b/_embed/templates/user-inbox/list-newspaper.html @@ -1,6 +1,6 @@ -{{- $inboxRenderer := index . 0 -}} +{{- $inboxBuilder := index . 0 -}} {{- $message := index . 1 -}} -{{- $stream := $inboxRenderer.ActivityStream $message.URL -}} +{{- $stream := $inboxBuilder.ActivityStream $message.URL -}} {{- $image := $stream.IconOrImage -}}
diff --git a/_embed/templates/user-inbox/list-social.html b/_embed/templates/user-inbox/list-social.html index fe0b2d093..41399c34e 100644 --- a/_embed/templates/user-inbox/list-social.html +++ b/_embed/templates/user-inbox/list-social.html @@ -1,6 +1,6 @@ -{{- $inboxRenderer := index . 0 -}} +{{- $inboxBuilder := index . 0 -}} {{- $message := index . 1 -}} -{{- $stream := $inboxRenderer.ActivityStream $message.URL -}} +{{- $stream := $inboxBuilder.ActivityStream $message.URL -}} {{- $image := $stream.ImageOrIcon -}} {{- $attributedTo := $stream.AttributedTo -}} diff --git a/_embed/templates/user-inbox/list.html b/_embed/templates/user-inbox/list.html index 9f7535eb4..27b93c43c 100644 --- a/_embed/templates/user-inbox/list.html +++ b/_embed/templates/user-inbox/list.html @@ -28,7 +28,7 @@ {{- if gt (len $inbox) 0 -}} - {{- $inboxRenderer := . -}} + {{- $inboxBuilder := . -}} {{- range $index, $message := $inbox -}}
{{- if eq "SOCIAL" $folder.Layout -}} - {{- template "list-social" (array $inboxRenderer $message) -}} + {{- template "list-social" (array $inboxBuilder $message) -}} {{- else if eq "NEWSPAPER" $folder.Layout -}} - {{- template "list-newspaper" (array $inboxRenderer $message) -}} + {{- template "list-newspaper" (array $inboxBuilder $message) -}} {{- else if eq "MAGAZINE" $folder.Layout -}} - {{- template "list-magazine" (array $inboxRenderer $message) -}} + {{- template "list-magazine" (array $inboxBuilder $message) -}} {{- else -}} - {{- template "list-social" (array $inboxRenderer $message) -}} + {{- template "list-social" (array $inboxBuilder $message) -}} {{- end -}}
diff --git a/_embed/templates/user-inbox/responses-replies-list.html b/_embed/templates/user-inbox/responses-replies-list.html index dbb67b477..d7f2f9161 100644 --- a/_embed/templates/user-inbox/responses-replies-list.html +++ b/_embed/templates/user-inbox/responses-replies-list.html @@ -5,7 +5,7 @@ {{- $publishedDate := .QueryParam "published" -}} {{- $pageSize := 4 -}} -{{- $renderer := . -}} +{{- $builder := . -}} {{- $replies := .RepliesBefore $url $publishedDate $pageSize -}} @@ -46,7 +46,7 @@ {{- if $actor.NotNil -}} - {{- if $renderer.IsMe $object.Actor.ID -}} + {{- if $builder.IsMe $object.Actor.ID -}} {{icon "more-horizontal"}} {{- else -}} {{- if $object.Actor.NotNil -}} - {{- if $renderer.IsMe $object.Actor.ID -}} + {{- if $builder.IsMe $object.Actor.ID -}} {{icon "more-horizontal"}} {{- else -}}
@@ -26,7 +26,7 @@
{{$stream.PublishDate | humanizeTime}}
- {{- if $outboxRenderer.UserCan "edit" -}} + {{- if $outboxBuilder.UserCan "edit" -}}
@@ -34,7 +34,7 @@
{{- end -}} -
+
Loading recent posts...
diff --git a/_embed/templates/user-outbox/replied-list.html b/_embed/templates/user-outbox/replied-list.html index f969b1742..90817438b 100644 --- a/_embed/templates/user-outbox/replied-list.html +++ b/_embed/templates/user-outbox/replied-list.html @@ -1,4 +1,4 @@ -{{- $outboxRenderer := . -}} +{{- $outboxBuilder := . -}} {{- $outbox := .Replies.Top12.ByPublishDate.Reverse.Slice -}} {{- if ne 0 (len $outbox) -}} @@ -6,7 +6,7 @@ {{- $isMyself := .IsMyself -}} {{- range $index, $stream := $outbox -}} - {{- $document := $outboxRenderer.ActivityStream $stream.URL -}} + {{- $document := $outboxBuilder.ActivityStream $stream.URL -}}
@@ -26,7 +26,7 @@
{{$stream.PublishDate | humanizeTime}}
- {{- if $outboxRenderer.UserCan "edit" -}} + {{- if $outboxBuilder.UserCan "edit" -}}
@@ -34,7 +34,7 @@
{{- end -}} -
+
Loading recent posts...
diff --git a/render/render.go b/build/build.go similarity index 68% rename from render/render.go rename to build/build.go index ba6c84fb7..10675c60d 100644 --- a/render/render.go +++ b/build/build.go @@ -1,12 +1,12 @@ /* -Package render contains render objects, which are passed to HTML templates +Package build contains build objects, which are passed to HTML templates to generate HTML pages. Render objects wrap a specific model object, providing some safety against direct access to protected data. Render objects also include additional methods to query related records in the -database. For example, the "Stream" renderer has queries for `Ancestors`, +database. For example, the "Stream" builder has queries for `Ancestors`, `Parent`, `Siblings`, and `Children` streams. This package also contains implementations for all the action steps available to template designers. */ -package render +package build diff --git a/render/renderer_.go b/build/builder_.go similarity index 60% rename from render/renderer_.go rename to build/builder_.go index 3372e0bfa..424327d6a 100644 --- a/render/renderer_.go +++ b/build/builder_.go @@ -1,4 +1,4 @@ -package render +package build import ( "html/template" @@ -13,10 +13,10 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Renderer safely wraps model objects for consumption by an html Template -type Renderer interface { +// Builder safely wraps model objects for consumption by an html Template +type Builder interface { - // Render is the main entry-point for templates to use a Renderer + // Render is the main entry-point for templates to use a Builder Render() (template.HTML, error) // Render function outputs an HTML template View(string) (template.HTML, error) // Render function outputs an HTML template @@ -24,17 +24,17 @@ type Renderer interface { Host() string // String representation of the protocol + hostname Protocol() string // String representation of the HTTP protocol to use when addressing this record (http:// or https://) Hostname() string // Hostname for this server - Token() string // URL Token of the record being rendered + Token() string // URL Token of the record being builded NavigationID() string // ID of the Top-Level item to highlight in the navigation. PageTitle() string // Human-friendly title to put at the top of the page. Summary() string // Human-friendly summary to put at the top of the page (maybe) - Permalink() string // Permanent link to the record being rendered + Permalink() string // Permanent link to the record being builded BasePath() string // URL Path of the root of this object, without any additional actions. URL() string // Complete URL of the requested page QueryParam(string) string // Query parameter of the requested page SetQueryParam(string, string) // Sets a queryString parameter ActionID() string // Token that identifies the action requested via the URL. - Action() model.Action // The pipeline action to be taken by this renderer + Action() model.Action // The pipeline action to be taken by this builder IsAuthenticated() bool // Returns TRUE if the user is signed in IsPartialRequest() bool // Returns TRUE if this is an HTMX request for a page fragment UserCan(string) bool // Returns TRUE if the signed-in user has access to the named action @@ -52,21 +52,21 @@ type Renderer interface { GetContent() template.HTML SetContent(string) - factory() Factory // The service factory - request() *http.Request // The original http.Request that we are responding to - response() http.ResponseWriter // The original http.ResponseWriter that we are responding to - authorization() model.Authorization // The user's authorization data from the context - service() service.ModelService // The abstracted ModelService the backs this Renderer - templateRole() string // Returns the role that the current template plays in the system. Used for choosing child template. - template() model.Template // The template used for this renderer (if any) - objectType() string // The type of object being rendered - schema() schema.Schema // Schema to use to validate this Object - object() data.Object // Model Object being rendered - objectID() primitive.ObjectID // MongoDB ObjectID of the Object being rendered - getUser() (model.User, error) // Retrieves the currently-logged-in user - lookupProvider() form.LookupProvider // Retrieves the LookupProvider for this user - debug() // Outputs debug information to the console - clone(action string) (Renderer, error) // Creates a new Renderer with the same type and object, but a different action + factory() Factory // The service factory + request() *http.Request // The original http.Request that we are responding to + response() http.ResponseWriter // The original http.ResponseWriter that we are responding to + authorization() model.Authorization // The user's authorization data from the context + service() service.ModelService // The abstracted ModelService the backs this Builder + templateRole() string // Returns the role that the current template plays in the system. Used for choosing child template. + template() model.Template // The template used for this builder (if any) + objectType() string // The type of object being builded + schema() schema.Schema // Schema to use to validate this Object + object() data.Object // Model Object being builded + objectID() primitive.ObjectID // MongoDB ObjectID of the Object being builded + getUser() (model.User, error) // Retrieves the currently-logged-in user + lookupProvider() form.LookupProvider // Retrieves the LookupProvider for this user + debug() // Outputs debug information to the console + clone(action string) (Builder, error) // Creates a new Builder with the same type and object, but a different action - executeTemplate(io.Writer, string, any) error // The HTML template used by this Renderer + executeTemplate(io.Writer, string, any) error // The HTML template used by this Builder } diff --git a/render/renderer_admin_block.go b/build/builder_admin_block.go similarity index 86% rename from render/renderer_admin_block.go rename to build/builder_admin_block.go index 7e0b42563..3bdeca285 100644 --- a/render/renderer_admin_block.go +++ b/build/builder_admin_block.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -17,23 +17,23 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Rule is a renderer for the admin/rules page +// Rule is a builder for the admin/rules page // It can only be accessed by a Domain Owner type Rule struct { _rule *model.Rule Common } -// NewRule returns a fully initialized `Rule` renderer. +// NewRule returns a fully initialized `Rule` builder. func NewRule(factory Factory, request *http.Request, response http.ResponseWriter, rule *model.Rule, template model.Template, actionID string) (Rule, error) { - const location = "render.NewRule" + const location = "build.NewRule" - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Rule{}, derp.Wrap(err, location, "Error creating common renderer") + return Rule{}, derp.Wrap(err, location, "Error creating common builder") } // Verify that the user is a Domain Owner @@ -41,7 +41,7 @@ func NewRule(factory Factory, request *http.Request, response http.ResponseWrite return Rule{}, derp.NewForbiddenError(location, "Must be domain owner to continue") } - // Return the Rule renderer + // Return the Rule builder return Rule{ _rule: rule, Common: common, @@ -61,7 +61,7 @@ func (w Rule) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w._factory, &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.Rule.Render", "Error generating HTML") + err := derp.Wrap(status.Error, "build.Rule.Render", "Error generating HTML") derp.Report(err) return "", err } @@ -74,15 +74,15 @@ func (w Rule) Render() (template.HTML, error) { // View executes a separate view for this Rule func (w Rule) View(actionID string) (template.HTML, error) { - const location = "render.Rule.View" + const location = "build.Rule.View" - renderer, err := NewRule(w._factory, w._request, w._response, w._rule, w._template, actionID) + builder, err := NewRule(w._factory, w._request, w._response, w._rule, w._template, actionID) if err != nil { - return template.HTML(""), derp.Wrap(err, location, "Error creating Rule renderer") + return template.HTML(""), derp.Wrap(err, location, "Error creating Rule builder") } - return renderer.Render() + return builder.Render() } func (w Rule) NavigationID() string { @@ -129,7 +129,7 @@ func (w Rule) executeTemplate(writer io.Writer, name string, data any) error { return w._template.HTMLTemplate.ExecuteTemplate(writer, name, data) } -func (w Rule) clone(action string) (Renderer, error) { +func (w Rule) clone(action string) (Builder, error) { return NewRule(w._factory, w._request, w._response, w._rule, w._template, action) } @@ -192,5 +192,5 @@ func (w Rule) ServerWideRules() *QueryBuilder[model.Rule] { } func (w Rule) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_admin_rule") + log.Debug().Interface("object", w.object()).Msg("builder_admin_rule") } diff --git a/render/renderer_admin_domain.go b/build/builder_admin_domain.go similarity index 87% rename from render/renderer_admin_domain.go rename to build/builder_admin_domain.go index 984dc9574..63e5ed3ca 100644 --- a/render/renderer_admin_domain.go +++ b/build/builder_admin_domain.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -21,7 +21,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Domain is the renderer for the admin/domain page +// Domain is the builder for the admin/domain page // It can only be accessed by a Domain Owner type Domain struct { _provider *service.Provider @@ -29,16 +29,16 @@ type Domain struct { Common } -// NewDomain returns a fully initialized `Domain` renderer. +// NewDomain returns a fully initialized `Domain` builder. func NewDomain(factory Factory, request *http.Request, response http.ResponseWriter, template model.Template, actionID string) (Domain, error) { - const location = "render.NewDomain" + const location = "build.NewDomain" - // Create the underlying common renderer + // Create the underlying common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Domain{}, derp.Wrap(err, location, "Error creating common renderer") + return Domain{}, derp.Wrap(err, location, "Error creating common builder") } // Verify that the user is a Domain Owner @@ -46,7 +46,7 @@ func NewDomain(factory Factory, request *http.Request, response http.ResponseWri return Domain{}, derp.NewForbiddenError(location, "Must be domain owner to continue") } - // Create and return the Domain renderer + // Create and return the Domain builder result := Domain{ _provider: factory.Provider(), Common: common, @@ -75,7 +75,7 @@ func (w Domain) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w._factory, &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.Domain.Render", "Error generating HTML") + err := derp.Wrap(status.Error, "build.Domain.Render", "Error generating HTML") derp.Report(err) return "", err } @@ -88,15 +88,15 @@ func (w Domain) Render() (template.HTML, error) { // View executes a separate view for this Group func (w Domain) View(actionID string) (template.HTML, error) { - const location = "render.Domain.View" + const location = "build.Domain.View" - renderer, err := NewDomain(w._factory, w._request, w._response, w._template, actionID) + builder, err := NewDomain(w._factory, w._request, w._response, w._template, actionID) if err != nil { - return template.HTML(""), derp.Wrap(err, location, "Error creating Group renderer") + return template.HTML(""), derp.Wrap(err, location, "Error creating Group builder") } - return renderer.Render() + return builder.Render() } func (w Domain) Token() string { @@ -149,7 +149,7 @@ func (w Domain) PageTitle() string { return "Settings" } -func (w Domain) clone(action string) (Renderer, error) { +func (w Domain) clone(action string) (Builder, error) { return NewDomain(w._factory, w._request, w._response, w._template, action) } @@ -214,5 +214,5 @@ func (w Domain) Provider(providerID string) providers.Provider { return result } func (w Domain) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_admin_domain") + log.Debug().Interface("object", w.object()).Msg("builder_admin_domain") } diff --git a/render/renderer_admin_groups.go b/build/builder_admin_groups.go similarity index 84% rename from render/renderer_admin_groups.go rename to build/builder_admin_groups.go index 2c5bb09e1..0ee4299fd 100644 --- a/render/renderer_admin_groups.go +++ b/build/builder_admin_groups.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -17,23 +17,23 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Group is a renderer for the admin/groups page +// Group is a builder for the admin/groups page // It can only be accessed by a Domain Owner type Group struct { _group *model.Group Common } -// NewGroup returns a fully initialized `Group` renderer. +// NewGroup returns a fully initialized `Group` builder. func NewGroup(factory Factory, request *http.Request, response http.ResponseWriter, template model.Template, group *model.Group, actionID string) (Group, error) { - const location = "render.NewGroup" + const location = "build.NewGroup" - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Group{}, derp.Wrap(err, location, "Error creating common renderer") + return Group{}, derp.Wrap(err, location, "Error creating common builder") } // Verify that the user is a Domain Owner @@ -41,7 +41,7 @@ func NewGroup(factory Factory, request *http.Request, response http.ResponseWrit return Group{}, derp.NewForbiddenError(location, "Must be domain owner to continue") } - // Return the Group renderer + // Return the Group builder return Group{ _group: group, Common: common, @@ -61,7 +61,7 @@ func (w Group) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w._factory, &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.Group.Render", "Error generating HTML") + err := derp.Wrap(status.Error, "build.Group.Render", "Error generating HTML") derp.Report(err) return "", err } @@ -74,15 +74,15 @@ func (w Group) Render() (template.HTML, error) { // View executes a separate view for this Group func (w Group) View(actionID string) (template.HTML, error) { - const location = "render.Group.View" + const location = "build.Group.View" - renderer, err := NewGroup(w._factory, w._request, w._response, w._template, w._group, actionID) + builder, err := NewGroup(w._factory, w._request, w._response, w._template, w._group, actionID) if err != nil { - return template.HTML(""), derp.Wrap(err, location, "Error creating Group renderer") + return template.HTML(""), derp.Wrap(err, location, "Error creating Group builder") } - return renderer.Render() + return builder.Render() } func (w Group) NavigationID() string { @@ -132,7 +132,7 @@ func (w Group) executeTemplate(writer io.Writer, name string, data any) error { return w._template.HTMLTemplate.ExecuteTemplate(writer, name, data) } -func (w Group) clone(action string) (Renderer, error) { +func (w Group) clone(action string) (Builder, error) { return NewGroup(w._factory, w._request, w._response, w._template, w._group, action) } @@ -175,5 +175,5 @@ func (w Group) Groups() *QueryBuilder[model.Group] { } func (w Group) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_admin_group") + log.Debug().Interface("object", w.object()).Msg("builder_admin_group") } diff --git a/render/renderer_admin_navigation.go b/build/builder_admin_navigation.go similarity index 79% rename from render/renderer_admin_navigation.go rename to build/builder_admin_navigation.go index f276ff350..375f734f2 100644 --- a/render/renderer_admin_navigation.go +++ b/build/builder_admin_navigation.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -16,23 +16,23 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Navigation is a renderer for the admin/navigation page +// Navigation is a builder for the admin/navigation page // It can only be accessed by a Domain Owner type Navigation struct { _stream *model.Stream Common } -// NewNavigation returns a fully initialized `Navigation` renderer. +// NewNavigation returns a fully initialized `Navigation` builder. func NewNavigation(factory Factory, request *http.Request, response http.ResponseWriter, template model.Template, stream *model.Stream, actionID string) (Navigation, error) { - const location = "render.NewGroup" + const location = "build.NewGroup" - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Navigation{}, derp.Wrap(err, location, "Error creating common renderer") + return Navigation{}, derp.Wrap(err, location, "Error creating common builder") } // Verify that the user is a Domain Owner @@ -40,7 +40,7 @@ func NewNavigation(factory Factory, request *http.Request, response http.Respons return Navigation{}, derp.NewForbiddenError(location, "Must be domain owner to continue") } - // Return the Navigation renderer + // Return the Navigation builder return Navigation{ Common: common, }, nil @@ -59,7 +59,7 @@ func (w Navigation) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w.factory(), &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.Navigation.Render", "Error generating HTML") + err := derp.Wrap(status.Error, "build.Navigation.Render", "Error generating HTML") derp.Report(err) return "", err } @@ -72,15 +72,15 @@ func (w Navigation) Render() (template.HTML, error) { // View executes a separate view for this Group func (w Navigation) View(actionID string) (template.HTML, error) { - const location = "render.Navigation.View" + const location = "build.Navigation.View" - renderer, err := NewNavigation(w.factory(), w._request, w._response, w._template, w._stream, actionID) + builder, err := NewNavigation(w.factory(), w._request, w._response, w._template, w._stream, actionID) if err != nil { - return template.HTML(""), derp.Wrap(err, location, "Error creating Group renderer") + return template.HTML(""), derp.Wrap(err, location, "Error creating Group builder") } - return renderer.Render() + return builder.Render() } func (w Navigation) Token() string { @@ -127,10 +127,10 @@ func (w Navigation) executeTemplate(wr io.Writer, name string, data any) error { return w._template.HTMLTemplate.ExecuteTemplate(wr, name, data) } -func (w Navigation) clone(action string) (Renderer, error) { +func (w Navigation) clone(action string) (Builder, error) { return NewNavigation(w._factory, w._request, w._response, w._template, w._stream, action) } func (w Navigation) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_admin_avigation") + log.Debug().Interface("object", w.object()).Msg("builder_admin_avigation") } diff --git a/render/renderer_admin_users.go b/build/builder_admin_users.go similarity index 84% rename from render/renderer_admin_users.go rename to build/builder_admin_users.go index db8b9c483..e171d45ec 100644 --- a/render/renderer_admin_users.go +++ b/build/builder_admin_users.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -17,23 +17,23 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// User is a renderer for the admin/users page +// User is a builder for the admin/users page // It can only be accessed by a Domain Owner type User struct { _user *model.User Common } -// NewUser returns a fully initialized `User` renderer. +// NewUser returns a fully initialized `User` builder. func NewUser(factory Factory, request *http.Request, response http.ResponseWriter, template model.Template, user *model.User, actionID string) (User, error) { - const location = "render.NewGroup" + const location = "build.NewGroup" - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return User{}, derp.Wrap(err, location, "Error creating common renderer") + return User{}, derp.Wrap(err, location, "Error creating common builder") } // Verify that the user is a Domain Owner @@ -41,7 +41,7 @@ func NewUser(factory Factory, request *http.Request, response http.ResponseWrite return User{}, derp.NewForbiddenError(location, "Must be domain owner to continue") } - // Return the User renderer + // Return the User builder return User{ _user: user, Common: common, @@ -61,7 +61,7 @@ func (w User) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w.factory(), &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.User.Render", "Error generating HTML") + err := derp.Wrap(status.Error, "build.User.Render", "Error generating HTML") derp.Report(err) return "", err } @@ -74,13 +74,13 @@ func (w User) Render() (template.HTML, error) { // View executes a separate view for this User func (w User) View(actionID string) (template.HTML, error) { - renderer, err := NewUser(w._factory, w._request, w._response, w._template, w._user, actionID) + builder, err := NewUser(w._factory, w._request, w._response, w._template, w._user, actionID) if err != nil { - return template.HTML(""), derp.Wrap(err, "render.User.View", "Error creating renderer") + return template.HTML(""), derp.Wrap(err, "build.User.View", "Error creating builder") } - return renderer.Render() + return builder.Render() } func (w User) NavigationID() string { @@ -127,7 +127,7 @@ func (w User) executeTemplate(writer io.Writer, name string, data any) error { return w._template.HTMLTemplate.ExecuteTemplate(writer, name, data) } -func (w User) clone(action string) (Renderer, error) { +func (w User) clone(action string) (Builder, error) { return NewUser(w._factory, w._request, w._response, w._template, w._user, action) } @@ -200,9 +200,9 @@ func (w User) AssignedGroups() ([]model.Group, error) { groupService := w.factory().Group() result, err := groupService.ListByIDs(w._user.GroupIDs...) - return result, derp.Wrap(err, "render.User.AssignedGroups", "Error listing groups", w._user.GroupIDs) + return result, derp.Wrap(err, "build.User.AssignedGroups", "Error listing groups", w._user.GroupIDs) } func (w User) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_admin_users") + log.Debug().Interface("object", w.object()).Msg("builder_admin_users") } diff --git a/render/renderer_common.go b/build/builder_common.go similarity index 90% rename from render/renderer_common.go rename to build/builder_common.go index c9902febc..4517e06db 100644 --- a/render/renderer_common.go +++ b/build/builder_common.go @@ -1,4 +1,4 @@ -package render +package build import ( "html/template" @@ -21,13 +21,13 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Common provides common rendering functions that are needed by ALL renderers +// Common provides common building functions that are needed by ALL builders type Common struct { _factory Factory // Factory interface is required for locating other services. _request *http.Request // Pointer to the HTTP request we are serving _response http.ResponseWriter // ResponseWriter for this request _authorization model.Authorization // Authorization information for the current user - _template model.Template // Template to use for this renderer + _template model.Template // Template to use for this builder action model.Action // Action to be performed on the Template actionID string // Token that identifies the action requested in the URL @@ -39,7 +39,7 @@ type Common struct { func NewCommon(factory Factory, request *http.Request, response http.ResponseWriter, template model.Template, actionID string) (Common, error) { - const location = "render.NewCommon" + const location = "build.NewCommon" // Retrieve the user's authorization information steranko := factory.Steranko() @@ -52,7 +52,7 @@ func NewCommon(factory Factory, request *http.Request, response http.ResponseWri return Common{}, derp.NewBadRequestError(location, "Invalid action", actionID) } - // Return a new Common renderer + // Return a new Common builder return Common{ _factory: factory, _request: request, @@ -67,10 +67,10 @@ func NewCommon(factory Factory, request *http.Request, response http.ResponseWri } /****************************************** - * Renderer Interface + * Builder Interface ******************************************/ -// context returns request context embedded in this renderer. +// context returns request context embedded in this builder. func (w Common) factory() Factory { return w._factory } @@ -87,7 +87,7 @@ func (w Common) authorization() model.Authorization { return w._authorization } -// Action returns the model.Action configured into this renderer +// Action returns the model.Action configured into this builder func (w Common) Action() model.Action { return w.action } @@ -192,8 +192,8 @@ func (w Common) Now() int64 { } // NavigationID returns the the identifier of the top-most stream in the -// navigation. The "common" renderer just returns a default value that -// other renderers should override. +// navigation. The "common" builder just returns a default value that +// other builders should override. func (w Common) NavigationID() string { return "" } @@ -295,7 +295,7 @@ func (w Common) UserName() (string, error) { user, err := w.getUser() if err != nil { - return "", derp.Wrap(err, "render.Stream.UserName", "Error loading User") + return "", derp.Wrap(err, "build.Stream.UserName", "Error loading User") } return user.DisplayName, nil @@ -306,7 +306,7 @@ func (w Common) UserImage() (string, error) { user, err := w.getUser() if err != nil { - return "", derp.Wrap(err, "render.Stream.UserAvatar", "Error loading User") + return "", derp.Wrap(err, "build.Stream.UserAvatar", "Error loading User") } return user.ActivityPubAvatarURL(), nil @@ -316,12 +316,12 @@ func (w Common) UserImage() (string, error) { * Misc Helper Methods ******************************************/ -// SubRenderer creates a new renderer for a child object. This function works +// SubBuilder creates a new builder for a child object. This function works // with Rule, Folder, Follower, Following, and Stream objects. It will return // an error if the object is not one of those types. -func (w Common) SubRenderer(object any) (Renderer, error) { +func (w Common) SubBuilder(object any) (Builder, error) { - var result Renderer + var result Builder var err error switch typed := object.(type) { @@ -342,11 +342,11 @@ func (w Common) SubRenderer(object any) (Renderer, error) { result, err = NewModel(w._factory, w._request, w._response, &typed, w._template, w.actionID) default: - result, err = nil, derp.NewInternalError("render.Common.SubRenderer", "Invalid object type", object) + result, err = nil, derp.NewInternalError("build.Common.SubBuilder", "Invalid object type", object) } if err != nil { - err = derp.Wrap(err, "render.Common.SubRenderer", "Error creating sub-renderer for object", object) + err = derp.Wrap(err, "build.Common.SubBuilder", "Error creating sub-builder for object", object) derp.Report(err) } @@ -362,7 +362,7 @@ func (w Common) ActivityStream(url string) streams.Document { result, err := w._factory.ActivityStream().Load(url) if err != nil { - derp.Report(derp.Wrap(err, "render.Common.ActivityStream", "Error loading ActivityStream")) + derp.Report(derp.Wrap(err, "build.Common.ActivityStream", "Error loading ActivityStream")) } // Search for rules that might add a LABEL to this document. @@ -412,7 +412,7 @@ func (w Common) GetFollowingID(url string) string { result, err := followingService.GetFollowingID(w.AuthenticatedID(), url) if err != nil { - derp.Report(derp.Wrap(err, "render.Common.GetFollowingID", "Error getting following status", url)) + derp.Report(derp.Wrap(err, "build.Common.GetFollowingID", "Error getting following status", url)) return "" } @@ -427,7 +427,7 @@ func (w Common) lookupProvider() form.LookupProvider { func (w Common) executeTemplate(writer io.Writer, name string, data any) error { if err := w._template.HTMLTemplate.ExecuteTemplate(writer, name, data); err != nil { - return derp.Wrap(err, "render.Common.executeTemplate", "Error executing template", name) + return derp.Wrap(err, "build.Common.executeTemplate", "Error executing template", name) } return nil @@ -456,7 +456,7 @@ func (w Common) template() model.Template { return w._template } -// getUser loads/caches the currently-signed-in user to be used by other functions in this renderer +// getUser loads/caches the currently-signed-in user to be used by other functions in this builder func (w Common) getUser() (model.User, error) { userService := w._factory.User() @@ -465,7 +465,7 @@ func (w Common) getUser() (model.User, error) { authorization := getAuthorization(steranko, w._request) if err := userService.LoadByID(authorization.UserID, &result); err != nil { - return model.User{}, derp.Wrap(err, "render.Common.getUser", "Error loading user from database", authorization.UserID) + return model.User{}, derp.Wrap(err, "build.Common.getUser", "Error loading user from database", authorization.UserID) } return result, nil @@ -477,7 +477,7 @@ func (w *Common) getDomain() (model.Domain, error) { domainService := w._factory.Domain() if !domainService.Ready() { - return model.Domain{}, derp.NewInternalError("render.Common.getDomain", "Domain service not ready", nil) + return model.Domain{}, derp.NewInternalError("build.Common.getDomain", "Domain service not ready", nil) } return domainService.Get(), nil diff --git a/render/renderer_inbox.go b/build/builder_inbox.go similarity index 88% rename from render/renderer_inbox.go rename to build/builder_inbox.go index aa3c81da9..b7e265b03 100644 --- a/render/renderer_inbox.go +++ b/build/builder_inbox.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -23,13 +23,13 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Inbox is a renderer for the @user/inbox page +// Inbox is a builder for the @user/inbox page type Inbox struct { _user *model.User Common } -// NewInbox returns a fully initialized `Inbox` renderer +// NewInbox returns a fully initialized `Inbox` builder func NewInbox(factory Factory, request *http.Request, response http.ResponseWriter, user *model.User, actionID string) (Inbox, error) { // Load the Template @@ -37,14 +37,14 @@ func NewInbox(factory Factory, request *http.Request, response http.ResponseWrit template, err := templateService.Load("user-inbox") // TODO: Users should get to select their inbox template if err != nil { - return Inbox{}, derp.Wrap(err, "render.NewInbox", "Error loading template") + return Inbox{}, derp.Wrap(err, "build.NewInbox", "Error loading template") } - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Inbox{}, derp.Wrap(err, "render.NewInbox", "Error creating common renderer") + return Inbox{}, derp.Wrap(err, "build.NewInbox", "Error creating common builder") } return Inbox{ @@ -66,7 +66,7 @@ func (w Inbox) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w._factory, &w, &buffer) if status.Error != nil { - return "", derp.ReportAndReturn(derp.Wrap(status.Error, "render.Inbox.Render", "Error generating HTML", w._request.URL.String())) + return "", derp.ReportAndReturn(derp.Wrap(status.Error, "build.Inbox.Render", "Error generating HTML", w._request.URL.String())) } // Success! @@ -77,13 +77,13 @@ func (w Inbox) Render() (template.HTML, error) { // View executes a separate view for this Inbox func (w Inbox) View(actionID string) (template.HTML, error) { - renderer, err := NewInbox(w._factory, w._request, w._response, w._user, actionID) + builder, err := NewInbox(w._factory, w._request, w._response, w._user, actionID) if err != nil { - return template.HTML(""), derp.ReportAndReturn(derp.Wrap(err, "render.Inbox.View", "Error creating Inbox renderer")) + return template.HTML(""), derp.ReportAndReturn(derp.Wrap(err, "build.Inbox.View", "Error creating Inbox builder")) } - return renderer.Render() + return builder.Render() } // NavigationID returns the ID to use for highlighing navigation menus @@ -140,7 +140,7 @@ func (w Inbox) templateRole() string { return "inbox" } -func (w Inbox) clone(action string) (Renderer, error) { +func (w Inbox) clone(action string) (Builder, error) { return NewInbox(w._factory, w._request, w._response, w._user, action) } @@ -240,14 +240,14 @@ func (w Inbox) FollowingByFolder(token string) ([]model.FollowingSummary, error) userID := w.AuthenticatedID() if userID.IsZero() { - return nil, derp.NewUnauthorizedError("render.Inbox.FollowingByFolder", "Must be signed in to view following") + return nil, derp.NewUnauthorizedError("build.Inbox.FollowingByFolder", "Must be signed in to view following") } // Get the followingID from the token followingID, err := primitive.ObjectIDFromHex(token) if err != nil { - return nil, derp.Wrap(err, "render.Inbox.FollowingByFolder", "Invalid following ID", token) + return nil, derp.Wrap(err, "build.Inbox.FollowingByFolder", "Invalid following ID", token) } // Try to load the matching records @@ -264,7 +264,7 @@ func (w Inbox) FollowingByToken(followingToken string) (model.Following, error) following := model.NewFollowing() if err := followingService.LoadByToken(userID, followingToken, &following); err != nil { - return model.Following{}, derp.Wrap(err, "render.Inbox.FollowingByID", "Error loading following") + return model.Following{}, derp.Wrap(err, "build.Inbox.FollowingByID", "Error loading following") } return following, nil @@ -291,7 +291,7 @@ func (w Inbox) RuleByToken(token string) model.Rule { rule := model.NewRule() if err := ruleService.LoadByToken(w.AuthenticatedID(), token, &rule); err != nil { - derp.Report(derp.Wrap(err, "render.Inbox.RuleByToken", "Error loading rule", token)) + derp.Report(derp.Wrap(err, "build.Inbox.RuleByToken", "Error loading rule", token)) } return rule @@ -303,7 +303,7 @@ func (w Inbox) Inbox() (QueryBuilder[model.Message], error) { userID := w.AuthenticatedID() if userID.IsZero() { - return QueryBuilder[model.Message]{}, derp.NewUnauthorizedError("render.Inbox.Inbox", "Must be signed in to view inbox") + return QueryBuilder[model.Message]{}, derp.NewUnauthorizedError("build.Inbox.Inbox", "Must be signed in to view inbox") } queryString := w._request.URL.Query() @@ -311,7 +311,7 @@ func (w Inbox) Inbox() (QueryBuilder[model.Message], error) { folderID, err := primitive.ObjectIDFromHex(queryString.Get("folderId")) if err != nil { - return QueryBuilder[model.Message]{}, derp.Wrap(err, "render.Inbox.Inbox", "Invalid folderId", queryString.Get("folderId")) + return QueryBuilder[model.Message]{}, derp.Wrap(err, "build.Inbox.Inbox", "Invalid folderId", queryString.Get("folderId")) } if queryString.Get("readDate") == "" { @@ -379,14 +379,14 @@ func (w Inbox) Folders() (model.FolderList, error) { // User must be authenticated to view any folders if !w.IsAuthenticated() { - return result, derp.NewForbiddenError("render.Inbox.Folders", "Not authenticated") + return result, derp.NewForbiddenError("build.Inbox.Folders", "Not authenticated") } folderService := w._factory.Folder() folders, err := folderService.QueryByUserID(w.AuthenticatedID()) if err != nil { - return result, derp.Wrap(err, "render.Inbox.Folders", "Error loading folders") + return result, derp.Wrap(err, "build.Inbox.Folders", "Error loading folders") } result.Folders = folders @@ -399,12 +399,12 @@ func (w Inbox) FoldersWithSelection() (model.FolderList, error) { result, err := w.Folders() if err != nil { - return result, derp.Wrap(err, "render.Inbox.FoldersWithSelection", "Error loading folders") + return result, derp.Wrap(err, "build.Inbox.FoldersWithSelection", "Error loading folders") } // Guarantee that we have at least one folder if len(result.Folders) == 0 { - return result, derp.NewInternalError("render.Inbox.FoldersWithSelection", "No folders found", nil) + return result, derp.NewInternalError("build.Inbox.FoldersWithSelection", "No folders found", nil) } // Find/Mark the Selected FolderID @@ -438,7 +438,7 @@ func (w Inbox) Message() model.Message { messageID, err := primitive.ObjectIDFromHex(w._request.URL.Query().Get("messageId")) if err != nil { - derp.Report(derp.Wrap(err, "render.Inbox.Message", "Invalid message ID", w._request.URL.Query().Get("messageId"))) + derp.Report(derp.Wrap(err, "build.Inbox.Message", "Invalid message ID", w._request.URL.Query().Get("messageId"))) return model.NewMessage() } @@ -447,7 +447,7 @@ func (w Inbox) Message() model.Message { message := model.NewMessage() if err := inboxService.LoadByID(w.AuthenticatedID(), messageID, &message); err != nil { - derp.Report(derp.Wrap(err, "render.Inbox.Message", "Error loading message", messageID)) + derp.Report(derp.Wrap(err, "build.Inbox.Message", "Error loading message", messageID)) return model.NewMessage() } @@ -623,7 +623,7 @@ func (w Inbox) HasRule(ruleType string, trigger string) model.Rule { // Retrieve rule record. "Not Found" is acceptable, but "legitimate" errors are not. // In either case, do not halt the request if err := ruleService.LoadByTrigger(w._user.UserID, ruleType, trigger, &rule); !derp.NilOrNotFound(err) { - derp.Report(derp.Wrap(err, "render.Inbox.HasRule", "Error loading rule", ruleType, trigger)) + derp.Report(derp.Wrap(err, "build.Inbox.HasRule", "Error loading rule", ruleType, trigger)) } // Return the (possibly empty) Rule record @@ -631,5 +631,5 @@ func (w Inbox) HasRule(ruleType string, trigger string) model.Rule { } func (w Inbox) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_Inbox") + log.Debug().Interface("object", w.object()).Msg("builder_Inbox") } diff --git a/render/renderer_model.go b/build/builder_model.go similarity index 83% rename from render/renderer_model.go rename to build/builder_model.go index a6d0c0391..e494c010a 100644 --- a/render/renderer_model.go +++ b/build/builder_model.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -14,17 +14,17 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Model renders objects from any model service that implements the ModelService interface +// Model builds objects from any model service that implements the ModelService interface type Model struct { _service service.ModelService _object data.Object Common } -// NewModel returns a fully initialized `Model` renderer. +// NewModel returns a fully initialized `Model` builder. func NewModel(factory Factory, request *http.Request, response http.ResponseWriter, object data.Object, template model.Template, actionID string) (Model, error) { - const location = "render.NewModel" + const location = "build.NewModel" action, ok := template.Action(actionID) @@ -32,11 +32,11 @@ func NewModel(factory Factory, request *http.Request, response http.ResponseWrit return Model{}, derp.NewBadRequestError(location, "Invalid action", actionID) } - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Model{}, derp.Wrap(err, location, "Error creating common renderer") + return Model{}, derp.Wrap(err, location, "Error creating common builder") } // Check permissions on this model object @@ -59,7 +59,7 @@ func NewModel(factory Factory, request *http.Request, response http.ResponseWrit return Model{}, derp.NewInternalError(location, "Invalid model service", object) } - // Return the Model renderer + // Return the Model builder return Model{ _service: modelService, _object: object, @@ -146,7 +146,7 @@ func (w Model) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w._factory, &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.Stream.Render", "Error generating HTML") + err := derp.Wrap(status.Error, "build.Stream.Render", "Error generating HTML") derp.Report(err) return "", err } @@ -159,13 +159,13 @@ func (w Model) Render() (template.HTML, error) { // View executes a separate view for this Stream func (w Model) View(actionID string) (template.HTML, error) { - const location = "render.Stream.View" + const location = "build.Stream.View" - // Create a new renderer (this will also validate the user's permissions) + // Create a new builder (this will also validate the user's permissions) subStream, err := NewModel(w._factory, w._request, w._response, w._object, w._template, actionID) if err != nil { - return template.HTML(""), derp.Wrap(err, location, "Error creating sub-renderer") + return template.HTML(""), derp.Wrap(err, location, "Error creating sub-builder") } // Generate HTML template @@ -179,13 +179,13 @@ func (w Model) setState(stateID string) error { return nil } - return derp.NewInternalError("render.Model.SetState", "Object does not implement model.StateSetter interface", w._object) + return derp.NewInternalError("build.Model.SetState", "Object does not implement model.StateSetter interface", w._object) } -func (w Model) clone(action string) (Renderer, error) { +func (w Model) clone(action string) (Builder, error) { return NewModel(w._factory, w._request, w._response, w._object, w._template, action) } func (w Model) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_Model") + log.Debug().Interface("object", w.object()).Msg("builder_Model") } diff --git a/render/renderer_oAuthAuthorization.go b/build/builder_oAuthAuthorization.go similarity index 93% rename from render/renderer_oAuthAuthorization.go rename to build/builder_oAuthAuthorization.go index 932dbf977..8e2604bb2 100644 --- a/render/renderer_oAuthAuthorization.go +++ b/build/builder_oAuthAuthorization.go @@ -1,4 +1,4 @@ -package render +package build import ( "strings" @@ -9,7 +9,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// OAuthAuthorization is a lightweight renderer that +// OAuthAuthorization is a lightweight builder that // displays UI pages for an OAuth Application. type OAuthAuthorization struct { _service *service.OAuthClient @@ -17,10 +17,10 @@ type OAuthAuthorization struct { _request model.OAuthAuthorizationRequest } -// NewOAuthAuthorization returns a fully initialized/loaded `OAuthAuthorization` renderer +// NewOAuthAuthorization returns a fully initialized/loaded `OAuthAuthorization` builder func NewOAuthAuthorization(factory Factory, request model.OAuthAuthorizationRequest) (OAuthAuthorization, error) { - const location = "render.NewOAuthAuthorization" + const location = "build.NewOAuthAuthorization" // Create the result object result := OAuthAuthorization{ diff --git a/render/renderer_outbox.go b/build/builder_outbox.go similarity index 87% rename from render/renderer_outbox.go rename to build/builder_outbox.go index 1cb3e8323..30d8acd32 100644 --- a/render/renderer_outbox.go +++ b/build/builder_outbox.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -16,13 +16,13 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Outbox renders individual messages from a User's Outbox. +// Outbox builds individual messages from a User's Outbox. type Outbox struct { _user *model.User Common } -// NewOutbox returns a fully initialized `Outbox` renderer. +// NewOutbox returns a fully initialized `Outbox` builder. func NewOutbox(factory Factory, request *http.Request, response http.ResponseWriter, user *model.User, actionID string) (Outbox, error) { // Load the Template @@ -30,22 +30,22 @@ func NewOutbox(factory Factory, request *http.Request, response http.ResponseWri template, err := templateService.Load("user-outbox") // Users should get to choose their own outbox template if err != nil { - return Outbox{}, derp.Wrap(err, "render.NewOutbox", "Error loading template") + return Outbox{}, derp.Wrap(err, "build.NewOutbox", "Error loading template") } - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Outbox{}, derp.Wrap(err, "render.NewOutbox", "Error creating common renderer") + return Outbox{}, derp.Wrap(err, "build.NewOutbox", "Error creating common builder") } // Verify that the User's profile is visible if !isUserVisible(&common._authorization, user) { - return Outbox{}, derp.NewNotFoundError("render.NewOutbox", "User not found") + return Outbox{}, derp.NewNotFoundError("build.NewOutbox", "User not found") } - // Return the Outbox renderer + // Return the Outbox builder return Outbox{ _user: user, Common: common, @@ -65,7 +65,7 @@ func (w Outbox) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w._factory, &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.Outbox.Render", "Error generating HTML", w._request.URL.String()) + err := derp.Wrap(status.Error, "build.Outbox.Render", "Error generating HTML", w._request.URL.String()) derp.Report(err) return "", err } @@ -78,13 +78,13 @@ func (w Outbox) Render() (template.HTML, error) { // View executes a separate view for this Outbox func (w Outbox) View(actionID string) (template.HTML, error) { - renderer, err := NewOutbox(w._factory, w._request, w._response, w._user, actionID) + builder, err := NewOutbox(w._factory, w._request, w._response, w._user, actionID) if err != nil { - return template.HTML(""), derp.Wrap(err, "render.Outbox.View", "Error creating Outbox renderer") + return template.HTML(""), derp.Wrap(err, "build.Outbox.View", "Error creating Outbox builder") } - return renderer.Render() + return builder.Render() } // NavigationID returns the ID to use for highlighing navigation menus @@ -135,7 +135,7 @@ func (w Outbox) templateRole() string { return "outbox" } -func (w Outbox) clone(action string) (Renderer, error) { +func (w Outbox) clone(action string) (Builder, error) { return NewOutbox(w._factory, w._request, w._response, w._user, action) } @@ -296,5 +296,5 @@ func (w Outbox) Responses() QueryBuilder[model.Response] { } func (w Outbox) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_Outbox") + log.Debug().Interface("object", w.object()).Msg("builder_Outbox") } diff --git a/render/renderer_stream.go b/build/builder_stream.go similarity index 87% rename from render/renderer_stream.go rename to build/builder_stream.go index 3eb697d2f..388b10a50 100644 --- a/render/renderer_stream.go +++ b/build/builder_stream.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -25,7 +25,7 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// Stream wraps a model.Stream object and provides functions that make it easy to render an HTML template with it. +// Stream wraps a model.Stream object and provides functions that make it easy to build an HTML template with it. type Stream struct { _service service.ModelService // Service to use to access streams (could be Stream or StreamDraft) _stream *model.Stream // The Stream to be displayed @@ -33,13 +33,13 @@ type Stream struct { } /****************************************** - * Stream Renderer Constructors + * Stream Builder Constructors ******************************************/ // NewStream creates a new object that can generate HTML for a specific stream/view func NewStream(factory Factory, request *http.Request, response http.ResponseWriter, template model.Template, stream *model.Stream, actionID string) (Stream, error) { - const location = "render.NewStream" + const location = "build.NewStream" // Verify the requested action action, ok := template.Action(actionID) @@ -48,11 +48,11 @@ func NewStream(factory Factory, request *http.Request, response http.ResponseWri return Stream{}, derp.ReportAndReturn(derp.NewBadRequestError(location, "Invalid action", template.TemplateID, actionID)) } - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(factory, request, response, template, actionID) if err != nil { - return Stream{}, derp.ReportAndReturn(derp.Wrap(err, location, "Error creating common renderer")) + return Stream{}, derp.ReportAndReturn(derp.Wrap(err, location, "Error creating common builder")) } if !action.UserCan(stream, &common._authorization) { @@ -79,18 +79,18 @@ func NewStreamWithoutTemplate(factory Factory, request *http.Request, response h template, err := templateService.Load(stream.TemplateID) if err != nil { - return Stream{}, derp.Wrap(err, "render.NewStreamWithoutTemplate", "Error loading Template", stream) + return Stream{}, derp.Wrap(err, "build.NewStreamWithoutTemplate", "Error loading Template", stream) } // Return a fully populated service return NewStream(factory, request, response, template, stream, actionID) } -// NewStreamFromURI creates a new Stream renderer for the provided request context. +// NewStreamFromURI creates a new Stream builder for the provided request context. // IMPORTANT: The stream parameter is expected to be an empty stream in the caller's scope that will be populated by this function. func NewStreamFromURI(serverFactory ServerFactory, request *http.Request, response http.ResponseWriter, stream *model.Stream, actionID string) (Stream, error) { - const location = "render.NewStreamFromURI" + const location = "build.NewStreamFromURI" // Locate the requested domain name factory, err := serverFactory.ByDomainName(request.Host) @@ -117,7 +117,7 @@ func NewStreamFromURI(serverFactory ServerFactory, request *http.Request, respon actionID = defaultAction } - // Create and return a new renderer + // Create and return a new builder return NewStreamWithoutTemplate(factory, request, response, stream, actionID) } @@ -134,7 +134,7 @@ func (w Stream) Render() (template.HTML, error) { status := Pipeline(w.action.Steps).Get(w._factory, &w, &buffer) if status.Error != nil { - err := derp.Wrap(status.Error, "render.Stream.Render", "Error generating HTML") + err := derp.Wrap(status.Error, "build.Stream.Render", "Error generating HTML") derp.Report(err) return "", err } @@ -144,7 +144,7 @@ func (w Stream) Render() (template.HTML, error) { return template.HTML(buffer.String()), nil } -// object returns the model object associated with this renderer +// object returns the model object associated with this builder func (w Stream) object() data.Object { return w._stream } @@ -157,7 +157,7 @@ func (w Stream) objectType() string { return "Stream" } -// schema returns the validation schema associated with this renderer +// schema returns the validation schema associated with this builder func (w Stream) schema() schema.Schema { return w._template.Schema } @@ -172,7 +172,7 @@ func (w Stream) templateRole() string { return w._template.TemplateRole } -func (w Stream) clone(action string) (Renderer, error) { +func (w Stream) clone(action string) (Builder, error) { return NewStream(w._factory, w._request, w._response, w._template, w._stream, action) } @@ -183,13 +183,13 @@ func (w Stream) clone(action string) (Renderer, error) { // View executes a separate view for this Stream func (w Stream) View(actionID string) (template.HTML, error) { - const location = "render.Stream.View" + const location = "build.Stream.View" - // Create a new renderer (this will also validate the user's permissions) + // Create a new builder (this will also validate the user's permissions) subStream, err := NewStream(w.factory(), w._request, w._response, w._template, w._stream, actionID) if err != nil { - return template.HTML(""), derp.ReportAndReturn(derp.Wrap(err, location, "Error creating sub-renderer")) + return template.HTML(""), derp.ReportAndReturn(derp.Wrap(err, location, "Error creating sub-builder")) } // Generate HTML template @@ -200,12 +200,12 @@ func (w Stream) View(actionID string) (template.HTML, error) { * STREAM DATA ******************************************/ -// StreamID returns the unique ID for the stream being rendered +// StreamID returns the unique ID for the stream being builded func (w Stream) StreamID() string { return w._stream.StreamID.Hex() } -// StreamID returns the unique ID for the stream being rendered +// StreamID returns the unique ID for the stream being builded func (w Stream) ParentID() string { return w._stream.ParentID.Hex() } @@ -218,12 +218,12 @@ func (w Stream) NavigationID() string { return w._stream.NavigationID } -// PageTitle returns the Label for the stream being rendered +// PageTitle returns the Label for the stream being builded func (w Stream) PageTitle() string { return w._stream.Label } -// StateID returns the current state of the stream being rendered +// StateID returns the current state of the stream being builded func (w Stream) StateID() string { return w._stream.StateID } @@ -233,7 +233,7 @@ func (w Stream) TemplateID() string { return w._stream.TemplateID } -// Token returns the unique URL token for the stream being rendered +// Token returns the unique URL token for the stream being builded func (w Stream) Token() string { return w._stream.Token } @@ -243,17 +243,17 @@ func (w Stream) Document() model.DocumentLink { return w._stream.DocumentLink() } -// Label returns the Label for the stream being rendered +// Label returns the Label for the stream being builded func (w Stream) Label() string { return w._stream.Label } -// Summary returns the description of the stream being rendered +// Summary returns the description of the stream being builded func (w Stream) Summary() string { return w._stream.Summary } -// SummaryHTML returns the description of the stream being rendered +// SummaryHTML returns the description of the stream being builded func (w Stream) SummaryHTML() template.HTML { return template.HTML(w._stream.Summary) } @@ -263,7 +263,7 @@ func (w Stream) ShortSummary() string { return htmlconv.Summary(w._stream.Summary) } -// ImageURL returns the thumbnail image URL of the stream being rendered +// ImageURL returns the thumbnail image URL of the stream being builded func (w Stream) ImageURL() string { return w._stream.ImageURL } @@ -311,12 +311,12 @@ func (w Stream) ContentRaw() string { return w._stream.Content.Raw } -// CreateDate returns the CreateDate of the stream being rendered +// CreateDate returns the CreateDate of the stream being builded func (w Stream) CreateDate() int64 { return w._stream.CreateDate } -// PublishDate returns the PublishDate of the stream being rendered +// PublishDate returns the PublishDate of the stream being builded func (w Stream) PublishDate() int64 { if w._stream.PublishDate > 0 { @@ -326,22 +326,22 @@ func (w Stream) PublishDate() int64 { return w._stream.CreateDate } -// UpdateDate returns the UpdateDate of the stream being rendered +// UpdateDate returns the UpdateDate of the stream being builded func (w Stream) UpdateDate() int64 { return w._stream.UpdateDate } -// Rank returns the Rank of the stream being rendered +// Rank returns the Rank of the stream being builded func (w Stream) Rank() int { return w._stream.Rank } -// Data returns the custom data map of the stream being rendered +// Data returns the custom data map of the stream being builded func (w Stream) Data(value string) any { return w._stream.Data[value] } -// HasParent returns TRUE if the stream being rendered has a parend objec +// HasParent returns TRUE if the stream being builded has a parend objec func (w Stream) HasParent() bool { return w._stream.HasParent() } @@ -408,10 +408,10 @@ func (w Stream) Widgets(location string) (template.HTML, error) { buffer.WriteString(`
`) for _, streamWidget := range list { if widget, ok := widgetService.Get(streamWidget.Type); ok { - widgetRenderer := NewWidget(&w, streamWidget) + widgetBuilder := NewWidget(&w, streamWidget) - if err := widget.HTMLTemplate.ExecuteTemplate(&buffer, "widget", widgetRenderer); err != nil { - derp.Report(derp.Wrap(err, "renderer.Stream.Widgets", "Error executing widget template", widget)) + if err := widget.HTMLTemplate.ExecuteTemplate(&buffer, "widget", widgetBuilder); err != nil { + derp.Report(derp.Wrap(err, "builder.Stream.Widgets", "Error executing widget template", widget)) } } } @@ -427,7 +427,7 @@ func (w Stream) Widgets(location string) (template.HTML, error) { // Parent returns a Stream containing the parent of the current stream func (w Stream) Parent(actionID string) (Stream, error) { - const location = "renderer.Stream.Parent" + const location = "builder.Stream.Parent" var parent model.Stream @@ -437,13 +437,13 @@ func (w Stream) Parent(actionID string) (Stream, error) { return Stream{}, derp.Wrap(err, location, "Error loading Parent") } - renderer, err := NewStreamWithoutTemplate(w.factory(), w._request, w._response, &parent, actionID) + builder, err := NewStreamWithoutTemplate(w.factory(), w._request, w._response, &parent, actionID) if err != nil { return Stream{}, derp.Wrap(err, location, "Unable to create new Stream") } - return renderer, nil + return builder, nil } // PrevSibling returns the sibling Stream that immediately preceeds this one, based on the provided sort field @@ -497,7 +497,7 @@ func (w Stream) getFirstStream(criteria exp.Expression, sortOption option.Option iterator, err := streamService.List(criteria, sortOption, option.FirstRow()) if err != nil { - derp.Report(derp.Wrap(err, "renderer.Stream.NextSibling", "Database error")) + derp.Report(derp.Wrap(err, "builder.Stream.NextSibling", "Database error")) return Stream{} } @@ -509,7 +509,7 @@ func (w Stream) getFirstStream(criteria exp.Expression, sortOption option.Option } } - // Fall through means no streams are valid. Return an empty renderer instead. + // Fall through means no streams are valid. Return an empty builder instead. return Stream{} } @@ -627,7 +627,7 @@ func (w Stream) Ancestors() QueryBuilder[model.StreamSummary] { streamService := w.factory().Stream() if err := streamService.LoadParent(w._stream, &parent); err != nil { - derp.Report(derp.Wrap(err, "renderer.Stream.Ancestors", "Error loading parent")) + derp.Report(derp.Wrap(err, "builder.Stream.Ancestors", "Error loading parent")) } return w.makeStreamQueryBuilder(exp.Equal("parentId", parent.ParentID)) @@ -694,7 +694,7 @@ func (w Stream) Following() ([]model.Following, error) { iterator, err := followingService.ListByUserID(w.AuthenticatedID()) if err != nil { - return result, derp.Wrap(err, "renderer.Stream.Following", "Error listing following") + return result, derp.Wrap(err, "builder.Stream.Following", "Error listing following") } following := model.NewFollowing() @@ -742,26 +742,26 @@ func (w Stream) CanCreate() []form.LookupCode { return templateService.ListByContainer(w._template.TemplateID) } -// draftRenderer returns a new render.Stream that is bound to the +// draftBuilder returns a new build.Stream that is bound to the // draft service, and a draft copy of the current stream. -func (w Stream) draftRenderer() (Stream, error) { +func (w Stream) draftBuilder() (Stream, error) { var draft model.Stream draftService := w.factory().StreamDraft() // Load the draft of the object if err := draftService.LoadByID(w._stream.StreamID, &draft); err != nil { - return Stream{}, derp.Wrap(err, "render.Stream.draftRenderer", "Error loading draft") + return Stream{}, derp.Wrap(err, "build.Stream.draftBuilder", "Error loading draft") } - // Create the underlying Common renderer + // Create the underlying Common builder common, err := NewCommon(w._factory, w._request, w._response, w._template, w.actionID) if err != nil { - return Stream{}, derp.Wrap(err, "render.Stream.draftRenderer", "Error creating common renderer") + return Stream{}, derp.Wrap(err, "build.Stream.draftBuilder", "Error creating common builder") } - // Make a duplicate of this renderer. Same object, template, action settings + // Make a duplicate of this builder. Same object, template, action settings return Stream{ _stream: &draft, _service: draftService, @@ -775,5 +775,5 @@ func (w Stream) setState(stateID string) error { } func (w Stream) debug() { - log.Debug().Interface("object", w.object()).Msg("renderer_Stream") + log.Debug().Interface("object", w.object()).Msg("builder_Stream") } diff --git a/render/renderer_widget.go b/build/builder_widget.go similarity index 66% rename from render/renderer_widget.go rename to build/builder_widget.go index d72692a2a..8b0502c9a 100644 --- a/render/renderer_widget.go +++ b/build/builder_widget.go @@ -1,10 +1,10 @@ -package render +package build import ( "github.com/EmissarySocial/emissary/model" ) -// Widget renderer is created by the "with-widget" action, and +// Widget builder is created by the "with-widget" action, and // can execute additional action steps on a widget that is // embedded in a stream. To save the final result, you must // call "save" on the stream itself, not within this widget. @@ -13,9 +13,9 @@ type Widget struct { *Stream } -func NewWidget(renderer *Stream, widget model.StreamWidget) Widget { +func NewWidget(builder *Stream, widget model.StreamWidget) Widget { return Widget{ Widget: widget, - Stream: renderer, + Stream: builder, } } diff --git a/render/constants.go b/build/constants.go similarity index 95% rename from render/constants.go rename to build/constants.go index b351e9a6e..2c5a0009e 100644 --- a/render/constants.go +++ b/build/constants.go @@ -1,4 +1,4 @@ -package render +package build // ActionMethod enumerates the HTTP methods that can be performed on Actions type ActionMethod uint8 diff --git a/render/factory.go b/build/factory.go similarity index 99% rename from render/factory.go rename to build/factory.go index 7fe735d1d..231a96ec1 100644 --- a/render/factory.go +++ b/build/factory.go @@ -1,4 +1,4 @@ -package render +package build import ( "github.com/EmissarySocial/emissary/config" diff --git a/render/functions.go b/build/functions.go similarity index 99% rename from render/functions.go rename to build/functions.go index 474411204..63947c6e3 100644 --- a/render/functions.go +++ b/build/functions.go @@ -1,4 +1,4 @@ -package render +package build import ( "encoding/json" diff --git a/render/functions_test.go b/build/functions_test.go similarity index 97% rename from render/functions_test.go rename to build/functions_test.go index a7c342c6d..fd5ee7176 100644 --- a/render/functions_test.go +++ b/build/functions_test.go @@ -1,4 +1,4 @@ -package render +package build import ( "html/template" diff --git a/render/interfaces.go b/build/interfaces.go similarity index 87% rename from render/interfaces.go rename to build/interfaces.go index 7ce1f358a..44c2507f6 100644 --- a/render/interfaces.go +++ b/build/interfaces.go @@ -1,11 +1,11 @@ -package render +package build import "github.com/EmissarySocial/emissary/model" type PipelineHalter interface { // HaltPipeline optionally allows a step to halt processing of an action pipeline - HaltPipeline(Renderer) bool + HaltPipeline(Builder) bool } type DocumentLinker interface { diff --git a/render/pipeline.go b/build/pipeline.go similarity index 63% rename from render/pipeline.go rename to build/pipeline.go index 562286521..349b036c1 100644 --- a/render/pipeline.go +++ b/build/pipeline.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -9,17 +9,17 @@ import ( type Pipeline []step.Step // Execute switches between GET and POST methods for this pipeline, based on the provided ActionMethod -func (pipeline Pipeline) Execute(factory Factory, renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineResult { +func (pipeline Pipeline) Execute(factory Factory, builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineResult { if actionMethod == ActionMethodGet { - return pipeline.Get(factory, renderer, buffer) + return pipeline.Get(factory, builder, buffer) } - return pipeline.Post(factory, renderer, buffer) + return pipeline.Post(factory, builder, buffer) } // Get runs all of the pipeline steps using the GET method -func (pipeline Pipeline) Get(factory Factory, renderer Renderer, buffer io.Writer) PipelineResult { +func (pipeline Pipeline) Get(factory Factory, builder Builder, buffer io.Writer) PipelineResult { status := NewPipelineResult() @@ -27,7 +27,7 @@ func (pipeline Pipeline) Get(factory Factory, renderer Renderer, buffer io.Write for _, step := range pipeline { // Execute the step and collect the results in the pipeline status - resultFn := ExecutableStep(step).Get(renderer, buffer) + resultFn := ExecutableStep(step).Get(builder, buffer) if resultFn != nil { resultFn(&status) @@ -42,14 +42,14 @@ func (pipeline Pipeline) Get(factory Factory, renderer Renderer, buffer io.Write } // Post runs runs all of the pipeline steps using the POST method -func (pipeline Pipeline) Post(factory Factory, renderer Renderer, buffer io.Writer) PipelineResult { +func (pipeline Pipeline) Post(factory Factory, builder Builder, buffer io.Writer) PipelineResult { status := NewPipelineResult() // Execute all of the steps of the requested action for _, step := range pipeline { - resultFn := ExecutableStep(step).Post(renderer, buffer) + resultFn := ExecutableStep(step).Post(builder, buffer) if resultFn != nil { resultFn(&status) diff --git a/render/pipeline_behavior.go b/build/pipeline_behavior.go similarity index 96% rename from render/pipeline_behavior.go rename to build/pipeline_behavior.go index ec50a9fa2..6ea4a52b1 100644 --- a/render/pipeline_behavior.go +++ b/build/pipeline_behavior.go @@ -1,4 +1,4 @@ -package render +package build type PipelineBehavior func(*PipelineResult) @@ -22,7 +22,7 @@ func UseResult(newStatus PipelineResult) PipelineBehavior { } // AsFullPage sets the FullPage flag on the PipelineResult object, which -// tells the renderer to NOT include the header/footer from the site theme. +// tells the builder to NOT include the header/footer from the site theme. func (exit PipelineBehavior) AsFullPage() PipelineBehavior { return func(status *PipelineResult) { if exit != nil { diff --git a/render/pipeline_result.go b/build/pipeline_result.go similarity index 98% rename from render/pipeline_result.go rename to build/pipeline_result.go index 0ce0df0b2..fb1f03b27 100644 --- a/render/pipeline_result.go +++ b/build/pipeline_result.go @@ -1,4 +1,4 @@ -package render +package build import ( "encoding/json" @@ -15,7 +15,7 @@ type PipelineResult struct { Events mapof.String // Map of events to trigger on the client (via HX-Trigger) FullPage bool // If true, then this result represents the entire page of content, and should not be wrapped in the global template Halt bool // If true, then this pipeline should halt execution - Error error // If present, then there was an error rendering this page + Error error // If present, then there was an error building this page } func NewPipelineResult() PipelineResult { diff --git a/render/queryBuilder.go b/build/queryBuilder.go similarity index 99% rename from render/queryBuilder.go rename to build/queryBuilder.go index 864f4a580..5b350031a 100644 --- a/render/queryBuilder.go +++ b/build/queryBuilder.go @@ -1,4 +1,4 @@ -package render +package build import ( "github.com/EmissarySocial/emissary/model" diff --git a/render/step_.go b/build/step_.go similarity index 93% rename from render/step_.go rename to build/step_.go index d57fa0558..ab11bce75 100644 --- a/render/step_.go +++ b/build/step_.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -7,16 +7,16 @@ import ( ) type Step interface { - Get(Renderer, io.Writer) PipelineBehavior - Post(Renderer, io.Writer) PipelineBehavior + Get(Builder, io.Writer) PipelineBehavior + Post(Builder, io.Writer) PipelineBehavior } // ExecutableStep uses an Step object to create a new action func ExecutableStep(stepInfo step.Step) Step { // These actual action steps require access to more of the application than is available in the model package. - // Model objects are only concerned with the data that they contain, and not with the actual rendering of that data. - // So, we convert the model object into a "render step" that CAN access the rendering context. + // Model objects are only concerned with the data that they contain, and not with the actual building of that data. + // So, we convert the model object into a "build step" that CAN access the building context. switch s := stepInfo.(type) { diff --git a/build/step_AddModelObject.go b/build/step_AddModelObject.go new file mode 100644 index 000000000..ca6949e2f --- /dev/null +++ b/build/step_AddModelObject.go @@ -0,0 +1,83 @@ +package build + +import ( + "io" + + "github.com/EmissarySocial/emissary/model/step" + "github.com/benpate/derp" + "github.com/benpate/form" +) + +// StepAddModelObject is an action that can add new model objects of any type +type StepAddModelObject struct { + Form form.Element + Defaults []step.Step +} + +// Get displays a modal form that lets users enter data for their new model object. +func (step StepAddModelObject) Get(builder Builder, buffer io.Writer) PipelineBehavior { + + factory := builder.factory() + schema := builder.schema() + object := builder.object() + + // First, try to execute any "default" steps so that the object is initialized + result := Pipeline(step.Defaults).Get(factory, builder, buffer) + + if result.Halt { + result.Error = derp.Wrap(result.Error, "build.StepAddModelObject.Get", "Error executing default steps") + return UseResult(result) + } + + // Try to build the Form HTML + formHTML, err := form.Editor(schema, step.Form, object, builder.lookupProvider()) + + if err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepAddModelObject.Get", "Error generating form")) + } + + formHTML = WrapForm(builder.URL(), formHTML) + + // Wrap formHTML as a modal dialog + // nolint:errcheck + io.WriteString(buffer, formHTML) + return nil +} + +// Post initializes a new model object, populates it with data from the form, then saves it to the database. +func (step StepAddModelObject) Post(builder Builder, buffer io.Writer) PipelineBehavior { + + // This finds/creates a new object in the builder + factory := builder.factory() + request := builder.request() + object := builder.object() + schema := builder.schema() + + // Execute any "default" steps so that the object is initialized + result := Pipeline(step.Defaults).Post(factory, builder, buffer) + + if result.Halt { + result.Error = derp.Wrap(result.Error, "build.StepAddModelObject.Post", "Error executing default steps") + return UseResult(result) + } + + // Parse form information + if err := request.ParseForm(); err != nil { + return Halt().WithError(derp.Wrap(err, "build.AddModelObject.Post", "Error parsing form data")) + } + + // Try to set each path from the Form into the builder. Note: schema.Set also converts and validated inputs before setting. + for key, value := range request.Form { + if err := schema.Set(object, key, value); err != nil { + return Halt().WithError(derp.Wrap(err, "build.AddModelObject.Post", "Error setting path value", key, value)) + } + } + + // Save the object to the database + if err := builder.service().ObjectSave(object, "Created"); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepAddModelObject.Post", "Error saving model object to database")) + } + + // Success! + return nil +} diff --git a/render/step_AddStream.go b/build/step_AddStream.go similarity index 73% rename from render/step_AddStream.go rename to build/step_AddStream.go index 9608ac340..506c26e64 100644 --- a/render/step_AddStream.go +++ b/build/step_AddStream.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -25,35 +25,35 @@ type StepAddStream struct { * GET Methods ******************************************/ -// Get renders the HTML for this step - either a modal template selector, or the embedded edit form -func (step StepAddStream) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +// Get builds the HTML for this step - either a modal template selector, or the embedded edit form +func (step StepAddStream) Get(builder Builder, buffer io.Writer) PipelineBehavior { if step.Style == "inline" { - if err := step.getInline(renderer, buffer); err != nil { + if err := step.getInline(builder, buffer); err != nil { return Halt().WithError(err) } return nil } // Fall through to displaying the default "CHOOSER" modal - if err := step.getChooser(renderer, buffer); err != nil { + if err := step.getChooser(builder, buffer); err != nil { return Halt().WithError(err) } return Halt() } -// modalAddStream renders an HTML dialog that lists all of the templates that the user can create +// modalAddStream builds an HTML dialog that lists all of the templates that the user can create // tempalteIDs is a limiter on the list of valid templates. If it is empty, then all valid templates are displayed. -func (step StepAddStream) getChooser(renderer Renderer, buffer io.Writer) error { +func (step StepAddStream) getChooser(builder Builder, buffer io.Writer) error { // response *echo.Response, templateService *service.Template, iconProvider icon.Provider, title string, buffer io.Writer, url string, parentRole string, allowedTemplateIDs []string) { - factory := renderer.factory() - response := renderer.response() + factory := builder.factory() + response := builder.response() templateService := factory.Template() iconProvider := factory.Icons() - parentRole := step.parentRole(renderer) + parentRole := step.parentRole(builder) templates := templateService.ListByContainerLimited(parentRole, step.TemplateRoles) @@ -64,7 +64,7 @@ func (step StepAddStream) getChooser(renderer Renderer, buffer io.Writer) error b.Table().Class("table margin-bottom") for _, template := range templates { - b.TR().Role("link").Data("hx-post", renderer.URL()+"?templateId="+template.Value) + b.TR().Role("link").Data("hx-post", builder.URL()+"?templateId="+template.Value) { b.TD().Class("text-3xl").Style("vertical-align:top").EndBracket() iconProvider.Write(template.Icon, b) @@ -89,27 +89,27 @@ func (step StepAddStream) getChooser(renderer Renderer, buffer io.Writer) error return nil } -// getInline renders the HTML for an embedded form -func (step StepAddStream) getInline(renderer Renderer, buffer io.Writer) error { +// getInline builds the HTML for an embedded form +func (step StepAddStream) getInline(builder Builder, buffer io.Writer) error { - const location = "render.StepAddStream.getInline" + const location = "build.StepAddStream.getInline" // Get prerequisites - factory := renderer.factory() + factory := builder.factory() templateService := factory.Template() - containedByRole := step.parentRole(renderer) + containedByRole := step.parentRole(builder) // Find the "selected" template - optionTemplates, newTemplate, err := step.getBestTemplate(templateService, containedByRole, renderer.QueryParam("templateId")) + optionTemplates, newTemplate, err := step.getBestTemplate(templateService, containedByRole, builder.QueryParam("templateId")) if err != nil { return derp.Wrap(err, location, "Error getting best template") } - iconService := renderer.factory().Icons() + iconService := builder.factory().Icons() - path := renderer.request().URL.Path - path = replaceActionID(path, renderer.ActionID()) + path := builder.request().URL.Path + path = replaceActionID(path, builder.ActionID()) // Build the HTML for the "embed" widget b := html.New() @@ -138,23 +138,23 @@ func (step StepAddStream) getInline(renderer Renderer, buffer io.Writer) error { b.Close() // DIV } - // If there is a child renderer, then render it here + // If there is a child builder, then build it here // Create a new child stream streamService := factory.Stream() child := streamService.New() - // Create a new child renderer - childRenderer, err := NewStream(factory, renderer.request(), renderer.response(), newTemplate, &child, "create") - childRenderer.setArguments(renderer.getArguments()) + // Create a new child builder + childBuilder, err := NewStream(factory, builder.request(), builder.response(), newTemplate, &child, "create") + childBuilder.setArguments(builder.getArguments()) if err != nil { - return derp.Wrap(err, location, "Error creating new child stream renderer") + return derp.Wrap(err, location, "Error creating new child stream builder") } - widgetHTML, err := childRenderer.Render() + widgetHTML, err := childBuilder.Render() if err != nil { - return derp.Wrap(err, location, "Error rendering new child stream") + return derp.Wrap(err, location, "Error building new child stream") } b.WriteString(string(widgetHTML)) @@ -172,17 +172,17 @@ func (step StepAddStream) getInline(renderer Renderer, buffer io.Writer) error { * POST Methods ******************************************/ -func (step StepAddStream) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepAddStream) Post(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepAddStream.Post" + const location = "build.StepAddStream.Post" // Collect prerequisites - factory := renderer.factory() + factory := builder.factory() templateService := factory.Template() - containedByRole := step.parentRole(renderer) + containedByRole := step.parentRole(builder) // Identify the Template to used for the new Stream - _, template, err := step.getBestTemplate(templateService, containedByRole, renderer.QueryParam("templateId")) + _, template, err := step.getBestTemplate(templateService, containedByRole, builder.QueryParam("templateId")) if err != nil { return Halt().WithError(derp.Wrap(err, location, "Invalid Template. Check template roles and 'containedBy' values.")) @@ -193,30 +193,30 @@ func (step StepAddStream) Post(renderer Renderer, buffer io.Writer) PipelineBeha newStream := streamService.New() // Validate and set the location for the new Stream - if err := step.setLocation(renderer, &template, &newStream); err != nil { + if err := step.setLocation(builder, &template, &newStream); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error getting location for new stream")) } // Apply custom stream data from the "with-data" map - if err := step.setStreamData(renderer, &newStream); err != nil { + if err := step.setStreamData(builder, &newStream); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error setting stream data")) } // Assign the current user as the author (with silent failure) - if user, err := renderer.getUser(); err == nil { + if user, err := builder.getUser(); err == nil { newStream.SetAttributedTo(user.PersonLink()) } - // Create a renderer for the new Stream - newRenderer, err := NewStream(factory, renderer.request(), renderer.response(), template, &newStream, "create") - newRenderer.setArguments(renderer.getArguments()) + // Create a builder for the new Stream + newBuilder, err := NewStream(factory, builder.request(), builder.response(), template, &newStream, "create") + newBuilder.setArguments(builder.getArguments()) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error creating renderer", newStream)) + return Halt().WithError(derp.Wrap(err, location, "Error creating builder", newStream)) } // Run the "create" action for the new stream's template, if possible - result := Pipeline(newRenderer.Action().Steps).Post(factory, &newRenderer, buffer) + result := Pipeline(newBuilder.Action().Steps).Post(factory, &newBuilder, buffer) result.Error = derp.Wrap(result.Error, location, "Unable to execute 'create' action on stream") // For "inline" styles, use the result from the child's "create" action @@ -232,18 +232,18 @@ func (step StepAddStream) Post(renderer Renderer, buffer io.Writer) PipelineBeha // setLocation returns the ParentIDs to use in for the new stream. // It returns an error if the template cannot be placed in the pre-determined location. -func (step StepAddStream) setLocation(renderer Renderer, template *model.Template, newStream *model.Stream) error { +func (step StepAddStream) setLocation(builder Builder, template *model.Template, newStream *model.Stream) error { - const location = "render.StepAddStream.setLocation" + const location = "build.StepAddStream.setLocation" - streamService := renderer.factory().Stream() + streamService := builder.factory().Stream() switch step.Location { // Special case for streams in User's Outbox case "outbox": - userID := renderer.AuthenticatedID() + userID := builder.AuthenticatedID() if err := streamService.SetLocationOutbox(template, newStream, userID); err != nil { return derp.Wrap(err, location, "Error setting location for outbox") } @@ -260,14 +260,14 @@ func (step StepAddStream) setLocation(renderer Renderer, template *model.Templat // Default to "Child" streams default: - // Guarantee that the current renderer is a Stream renderer - streamRenderer, ok := renderer.(*Stream) + // Guarantee that the current builder is a Stream builder + streamBuilder, ok := builder.(*Stream) if !ok { - return derp.NewForbiddenError(location, "Cannot add child stream to non-stream renderer") + return derp.NewForbiddenError(location, "Cannot add child stream to non-stream builder") } - parent := streamRenderer._stream + parent := streamBuilder._stream if err := streamService.SetLocationChild(template, newStream, parent); err != nil { return derp.Wrap(err, step.Location, "Error setting location for child") } @@ -275,7 +275,7 @@ func (step StepAddStream) setLocation(renderer Renderer, template *model.Templat } } -func (step StepAddStream) setStreamData(renderer Renderer, stream *model.Stream) error { +func (step StepAddStream) setStreamData(builder Builder, stream *model.Stream) error { if len(step.WithData) == 0 { return nil @@ -284,9 +284,9 @@ func (step StepAddStream) setStreamData(renderer Renderer, stream *model.Stream) s := schema.New(model.StreamSchema()) for key, valueTemplate := range step.WithData { - value := executeTemplate(valueTemplate, renderer) + value := executeTemplate(valueTemplate, builder) if err := s.Set(stream, key, value); err != nil { - return derp.Wrap(err, "render.StepAddStream.setStreamData", "Error setting stream data", key, value) + return derp.Wrap(err, "build.StepAddStream.setStreamData", "Error setting stream data", key, value) } } @@ -297,7 +297,7 @@ func (step StepAddStream) setStreamData(renderer Renderer, stream *model.Stream) // It returns a slice of eligible Templates (as form.LookupCodes), and the selected Template. func (step StepAddStream) getBestTemplate(templateService *service.Template, containedByRole string, selectedTemplateID string) ([]form.LookupCode, model.Template, error) { - const location = "render.StepAddStream.getBestTemplate" + const location = "build.StepAddStream.getBestTemplate" // Query all eligible Templates eligible := templateService.ListByContainerLimited(containedByRole, step.TemplateRoles) @@ -331,7 +331,7 @@ func (step StepAddStream) getBestTemplate(templateService *service.Template, con // getBestTemplate_result finishes the job of getBestTemplate by loading the selected Template from the memory-cache func (step StepAddStream) getBestTemplate_result(templateService *service.Template, eligible []form.LookupCode, templateID string) ([]form.LookupCode, model.Template, error) { - const location = "render.StepAddStream.getBestTemplate_result" + const location = "build.StepAddStream.getBestTemplate_result" template, err := templateService.Load(templateID) @@ -342,10 +342,10 @@ func (step StepAddStream) getBestTemplate_result(templateService *service.Templa return eligible, template, nil } -func (step StepAddStream) parentRole(renderer Renderer) string { +func (step StepAddStream) parentRole(builder Builder) string { if step.Location == "child" { - return renderer.templateRole() + return builder.templateRole() } return step.Location diff --git a/render/step_AsConfirmation.go b/build/step_AsConfirmation.go similarity index 67% rename from render/step_AsConfirmation.go rename to build/step_AsConfirmation.go index c1ac3485c..b845cebff 100644 --- a/render/step_AsConfirmation.go +++ b/build/step_AsConfirmation.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -14,7 +14,7 @@ type StepAsConfirmation struct { } // Get displays a modal that asks users to continue or not. -func (step StepAsConfirmation) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepAsConfirmation) Get(builder Builder, buffer io.Writer) PipelineBehavior { // Modal Content b := html.New() @@ -22,13 +22,13 @@ func (step StepAsConfirmation) Get(renderer Renderer, buffer io.Writer) Pipeline b.Div().Class("margin-bottom").InnerText(step.Message).Close() b.Div() - b.Button().Class("primary").Data("hx-post", renderer.URL()).Data("hx-swap", "none").InnerText(step.Submit).Close() + b.Button().Class("primary").Data("hx-post", builder.URL()).Data("hx-swap", "none").InnerText(step.Submit).Close() b.Button().Script("on click trigger closeModal").InnerText("Cancel").Close() // Done b.CloseAll() - modalHTML := WrapModal(renderer.response(), b.String()) + modalHTML := WrapModal(builder.response(), b.String()) // nolint:errcheck io.WriteString(buffer, modalHTML) @@ -36,6 +36,6 @@ func (step StepAsConfirmation) Get(renderer Renderer, buffer io.Writer) Pipeline } // Post does nothing. (Other steps in the pipeline will make changes) -func (step StepAsConfirmation) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepAsConfirmation) Post(builder Builder, _ io.Writer) PipelineBehavior { return Continue().WithEvent("closeModal", "true") } diff --git a/render/step_AsModal.go b/build/step_AsModal.go similarity index 58% rename from render/step_AsModal.go rename to build/step_AsModal.go index f9d28e408..8dc5132dc 100644 --- a/render/step_AsModal.go +++ b/build/step_AsModal.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -17,14 +17,14 @@ type StepAsModal struct { } // Get displays a form where users can update stream data -func (step StepAsModal) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepAsModal) Get(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepAsModal.Get" + const location = "build.StepAsModal.Get" - // Partial pages only render the modal window. This happens MOST of the time. - if renderer.IsPartialRequest() { + // Partial pages only build the modal window. This happens MOST of the time. + if builder.IsPartialRequest() { - modalContent, status := step.getModalContent(renderer) + modalContent, status := step.getModalContent(builder) if status.Halt { return UseResult(status) @@ -48,45 +48,45 @@ func (step StepAsModal) Get(renderer Renderer, buffer io.Writer) PipelineBehavio return result } - // Otherwise, we can render the modal on a page background... IF we have a background view defined. + // Otherwise, we can build the modal on a page background... IF we have a background view defined. if step.Background == "" { return Halt().WithError(derp.NewBadRequestError(location, "Cannot open this route directly.")) } - // Full pages render the entire page, including the modal window - fullPageRenderer, err := renderer.clone(step.Background) + // Full pages build the entire page, including the modal window + fullPageBuilder, err := builder.clone(step.Background) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error creating fullPageRenderer")) + return Halt().WithError(derp.Wrap(err, location, "Error creating fullPageBuilder")) } // Execute the action pipeline var partialPage bytes.Buffer - factory := fullPageRenderer.factory() - pipeline := Pipeline(fullPageRenderer.Action().Steps) + factory := fullPageBuilder.factory() + pipeline := Pipeline(fullPageBuilder.Action().Steps) - status := pipeline.Execute(factory, fullPageRenderer, &partialPage, ActionMethodGet) + status := pipeline.Execute(factory, fullPageBuilder, &partialPage, ActionMethodGet) if status.Error != nil { - return Halt().WithError(derp.Wrap(status.Error, location, "Error executing action pipeline on fullPageRenderer")) + return Halt().WithError(derp.Wrap(status.Error, location, "Error executing action pipeline on fullPageBuilder")) } // Copy status values into the Response... - status.Apply(fullPageRenderer.response()) + status.Apply(fullPageBuilder.response()) - // Full Page requests require the theme service to wrap the rendered content + // Full Page requests require the theme service to wrap the builded content var fullPage bytes.Buffer htmlTemplate := factory.Domain().Theme().HTMLTemplate - fullPageRenderer.SetContent(partialPage.String()) + fullPageBuilder.SetContent(partialPage.String()) - if err := htmlTemplate.ExecuteTemplate(&fullPage, "page", fullPageRenderer); err != nil { + if err := htmlTemplate.ExecuteTemplate(&fullPage, "page", fullPageBuilder); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error executing template")) } // Insert the modal into the page asideBegin := "" - modalString, result := step.getModalContent(renderer) + modalString, result := step.getModalContent(builder) fullPageString := strings.Replace(fullPage.String(), asideBegin+asideEnd, asideBegin+modalString+asideEnd, 1) if _, err := io.WriteString(buffer, fullPageString); err != nil { @@ -97,28 +97,28 @@ func (step StepAsModal) Get(renderer Renderer, buffer io.Writer) PipelineBehavio } // Post updates the stream with approved data from the request body. -func (step StepAsModal) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepAsModal) Post(builder Builder, buffer io.Writer) PipelineBehavior { // Write inner items - result := Pipeline(step.SubSteps).Post(renderer.factory(), renderer, buffer) - result.Error = derp.Wrap(result.Error, "render.StepAsModal.Post", "Error executing subSteps") + result := Pipeline(step.SubSteps).Post(builder.factory(), builder, buffer) + result.Error = derp.Wrap(result.Error, "build.StepAsModal.Post", "Error executing subSteps") return UseResult(result).WithEvent("closeModal", "true") } -func (step StepAsModal) getModalContent(renderer Renderer) (string, PipelineResult) { +func (step StepAsModal) getModalContent(builder Builder) (string, PipelineResult) { - const location = "render.StepAsModal.getModalContent" + const location = "build.StepAsModal.getModalContent" // Write inner items var buffer bytes.Buffer - result := Pipeline(step.SubSteps).Get(renderer.factory(), renderer, &buffer) + result := Pipeline(step.SubSteps).Get(builder.factory(), builder, &buffer) result.Error = derp.Wrap(result.Error, location, "Error executing subSteps") if result.Halt { return "", result } - return WrapModal(renderer.response(), buffer.String(), step.Options...), result + return WrapModal(builder.response(), buffer.String(), step.Options...), result } diff --git a/render/step_AsTooltip.go b/build/step_AsTooltip.go similarity index 62% rename from render/step_AsTooltip.go rename to build/step_AsTooltip.go index fb5ffe737..2eb629265 100644 --- a/render/step_AsTooltip.go +++ b/build/step_AsTooltip.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -14,14 +14,14 @@ type StepAsTooltip struct { } // Get displays a form where users can update stream data -func (step StepAsTooltip) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepAsTooltip) Get(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepAsTooltip.Get" + const location = "build.StepAsTooltip.Get" // Write inner items var tooltipBuffer bytes.Buffer - result := Pipeline(step.SubSteps).Get(renderer.factory(), renderer, &tooltipBuffer) + result := Pipeline(step.SubSteps).Get(builder.factory(), builder, &tooltipBuffer) result.Error = derp.Wrap(result.Error, location, "Error executing subSteps") if result.Halt { @@ -29,7 +29,7 @@ func (step StepAsTooltip) Get(renderer Renderer, buffer io.Writer) PipelineBehav } // Wrap the content in a tooltip - tooltipContent := WrapTooltip(renderer.response(), tooltipBuffer.String()) + tooltipContent := WrapTooltip(builder.response(), tooltipBuffer.String()) if _, err := io.WriteString(buffer, tooltipContent); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error writing from builder to buffer")) @@ -43,11 +43,11 @@ func (step StepAsTooltip) UseGlobalWrapper() bool { } // Post updates the stream with approved data from the request body. -func (step StepAsTooltip) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepAsTooltip) Post(builder Builder, buffer io.Writer) PipelineBehavior { // Write inner items - result := Pipeline(step.SubSteps).Post(renderer.factory(), renderer, buffer) - result.Error = derp.Wrap(result.Error, "render.StepAsTooltip.Post", "Error executing subSteps") + result := Pipeline(step.SubSteps).Post(builder.factory(), builder, buffer) + result.Error = derp.Wrap(result.Error, "build.StepAsTooltip.Post", "Error executing subSteps") return UseResult(result).WithEvent("closeTooltip", "true") } diff --git a/render/step_Delete.go b/build/step_Delete.go similarity index 62% rename from render/step_Delete.go rename to build/step_Delete.go index 839affd9b..e506f274f 100644 --- a/render/step_Delete.go +++ b/build/step_Delete.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -16,15 +16,15 @@ type StepDelete struct { } // Get displays a customizable confirmation form for the delete -func (step StepDelete) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepDelete) Get(builder Builder, buffer io.Writer) PipelineBehavior { b := html.New() - b.H1().InnerText(executeTemplate(step.Title, renderer)).Close() - b.Div().Class("margin-bottom").InnerText(executeTemplate(step.Message, renderer)).Close() + b.H1().InnerText(executeTemplate(step.Title, builder)).Close() + b.Div().Class("margin-bottom").InnerText(executeTemplate(step.Message, builder)).Close() b.Button().Class("warning"). - Attr("hx-post", renderer.URL()). + Attr("hx-post", builder.URL()). Attr("hx-swap", "none"). Attr("hx-push-url", "false"). InnerText(step.Submit). @@ -33,7 +33,7 @@ func (step StepDelete) Get(renderer Renderer, buffer io.Writer) PipelineBehavior b.Button().Script("on click trigger closeModal").InnerText("Cancel").Close() b.CloseAll() - modalHTML := WrapModal(renderer.response(), b.String()) + modalHTML := WrapModal(builder.response(), b.String()) // nolint:errcheck io.WriteString(buffer, modalHTML) @@ -42,11 +42,11 @@ func (step StepDelete) Get(renderer Renderer, buffer io.Writer) PipelineBehavior } // Post removes the object from the database (likely using a soft-delete, though) -func (step StepDelete) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepDelete) Post(builder Builder, _ io.Writer) PipelineBehavior { // Delete the object via the model service. - if err := renderer.service().ObjectDelete(renderer.object(), "Deleted"); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepDelete.Post", "Error deleting stream")) + if err := builder.service().ObjectDelete(builder.object(), "Deleted"); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepDelete.Post", "Error deleting stream")) } return Continue().WithEvent("closeModal", "true") diff --git a/render/step_DeleteAttachments.go b/build/step_DeleteAttachments.go similarity index 64% rename from render/step_DeleteAttachments.go rename to build/step_DeleteAttachments.go index b5a8ad0a0..2d7b32b5e 100644 --- a/render/step_DeleteAttachments.go +++ b/build/step_DeleteAttachments.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -7,25 +7,25 @@ import ( "go.mongodb.org/mongo-driver/bson/primitive" ) -// StepDeleteAttachments represents an action that can upload attachments. It can only be used on a StreamRenderer +// StepDeleteAttachments represents an action that can upload attachments. It can only be used on a StreamBuilder type StepDeleteAttachments struct { All bool } -func (step StepDeleteAttachments) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepDeleteAttachments) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } -func (step StepDeleteAttachments) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepDeleteAttachments) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "renderer.StepDeleteAttachments.Post" + const location = "builder.StepDeleteAttachments.Post" - factory := renderer.factory() + factory := builder.factory() attachmentService := factory.Attachment() - objectType := renderer.service().ObjectType() - objectID := renderer.objectID() + objectType := builder.service().ObjectType() + objectID := builder.objectID() if step.All { @@ -36,7 +36,7 @@ func (step StepDeleteAttachments) Post(renderer Renderer, _ io.Writer) PipelineB } else { - attachmentIDString := renderer.QueryParam("attachmentId") + attachmentIDString := builder.QueryParam("attachmentId") attachmentID, err := primitive.ObjectIDFromHex(attachmentIDString) if err != nil { @@ -49,7 +49,7 @@ func (step StepDeleteAttachments) Post(renderer Renderer, _ io.Writer) PipelineB } // Notify the client - renderer.response().Header().Set("HX-Trigger", `attachments-updated`) + builder.response().Header().Set("HX-Trigger", `attachments-updated`) return nil } diff --git a/render/step_EditConnection.go b/build/step_EditConnection.go similarity index 64% rename from render/step_EditConnection.go rename to build/step_EditConnection.go index fc17ca9d4..a331aee5c 100644 --- a/render/step_EditConnection.go +++ b/build/step_EditConnection.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -12,18 +12,18 @@ import ( type StepEditConnection struct{} -func (step StepEditConnection) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepEditConnection) Get(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepEditConnection.Get" + const location = "build.StepEditConnection.Get" // This step must be run in a Domain admin - domainRenderer := renderer.(*Domain) + domainBuilder := builder.(*Domain) // Collect parameters and services - providerID := renderer.QueryParam("provider") + providerID := builder.QueryParam("provider") - client := domainRenderer.Client(providerID) - adapter := domainRenderer.Provider(providerID) + client := domainBuilder.Client(providerID) + adapter := domainBuilder.Provider(providerID) // Try to find a Manual Provider for this Provider manualProvider, ok := adapter.(providers.ManualProvider) @@ -43,7 +43,7 @@ func (step StepEditConnection) Get(renderer Renderer, buffer io.Writer) Pipeline } // Wrap the form as a ModalForm and return - formHTML = WrapModalForm(renderer.response(), renderer.URL(), formHTML) + formHTML = WrapModalForm(builder.response(), builder.URL(), formHTML) // nolint:errcheck buffer.Write([]byte(formHTML)) @@ -51,24 +51,24 @@ func (step StepEditConnection) Get(renderer Renderer, buffer io.Writer) Pipeline return Halt().AsFullPage() } -func (step StepEditConnection) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepEditConnection) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepEditConnection.Post" + const location = "build.StepEditConnection.Post" // This step must be run in a Domain admin - domainRenderer := renderer.(Domain) + domainBuilder := builder.(Domain) postData := mapof.NewAny() - if err := bind(renderer.request(), &postData); err != nil { + if err := bind(builder.request(), &postData); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error parsing POST data")) } // Collect parameters and services - providerID := renderer.QueryParam("provider") + providerID := builder.QueryParam("provider") - client := domainRenderer.Client(providerID) - adapter := domainRenderer.Provider(providerID) + client := domainBuilder.Client(providerID) + adapter := domainBuilder.Provider(providerID) // Try to find a Manual Provider for this Provider manualProvider, ok := adapter.(providers.ManualProvider) @@ -86,21 +86,21 @@ func (step StepEditConnection) Post(renderer Renderer, _ io.Writer) PipelineBeha } // Run post-configuration scripts, if any - if err := adapter.AfterConnect(renderer.factory(), &client); err != nil { + if err := adapter.AfterConnect(builder.factory(), &client); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error installing client")) } // Prevent nil maps - if domainRenderer.domain.Clients == nil { - domainRenderer.domain.Clients = make(set.Map[model.Client]) + if domainBuilder.domain.Clients == nil { + domainBuilder.domain.Clients = make(set.Map[model.Client]) } - domainRenderer.domain.Clients.Put(client) + domainBuilder.domain.Clients.Put(client) // Try to save the domain object back to the database - domainService := domainRenderer.domainService() + domainService := domainBuilder.domainService() - if err := domainService.Save(*domainRenderer._domain, "Updated connection"); err != nil { + if err := domainService.Save(*domainBuilder._domain, "Updated connection"); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error saving domain object")) } diff --git a/build/step_EditContent.go b/build/step_EditContent.go new file mode 100644 index 000000000..a1297561a --- /dev/null +++ b/build/step_EditContent.go @@ -0,0 +1,73 @@ +package build + +import ( + "bytes" + "io" + + "github.com/EmissarySocial/emissary/model" + "github.com/benpate/derp" + "github.com/benpate/rosetta/mapof" +) + +// StepEditContent represents an action-step that can edit/update Container in a streamDraft. +type StepEditContent struct { + Filename string + Format string +} + +func (step StepEditContent) Get(builder Builder, buffer io.Writer) PipelineBehavior { + + if err := builder.executeTemplate(buffer, step.Filename, builder); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepEditContent.Get", "Error executing template")) + } + + return nil +} + +func (step StepEditContent) Post(builder Builder, _ io.Writer) PipelineBehavior { + + var rawContent string + + // Require that we're working with a Stream + stream, ok := builder.object().(*model.Stream) + + if !ok { + return Halt().WithError(derp.NewInternalError("build.StepEditContent.Post", "step: EditContent can only be used on a Stream")) + } + + // Try to read the content from the request body + switch step.Format { + + // EditorJS writes directly to the request body + case model.ContentFormatEditorJS: + var buffer bytes.Buffer + + if _, err := io.Copy(&buffer, builder.request().Body); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepEditContent.Post", "Error reading request data")) + } + + rawContent = buffer.String() + + // All other types are a Form post + default: + + body := mapof.NewAny() + if err := bind(builder.request(), &body); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepEditContent.Post", "Error parsing request data")) + } + + rawContent, _ = body.GetStringOK("content") + } + + // Set the new Content value in the Stream + contentService := builder.factory().Content() + stream.Content = contentService.New(step.Format, rawContent) + + // Try to save the object back to the database + if err := builder.service().ObjectSave(stream, "Content edited"); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepEditContent.Post", "Error saving stream")) + } + + // Success! + return nil +} diff --git a/render/step_EditModelObject.go b/build/step_EditModelObject.go similarity index 60% rename from render/step_EditModelObject.go rename to build/step_EditModelObject.go index d549b84cd..88387f9a5 100644 --- a/render/step_EditModelObject.go +++ b/build/step_EditModelObject.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -17,14 +17,14 @@ type StepEditModelObject struct { } // Get displays a modal form that lets users enter data for their new model object. -func (step StepEditModelObject) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepEditModelObject) Get(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepEditModelObject.Get" + const location = "build.StepEditModelObject.Get" - schema := renderer.schema() + schema := builder.schema() - // Try to render the Form HTML - result, err := form.Editor(schema, step.Form, renderer.object(), renderer.lookupProvider()) + // Try to build the Form HTML + result, err := form.Editor(schema, step.Form, builder.object(), builder.lookupProvider()) if err != nil { return Halt().WithError(derp.Wrap(err, location, "Error generating form")) @@ -33,10 +33,10 @@ func (step StepEditModelObject) Get(renderer Renderer, buffer io.Writer) Pipelin optionStrings := make([]string, 0, len(step.Options)) for _, option := range step.Options { - optionString := executeTemplate(option, renderer) + optionString := executeTemplate(option, builder) // Remove "delete" options from new objects. - if renderer.object().IsNew() && strings.HasPrefix(optionString, "delete:") { + if builder.object().IsNew() && strings.HasPrefix(optionString, "delete:") { continue } @@ -44,7 +44,7 @@ func (step StepEditModelObject) Get(renderer Renderer, buffer io.Writer) Pipelin optionStrings = append(optionStrings, optionString) } - result = WrapForm(renderer.URL(), result, optionStrings...) + result = WrapForm(builder.URL(), result, optionStrings...) // nolint:errcheck io.WriteString(buffer, result) @@ -53,27 +53,27 @@ func (step StepEditModelObject) Get(renderer Renderer, buffer io.Writer) Pipelin } // Post initializes a new model object, populates it with data from the form, then saves it to the database. -func (step StepEditModelObject) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepEditModelObject) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepEditModelObject.Post" + const location = "build.StepEditModelObject.Post" // Get the request body body := mapof.NewAny() - if err := bind(renderer.request(), &body); err != nil { + if err := bind(builder.request(), &body); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error binding request body")) } // Appy request body to the object (limited and validated by the form schema) - stepForm := form.New(renderer.schema(), step.Form) - object := renderer.object() + stepForm := form.New(builder.schema(), step.Form) + object := builder.object() - if err := stepForm.SetAll(object, body, renderer.lookupProvider()); err != nil { + if err := stepForm.SetAll(object, body, builder.lookupProvider()); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error applying request body to model object", body)) } // Save the object to the database - if err := renderer.service().ObjectSave(object, "Edited"); err != nil { + if err := builder.service().ObjectSave(object, "Edited"); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error saving model object to database")) } diff --git a/render/step_EditTable.go b/build/step_EditTable.go similarity index 62% rename from render/step_EditTable.go rename to build/step_EditTable.go index 99af61444..911a97c52 100644 --- a/render/step_EditTable.go +++ b/build/step_EditTable.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -16,22 +16,22 @@ type StepTableEditor struct { Form form.Element } -func (step StepTableEditor) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepTableEditor) Get(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepTableEditor.Get" + const location = "build.StepTableEditor.Get" var err error - s := renderer.schema() - factory := renderer.factory() + s := builder.schema() + factory := builder.factory() - targetURL := step.getTargetURL(renderer) - t := table.New(&s, &step.Form, renderer.object(), step.Path, factory.Icons(), targetURL) - t.UseLookupProvider(renderer.lookupProvider()) + targetURL := step.getTargetURL(builder) + t := table.New(&s, &step.Form, builder.object(), step.Path, factory.Icons(), targetURL) + t.UseLookupProvider(builder.lookupProvider()) t.AllowAll() - if editRow, ok := convert.IntOk(renderer.QueryParam("edit"), 0); ok { + if editRow, ok := convert.IntOk(builder.QueryParam("edit"), 0); ok { err = t.DrawEdit(editRow, buffer) - } else if add := renderer.QueryParam("add"); add != "" { + } else if add := builder.QueryParam("add"); add != "" { err = t.DrawAdd(buffer) } else { err = t.DrawView(buffer) @@ -44,21 +44,21 @@ func (step StepTableEditor) Get(renderer Renderer, buffer io.Writer) PipelineBeh return nil } -func (step StepTableEditor) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepTableEditor) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepTableEditor.Post" + const location = "build.StepTableEditor.Post" - s := renderer.schema() - object := renderer.object() + s := builder.schema() + object := builder.object() // Try to get the form post data body := mapof.NewAny() - if err := bindBody(renderer.request(), &body); err != nil { + if err := bindBody(builder.request(), &body); err != nil { return Halt().WithError(derp.Wrap(err, location, "Failed to bind body", step)) } - if edit := renderer.QueryParam("edit"); edit != "" { + if edit := builder.QueryParam("edit"); edit != "" { // Bounds checking editIndex, ok := convert.IntOk(edit, 0) @@ -81,7 +81,7 @@ func (step StepTableEditor) Post(renderer Renderer, _ io.Writer) PipelineBehavio } // Try to delete an existing record - } else if delete := renderer.QueryParam("delete"); delete != "" { + } else if delete := builder.QueryParam("delete"); delete != "" { table, err := s.Get(object, step.Path) @@ -101,20 +101,20 @@ func (step StepTableEditor) Post(renderer Renderer, _ io.Writer) PipelineBehavio } // Try to find the schema element for this table control - if ok := renderer.schema().Remove(renderer.object(), step.Path+"."+delete); !ok { + if ok := builder.schema().Remove(builder.object(), step.Path+"."+delete); !ok { return Halt().WithError(derp.NewInternalError(location, "Failed to remove row from table", step.Path)) } } - // Once we're done, re-render the table and send it back to the client - targetURL := step.getTargetURL(renderer) + // Once we're done, re-build the table and send it back to the client + targetURL := step.getTargetURL(builder) - factory := renderer.factory() - t := table.New(&s, &step.Form, renderer.object(), step.Path, factory.Icons(), targetURL) - t.UseLookupProvider(renderer.lookupProvider()) + factory := builder.factory() + t := table.New(&s, &step.Form, builder.object(), step.Path, factory.Icons(), targetURL) + t.UseLookupProvider(builder.lookupProvider()) t.AllowAll() - if err := t.DrawView(renderer.response()); err != nil { + if err := t.DrawView(builder.response()); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error building HTML")) } @@ -122,9 +122,9 @@ func (step StepTableEditor) Post(renderer Renderer, _ io.Writer) PipelineBehavio } // getTargetURL returns the URL that the table should use for all of its links -func (step StepTableEditor) getTargetURL(renderer Renderer) string { - originalPath := renderer.request().URL.Path - actionID := renderer.ActionID() +func (step StepTableEditor) getTargetURL(builder Builder) string { + originalPath := builder.request().URL.Path + actionID := builder.ActionID() pathSlice := strings.Split(originalPath, "/") pathSlice[len(pathSlice)-1] = actionID return strings.Join(pathSlice, "/") diff --git a/render/step_EditWidget.go b/build/step_EditWidget.go similarity index 62% rename from render/step_EditWidget.go rename to build/step_EditWidget.go index b8ecb9f0e..94ebe159c 100644 --- a/render/step_EditWidget.go +++ b/build/step_EditWidget.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -12,23 +12,23 @@ import ( // StepEditWidget represents an action-step that can update the data.DataMap custom data stored in a Stream type StepEditWidget struct{} -func (step StepEditWidget) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepEditWidget) Get(builder Builder, buffer io.Writer) PipelineBehavior { - widget, streamWidget, _, err := step.common(renderer) + widget, streamWidget, _, err := step.common(builder) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditWidget.Get", "Error locating widget")) + return Halt().WithError(derp.Wrap(err, "build.StepEditWidget.Get", "Error locating widget")) } // Render the Form formHTML, err := form.Editor(widget.Schema, widget.Form, streamWidget.Data, nil) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditWidget.Get", "Error rendering form")) + return Halt().WithError(derp.Wrap(err, "build.StepEditWidget.Get", "Error building form")) } // Wrap the form as a modal and return it to the client - formHTML = WrapModalForm(renderer.response(), renderer.URL(), formHTML) + formHTML = WrapModalForm(builder.response(), builder.URL(), formHTML) // nolint:errcheck buffer.Write([]byte(formHTML)) @@ -37,65 +37,65 @@ func (step StepEditWidget) Get(renderer Renderer, buffer io.Writer) PipelineBeha } // Post updates the stream with approved data from the request body. -func (step StepEditWidget) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepEditWidget) Post(builder Builder, _ io.Writer) PipelineBehavior { // Locate the widget and its configuration - widget, streamWidget, streamRenderer, err := step.common(renderer) + widget, streamWidget, streamBuilder, err := step.common(builder) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditWidget.Post", "Error locating widget")) + return Halt().WithError(derp.Wrap(err, "build.StepEditWidget.Post", "Error locating widget")) } // Get the form post information formData := mapof.NewAny() - if err := bind(renderer.request(), &formData); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditWidget.Post", "Error binding form data")) + if err := bind(builder.request(), &formData); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepEditWidget.Post", "Error binding form data")) } // Apply the form data to the widget f := form.New(widget.Schema, widget.Form) if err := f.SetAll(&streamWidget.Data, formData, nil); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditWidget.Post", "Error applying form data to widget")) + return Halt().WithError(derp.Wrap(err, "build.StepEditWidget.Post", "Error applying form data to widget")) } // Update the stream with the new widget (in the same location) - streamRenderer._stream.Widgets.Put(streamWidget) + streamBuilder._stream.Widgets.Put(streamWidget) return Continue().WithEvent("closeModal", "true") } // common locates the widget and its configuration -func (step StepEditWidget) common(renderer Renderer) (model.Widget, model.StreamWidget, *Stream, error) { +func (step StepEditWidget) common(builder Builder) (model.Widget, model.StreamWidget, *Stream, error) { - const location = "render.StepEditWidget.doStep" + const location = "build.StepEditWidget.doStep" // WithWidget can only be used on a Stream - streamRenderer, ok := renderer.(*Stream) + streamBuilder, ok := builder.(*Stream) if !ok { - return model.Widget{}, model.StreamWidget{}, nil, derp.NewInternalError(location, "Renderer is not a StreamRenderer") + return model.Widget{}, model.StreamWidget{}, nil, derp.NewInternalError(location, "Builder is not a StreamBuilder") } // User must be authenticated to view widget details - if !streamRenderer.IsAuthenticated() { + if !streamBuilder.IsAuthenticated() { return model.Widget{}, model.StreamWidget{}, nil, derp.NewUnauthorizedError(location, "Anonymous user is not authorized to perform this action") } // Get the token from the request - token := renderer.QueryParam("widgetId") + token := builder.QueryParam("widgetId") if token == "" { return model.Widget{}, model.StreamWidget{}, nil, derp.NewBadRequestError(location, "Missing required parameter: widgetId") } // Try to find the widget in the stream - streamWidget, ok := streamRenderer._stream.Widgets.Get(token) + streamWidget, ok := streamBuilder._stream.Widgets.Get(token) if !ok { return model.Widget{}, model.StreamWidget{}, nil, derp.NewBadRequestError(location, "Invalid widgetId", token) } - widgetService := streamRenderer.factory().Widget() + widgetService := streamBuilder.factory().Widget() widget, ok := widgetService.Get(streamWidget.Type) if !ok { @@ -112,5 +112,5 @@ func (step StepEditWidget) common(renderer Renderer) (model.Widget, model.Stream return model.Widget{}, model.StreamWidget{}, nil, derp.NewBadRequestError(location, "Widget does not support editing (empty schema)", streamWidget.Type) } - return widget, streamWidget, streamRenderer, nil + return widget, streamWidget, streamBuilder, nil } diff --git a/build/step_Error.go b/build/step_Error.go new file mode 100644 index 000000000..acab11a64 --- /dev/null +++ b/build/step_Error.go @@ -0,0 +1,20 @@ +package build + +import ( + "io" + + "github.com/EmissarySocial/emissary/model/step" + "github.com/benpate/derp" +) + +type StepError struct { + Original step.Step +} + +func (step StepError) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return Halt().WithError(derp.NewInternalError("build.StepError", "Unrecognized Pipeline Step", "This should never happen", builder.ActionID(), builder.Action(), builder.Action().Steps, builder.object(), step.Original)) +} + +func (step StepError) Post(builder Builder, _ io.Writer) PipelineBehavior { + return Halt().WithError(derp.NewInternalError("build.StepError", "Unrecognized Pipeline Step", "This should never happen", step.Original)) +} diff --git a/render/step_ForwardTo.go b/build/step_ForwardTo.go similarity index 63% rename from render/step_ForwardTo.go rename to build/step_ForwardTo.go index 812ddc2e1..cd52432a2 100644 --- a/render/step_ForwardTo.go +++ b/build/step_ForwardTo.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -13,17 +13,17 @@ type StepForwardTo struct { URL *template.Template } -func (step StepForwardTo) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepForwardTo) Get(builder Builder, buffer io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepForwardTo) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepForwardTo) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepForwardTo.Post" + const location = "build.StepForwardTo.Post" var nextPage bytes.Buffer - if err := step.URL.Execute(&nextPage, renderer); err != nil { + if err := step.URL.Execute(&nextPage, builder); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error evaluating 'url'")) } diff --git a/render/step_Halt.go b/build/step_Halt.go similarity index 53% rename from render/step_Halt.go rename to build/step_Halt.go index e1790d22e..c73b3d882 100644 --- a/render/step_Halt.go +++ b/build/step_Halt.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -7,11 +7,11 @@ import ( // StepHalt represents an action-step that can save changes to any object type StepHalt struct{} -func (step StepHalt) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepHalt) Get(builder Builder, _ io.Writer) PipelineBehavior { return Halt() } // Post saves the object to the database -func (step StepHalt) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepHalt) Post(builder Builder, _ io.Writer) PipelineBehavior { return Halt() } diff --git a/render/step_IfCondition.go b/build/step_IfCondition.go similarity index 53% rename from render/step_IfCondition.go rename to build/step_IfCondition.go index 24714a426..a4855005d 100644 --- a/render/step_IfCondition.go +++ b/build/step_IfCondition.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -18,39 +18,39 @@ type StepIfCondition struct { } // Get displays a form where users can update stream data -func (step StepIfCondition) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) +func (step StepIfCondition) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) } // Post updates the stream with approved data from the request body. -func (step StepIfCondition) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) +func (step StepIfCondition) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) } // Get displays a form where users can update stream data -func (step StepIfCondition) execute(renderer Renderer, buffer io.Writer, method ActionMethod) PipelineBehavior { +func (step StepIfCondition) execute(builder Builder, buffer io.Writer, method ActionMethod) PipelineBehavior { - const location = "renderer.StepIfCondition.execute" + const location = "builder.StepIfCondition.execute" - factory := renderer.factory() + factory := builder.factory() - if step.evaluateCondition(renderer) { - result := Pipeline(step.Then).Execute(factory, renderer, buffer, method) + if step.evaluateCondition(builder) { + result := Pipeline(step.Then).Execute(factory, builder, buffer, method) result.Error = derp.Wrap(result.Error, location, "Error executing 'then' sub-steps") return UseResult(result) } - result := Pipeline(step.Otherwise).Get(factory, renderer, buffer) + result := Pipeline(step.Otherwise).Get(factory, builder, buffer) result.Error = derp.Wrap(result.Error, location, "Error executing 'otherwise' sub-steps") return UseResult(result) } // evaluateCondition executes the conditional template and -func (step StepIfCondition) evaluateCondition(renderer Renderer) bool { +func (step StepIfCondition) evaluateCondition(builder Builder) bool { var result bytes.Buffer - if err := step.Condition.Execute(&result, renderer); err != nil { + if err := step.Condition.Execute(&result, builder); err != nil { return false } diff --git a/build/step_InlineError.go b/build/step_InlineError.go new file mode 100644 index 000000000..8ff8f83dc --- /dev/null +++ b/build/step_InlineError.go @@ -0,0 +1,26 @@ +package build + +import ( + "io" + "text/template" +) + +// StepInlineError represents an action-step that can build a Stream into HTML +type StepInlineError struct { + Message *template.Template +} + +// Get builds the Stream HTML to the context +func (step StepInlineError) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return nil +} + +func (step StepInlineError) Post(builder Builder, buffer io.Writer) PipelineBehavior { + result := executeTemplate(step.Message, builder) + + if _, err := buffer.Write([]byte(`` + result + ``)); err != nil { + return Halt().WithError(err) + } + + return Halt().WithHeader("HX-Reswap", "innerHTML").WithHeader("HX-Retarget", "#htmx-response-message") +} diff --git a/build/step_InlineSuccess.go b/build/step_InlineSuccess.go new file mode 100644 index 000000000..caaa2789a --- /dev/null +++ b/build/step_InlineSuccess.go @@ -0,0 +1,25 @@ +package build + +import ( + "io" + "text/template" +) + +// StepInlineSuccess represents an action-step that can build a Stream into HTML +type StepInlineSuccess struct { + Message *template.Template +} + +// Get builds the Stream HTML to the context +func (step StepInlineSuccess) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return nil +} + +func (step StepInlineSuccess) Post(builder Builder, buffer io.Writer) PipelineBehavior { + result := executeTemplate(step.Message, builder) + + if _, err := buffer.Write([]byte(`` + result + ``)); err != nil { + return Halt().WithError(err) + } + return Halt().WithHeader("HX-Reswap", "innerHTML").WithHeader("HX-Retarget", "#htmx-response-message") +} diff --git a/render/step_ProcessContent.go b/build/step_ProcessContent.go similarity index 65% rename from render/step_ProcessContent.go rename to build/step_ProcessContent.go index e7f1fd4f9..65a876cf0 100644 --- a/render/step_ProcessContent.go +++ b/build/step_ProcessContent.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -15,27 +15,27 @@ type StepProcessContent struct { AddLinks bool } -// Get renders the HTML for this step - either a modal template selector, or the embedded edit form -func (step StepProcessContent) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +// Get builds the HTML for this step - either a modal template selector, or the embedded edit form +func (step StepProcessContent) Get(builder Builder, buffer io.Writer) PipelineBehavior { return nil } -func (step StepProcessContent) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepProcessContent) Post(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepProcessContent.Post" + const location = "build.StepProcessContent.Post" // Require that we are working with a Stream object - streamRenderer, ok := renderer.(*Stream) + streamBuilder, ok := builder.(*Stream) if !ok { return Halt().WithError(derp.NewInternalError(location, "step: AddTags can only be used on a Stream")) } - factory := streamRenderer.factory() + factory := streamBuilder.factory() streamService := factory.Stream() contentService := factory.Content() - stream := streamRenderer._stream + stream := streamBuilder._stream if step.RemoveHTML { stream.Content.HTML = html.RemoveAnchors(stream.Content.HTML) diff --git a/build/step_PromoteDraft.go b/build/step_PromoteDraft.go new file mode 100644 index 000000000..ec9463b21 --- /dev/null +++ b/build/step_PromoteDraft.go @@ -0,0 +1,37 @@ +package build + +import ( + "io" + + "github.com/benpate/derp" +) + +// StepStreamPromoteDraft represents an action-step that can copy the Container from a StreamDraft into its corresponding Stream +type StepStreamPromoteDraft struct { + StateID string +} + +func (step StepStreamPromoteDraft) Get(builder Builder, _ io.Writer) PipelineBehavior { + return nil +} + +// Post copies relevant information from the draft into the primary stream, then deletes the draft +func (step StepStreamPromoteDraft) Post(builder Builder, _ io.Writer) PipelineBehavior { + + streamBuilder := builder.(*Stream) + + factory := builder.factory() + + // Try to load the draft from the database, overwriting the stream already in the builder + stream, err := factory.StreamDraft().Promote(builder.objectID(), step.StateID) + + if err != nil { + return Halt().WithError(derp.Wrap(err, "builder.StepStreamPromoteDraft.Post", "Error publishing draft")) + } + + // Push the newly updated stream back to the builder so that subsequent + // steps (e.g. publish) can use the correct data. + streamBuilder._stream = &stream + + return nil +} diff --git a/render/step_Publish.go b/build/step_Publish.go similarity index 58% rename from render/step_Publish.go rename to build/step_Publish.go index 84444346f..8cc80f0ba 100644 --- a/render/step_Publish.go +++ b/build/step_Publish.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -10,36 +10,36 @@ import ( // StepPublish represents an action-step that can update a stream's PublishDate with the current time. type StepPublish struct{} -func (step StepPublish) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepPublish) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with the current date as the "PublishDate" -func (step StepPublish) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepPublish) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepPublish.Post" + const location = "build.StepPublish.Post" // Require that the user is signed in to perform this action - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "User is not authenticated", nil)) } - streamRenderer := renderer.(*Stream) - factory := streamRenderer.factory() + streamBuilder := builder.(*Stream) + factory := streamBuilder.factory() // Try to load the User from the Database userService := factory.User() user := model.NewUser() - if err := userService.LoadByID(streamRenderer.AuthenticatedID(), &user); err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error loading user", streamRenderer.AuthenticatedID())) + if err := userService.LoadByID(streamBuilder.AuthenticatedID(), &user); err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error loading user", streamBuilder.AuthenticatedID())) } // Try to Publish the Stream to ActivityPub streamService := factory.Stream() - if err := streamService.Publish(&user, streamRenderer._stream); err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error publishing stream", streamRenderer._stream)) + if err := streamService.Publish(&user, streamBuilder._stream); err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error publishing stream", streamBuilder._stream)) } return nil diff --git a/render/step_RedirectTo.go b/build/step_RedirectTo.go similarity index 53% rename from render/step_RedirectTo.go rename to build/step_RedirectTo.go index a5638d42c..6d7963a93 100644 --- a/render/step_RedirectTo.go +++ b/build/step_RedirectTo.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -14,26 +14,26 @@ type StepRedirectTo struct { URL *template.Template } -func (step StepRedirectTo) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer) +func (step StepRedirectTo) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder) } // Post updates the stream with approved data from the request body. -func (step StepRedirectTo) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - return step.execute(renderer) +func (step StepRedirectTo) Post(builder Builder, _ io.Writer) PipelineBehavior { + return step.execute(builder) } // Redirect returns an HTTP 307 Temporary Redirect that works for both GET and POST methods -func (step StepRedirectTo) execute(renderer Renderer) PipelineBehavior { +func (step StepRedirectTo) execute(builder Builder) PipelineBehavior { - const location = "render.StepRedirectTo.execute" + const location = "build.StepRedirectTo.execute" var nextPage bytes.Buffer - if err := step.URL.Execute(&nextPage, renderer); err != nil { + if err := step.URL.Execute(&nextPage, builder); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error evaluating 'url'")) } - if err := redirect(renderer.response(), http.StatusTemporaryRedirect, nextPage.String()); err != nil { + if err := redirect(builder.response(), http.StatusTemporaryRedirect, nextPage.String()); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error redirecting to new page")) } diff --git a/render/step_RefreshPage.go b/build/step_RefreshPage.go similarity index 62% rename from render/step_RefreshPage.go rename to build/step_RefreshPage.go index 499d13e8a..fa5ac99e2 100644 --- a/render/step_RefreshPage.go +++ b/build/step_RefreshPage.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -7,12 +7,12 @@ import ( // StepRefreshPage represents an action-step that forwards the user to a new page. type StepRefreshPage struct{} -func (step StepRefreshPage) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepRefreshPage) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepRefreshPage) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepRefreshPage) Post(builder Builder, _ io.Writer) PipelineBehavior { return Continue(). WithEvent("closeModal", "true"). WithEvent("refreshPage", "true") diff --git a/render/step_ReloadPage.go b/build/step_ReloadPage.go similarity index 59% rename from render/step_ReloadPage.go rename to build/step_ReloadPage.go index 6e287b316..7ef58ef94 100644 --- a/render/step_ReloadPage.go +++ b/build/step_ReloadPage.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -7,11 +7,11 @@ import ( // StepReloadPage represents an action-step that forwards the user to a new page. type StepReloadPage struct{} -func (step StepReloadPage) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepReloadPage) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepReloadPage) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepReloadPage) Post(builder Builder, _ io.Writer) PipelineBehavior { return Continue().WithHeader("HX-Refresh", "true") } diff --git a/render/step_RemoveEvent.go b/build/step_RemoveEvent.go similarity index 67% rename from render/step_RemoveEvent.go rename to build/step_RemoveEvent.go index d062919a9..7feee8434 100644 --- a/render/step_RemoveEvent.go +++ b/build/step_RemoveEvent.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -9,11 +9,11 @@ type StepRemoveEvent struct { Event string } -func (step StepRemoveEvent) Get(_ Renderer, _ io.Writer) PipelineBehavior { +func (step StepRemoveEvent) Get(_ Builder, _ io.Writer) PipelineBehavior { return Continue().RemoveEvent(step.Event) } // Post updates the stream with approved data from the request body. -func (step StepRemoveEvent) Post(_ Renderer, _ io.Writer) PipelineBehavior { +func (step StepRemoveEvent) Post(_ Builder, _ io.Writer) PipelineBehavior { return Continue().RemoveEvent(step.Event) } diff --git a/render/step_Save.go b/build/step_Save.go similarity index 61% rename from render/step_Save.go rename to build/step_Save.go index 78f3ff4cc..9a8f9fac0 100644 --- a/render/step_Save.go +++ b/build/step_Save.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,21 +13,21 @@ type StepSave struct { Comment *template.Template } -func (step StepSave) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSave) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post saves the object to the database -func (step StepSave) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSave) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepSave.Post" + const location = "build.StepSave.Post" - modelService := renderer.service() - object := renderer.object() - comment := executeTemplate(step.Comment, renderer) + modelService := builder.service() + object := builder.object() + comment := executeTemplate(step.Comment, builder) if setter, ok := modelService.(service.AuthorSetter); ok { - if err := setter.SetAuthor(object, renderer.AuthenticatedID()); err != nil { + if err := setter.SetAuthor(object, builder.AuthenticatedID()); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error setting author")) } } diff --git a/build/step_SendEmail.go b/build/step_SendEmail.go new file mode 100644 index 000000000..0e397bdb1 --- /dev/null +++ b/build/step_SendEmail.go @@ -0,0 +1,47 @@ +package build + +import ( + "io" + + "github.com/benpate/derp" +) + +// StepSendEmail represents an action-step that can send a named email to a recipient +type StepSendEmail struct { + Email string +} + +func (step StepSendEmail) Get(_ Builder, _ io.Writer) PipelineBehavior { + return nil +} + +// Post saves the object to the database +func (step StepSendEmail) Post(builder Builder, _ io.Writer) PipelineBehavior { + factory := builder.factory() + emailService := factory.Email() + + userBuilder, ok := builder.(User) + + if !ok { + return Halt().WithError(derp.NewInternalError("build.StepSendEmail.Post", "Invalid Builder", "Builder must be Admin/User")) + } + + switch step.Email { + + case "welcome": + + if err := emailService.SendWelcome(userBuilder._user); err != nil { + return Halt().WithError(err) + } + + case "password-reset": + if err := emailService.SendPasswordReset(userBuilder._user); err != nil { + return Halt().WithError(err) + } + + default: + return Halt().WithError(derp.NewInternalError("build.StepSendEmail.Post", "Invalid email name", "Name must be 'welcome' or 'password-reset'")) + } + + return nil +} diff --git a/build/step_ServerRedirect.go b/build/step_ServerRedirect.go new file mode 100644 index 000000000..59b8377a2 --- /dev/null +++ b/build/step_ServerRedirect.go @@ -0,0 +1,54 @@ +package build + +import ( + "io" + + "github.com/benpate/derp" +) + +// StepServerRedirect represents an action-step that continues building the output stream as +// a GET request to a new action. +type StepServerRedirect struct { + On string // "get" or "post" or "both" + Action string +} + +func (step StepServerRedirect) Get(builder Builder, buffer io.Writer) PipelineBehavior { + + if step.On == "post" { + return nil + } + + return step.redirect(builder, buffer) +} + +// Post updates the stream with approved data from the request body. +func (step StepServerRedirect) Post(builder Builder, _ io.Writer) PipelineBehavior { + if step.On == "get" { + return nil + } + + return step.redirect(builder, builder.response()) +} + +// redirect creates a new builder on this object with the requested Action and then continues as a GET request. +func (step StepServerRedirect) redirect(builder Builder, buffer io.Writer) PipelineBehavior { + + newBuilder, err := builder.clone(step.Action) + + if err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepServerRedirect.Redirect", "Error creating new builder")) + } + + result, err := newBuilder.Render() + + if err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepServerRedirect.Redirect", "Error building new page")) + } + + if _, err := buffer.Write([]byte(result)); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepServerRedirect.Redirect", "Error writing output buffer")) + } + + return nil +} diff --git a/render/step_SetData.go b/build/step_SetData.go similarity index 67% rename from render/step_SetData.go rename to build/step_SetData.go index a95613989..60415362f 100644 --- a/render/step_SetData.go +++ b/build/step_SetData.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -18,33 +18,33 @@ type StepSetData struct { Defaults mapof.Any // values to set into the object IFF they are currently empty. } -func (step StepSetData) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepSetData) Get(builder Builder, buffer io.Writer) PipelineBehavior { - if err := step.setURLPaths(renderer); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSetData.Get", "Error setting data from URL")) + if err := step.setURLPaths(builder); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepSetData.Get", "Error setting data from URL")) } return nil } // Post updates the stream with approved data from the request body. -func (step StepSetData) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSetData) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepSetData.Post" + const location = "build.StepSetData.Post" - if err := step.setURLPaths(renderer); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSetData.Get", "Error setting data from URL")) + if err := step.setURLPaths(builder); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepSetData.Get", "Error setting data from URL")) } - object := renderer.object() - schema := renderer.schema() + object := builder.object() + schema := builder.schema() if len(step.FromForm) > 0 { transaction := mapof.NewAny() // Collect form POST information - if err := bindBody(renderer.request(), &transaction); err != nil { + if err := bindBody(builder.request(), &transaction); err != nil { result := derp.Wrap(err, location, "Error binding body", derp.WithCode(http.StatusBadRequest)) return Halt().WithError(result) } @@ -60,7 +60,7 @@ func (step StepSetData) Post(renderer Renderer, _ io.Writer) PipelineBehavior { // Put values from template.json into the stream for key, value := range step.Values { - valueString := executeTemplate(value, renderer) + valueString := executeTemplate(value, builder) if err := schema.Set(object, key, valueString); err != nil { result := derp.Wrap(err, location, "Error setting value from template.json", key, derp.WithCode(http.StatusBadRequest)) return Halt().WithError(result) @@ -69,7 +69,7 @@ func (step StepSetData) Post(renderer Renderer, _ io.Writer) PipelineBehavior { // Set default values (only if no value already exists) for name, value := range step.Defaults { - currentValue, _ := schema.Get(renderer, name) + currentValue, _ := schema.Get(builder, name) if compare.IsZero(currentValue) { if err := schema.Set(object, name, value); err != nil { result := derp.Wrap(err, location, "Error setting default value", name, value, derp.WithCode(http.StatusBadRequest)) @@ -82,16 +82,16 @@ func (step StepSetData) Post(renderer Renderer, _ io.Writer) PipelineBehavior { return nil } -func (step StepSetData) setURLPaths(renderer Renderer) error { +func (step StepSetData) setURLPaths(builder Builder) error { if len(step.FromURL) > 0 { - query := renderer.request().URL.Query() - schema := renderer.schema() - object := renderer.object() + query := builder.request().URL.Query() + schema := builder.schema() + object := builder.object() for _, path := range step.FromURL { if value := query.Get(path); value != "" { if err := schema.Set(object, path, value); err != nil { - result := derp.Wrap(err, "render.StepSetData.setURLPaths", "Error setting data from URL", derp.WithCode(http.StatusBadRequest)) + result := derp.Wrap(err, "build.StepSetData.setURLPaths", "Error setting data from URL", derp.WithCode(http.StatusBadRequest)) return result } } diff --git a/build/step_SetHeader.go b/build/step_SetHeader.go new file mode 100644 index 000000000..dc7cd57f1 --- /dev/null +++ b/build/step_SetHeader.go @@ -0,0 +1,44 @@ +package build + +import ( + "bytes" + "io" + "text/template" + + "github.com/benpate/derp" +) + +// StepSetHeader represents an action-step that can update the custom data stored in a Stream +type StepSetHeader struct { + Method string + Name string + Value *template.Template +} + +func (step StepSetHeader) Get(builder Builder, buffer io.Writer) PipelineBehavior { + if step.Method == "post" { + return nil + } + return step.setHeader(builder) +} + +// Post updates the stream with approved data from the request body. +func (step StepSetHeader) Post(builder Builder, _ io.Writer) PipelineBehavior { + if step.Method == "get" { + return nil + } + return step.setHeader(builder) +} + +func (step StepSetHeader) setHeader(builder Builder) PipelineBehavior { + + var value bytes.Buffer + + if err := step.Value.Execute(&value, builder); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepSetHeader.Post", "Error executing template", step.Value)) + } + + builder.response().Header().Set(step.Name, value.String()) + + return nil +} diff --git a/build/step_SetQueryParam.go b/build/step_SetQueryParam.go new file mode 100644 index 000000000..ce9a6ed63 --- /dev/null +++ b/build/step_SetQueryParam.go @@ -0,0 +1,33 @@ +package build + +import ( + "io" + "text/template" +) + +// StepSetQueryParam represents an action-step that sets values to the request query string +type StepSetQueryParam struct { + Values map[string]*template.Template +} + +// Get displays a form where users can update stream data +func (step StepSetQueryParam) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.Do(builder) +} + +// Post updates the stream with approved data from the request body. +func (step StepSetQueryParam) Post(builder Builder, _ io.Writer) PipelineBehavior { + return step.Do(builder) +} + +func (step StepSetQueryParam) Do(builder Builder) PipelineBehavior { + query := builder.request().URL.Query() + + for key, value := range step.Values { + queryValue := executeTemplate(value, builder) + query.Set(key, queryValue) + } + + builder.request().URL.RawQuery = query.Encode() + return nil +} diff --git a/build/step_SetRenderData.go b/build/step_SetRenderData.go new file mode 100644 index 000000000..114b7d566 --- /dev/null +++ b/build/step_SetRenderData.go @@ -0,0 +1,30 @@ +package build + +import ( + "io" + "text/template" +) + +// StepSetRenderData represents an action-step that sets values to the request query string +type StepSetRenderData struct { + Values map[string]*template.Template +} + +// Get displays a form where users can update stream data +func (step StepSetRenderData) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.Do(builder) +} + +// Post updates the stream with approved data from the request body. +func (step StepSetRenderData) Post(builder Builder, _ io.Writer) PipelineBehavior { + return step.Do(builder) +} + +func (step StepSetRenderData) Do(builder Builder) PipelineBehavior { + for key, value := range step.Values { + queryValue := executeTemplate(value, builder) + builder.setString(key, queryValue) + } + + return nil +} diff --git a/render/step_SetResponse.go b/build/step_SetResponse.go similarity index 79% rename from render/step_SetResponse.go rename to build/step_SetResponse.go index bedda8276..63a14ddbc 100644 --- a/render/step_SetResponse.go +++ b/build/step_SetResponse.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -16,30 +16,30 @@ type StepSetResponseTransaction struct { Exists string `json:"exists" form:"exists"` // If TRUE, then create/update the response. If FALSE, remove it. } -func (step StepSetResponse) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepSetResponse) Get(builder Builder, buffer io.Writer) PipelineBehavior { return nil } -func (step StepSetResponse) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSetResponse) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepSetResponse.Post" + const location = "build.StepSetResponse.Post" transaction := StepSetResponseTransaction{} // Receive the transaction data - if err := bind(renderer.request(), &transaction); err != nil { + if err := bind(builder.request(), &transaction); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error binding transaction")) } // Retrieve the currently authenticated user - user, err := renderer.getUser() + user, err := builder.getUser() if err != nil { return Halt().WithError(derp.Wrap(err, location, "Error getting user")) } // Set the value in the database - responseService := renderer.factory().Response() + responseService := builder.factory().Response() // Create/Update the response if convert.Bool(transaction.Exists) { diff --git a/render/step_SetSimpleSharing.go b/build/step_SetSimpleSharing.go similarity index 80% rename from render/step_SetSimpleSharing.go rename to build/step_SetSimpleSharing.go index 3c0c16b66..c82ee6aaa 100644 --- a/render/step_SetSimpleSharing.go +++ b/build/step_SetSimpleSharing.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -21,16 +21,16 @@ type StepSetSimpleSharing struct { Roles []string } -func (step StepSetSimpleSharing) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepSetSimpleSharing) Get(builder Builder, buffer io.Writer) PipelineBehavior { - streamRenderer := renderer.(*Stream) - model := streamRenderer._stream.SimplePermissionModel() + streamBuilder := builder.(*Stream) + model := streamBuilder._stream.SimplePermissionModel() // Try to write form HTML - formHTML, err := form.Editor(step.schema(), step.form(), model, renderer.lookupProvider()) + formHTML, err := form.Editor(step.schema(), step.form(), model, builder.lookupProvider()) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSetSimpleSharing.Get", "Error rendering form")) + return Halt().WithError(derp.Wrap(err, "build.StepSetSimpleSharing.Get", "Error building form")) } // Write the rest of the HTML that contains the form @@ -42,7 +42,7 @@ func (step StepSetSimpleSharing) Get(renderer Renderer, buffer io.Writer) Pipeli // Form b.Form("", ""). - Data("hx-post", renderer.URL()). + Data("hx-post", builder.URL()). Data("hx-swap", "none"). Data("hx-push-url", "false"). Script("init send checkFormRules(changed:me as Values)"). @@ -59,15 +59,15 @@ func (step StepSetSimpleSharing) Get(renderer Renderer, buffer io.Writer) Pipeli return nil } -func (step StepSetSimpleSharing) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSetSimpleSharing) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepSetSimpleSharing.Post" + const location = "build.StepSetSimpleSharing.Post" - request := renderer.request() + request := builder.request() // Try to parse the form input if err := request.ParseForm(); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSetSimpleSharing", "Error parsing form input")) + return Halt().WithError(derp.Wrap(err, "build.StepSetSimpleSharing", "Error parsing form input")) } var groupIDs []primitive.ObjectID @@ -89,8 +89,8 @@ func (step StepSetSimpleSharing) Post(renderer Renderer, _ io.Writer) PipelineBe } // Build the stream criteria - streamRenderer := renderer.(*Stream) - stream := streamRenderer._stream + streamBuilder := builder.(*Stream) + stream := streamBuilder._stream stream.Permissions = model.NewStreamPermissions() for _, groupID := range groupIDs { diff --git a/build/step_SetState.go b/build/step_SetState.go new file mode 100644 index 000000000..dac585322 --- /dev/null +++ b/build/step_SetState.go @@ -0,0 +1,36 @@ +package build + +import ( + "io" + + "github.com/benpate/derp" +) + +// StepSetState represents an action-step that can change a Stream's state +type StepSetState struct { + State string +} + +func (step StepSetState) Get(builder Builder, _ io.Writer) PipelineBehavior { + return nil +} + +// Post updates the stream with configured data, and moves the stream to a new state +func (step StepSetState) Post(builder Builder, _ io.Writer) PipelineBehavior { + + // If the builder is a StateSetter, then try to update the state + if setter, ok := builder.(StateSetter); ok { + + // This action may still fail (for instance) if the builder wraps + // a model object that is not a `model.StateSetter` + if err := setter.setState(step.State); err != nil { + return Halt().WithError(derp.Wrap(err, "build.stepSetState.Post", "Error setting state")) + } + + // Success + return nil + } + + // Failure (obv) + return Halt().WithError(derp.NewInternalError("build.stepSetState.Post", "Builder does not implement StateSetter interface")) +} diff --git a/render/step_SetThumbnail.go b/build/step_SetThumbnail.go similarity index 58% rename from render/step_SetThumbnail.go rename to build/step_SetThumbnail.go index 25e1990fb..d37368106 100644 --- a/render/step_SetThumbnail.go +++ b/build/step_SetThumbnail.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -11,29 +11,29 @@ type StepSetThumbnail struct { Path string } -func (step StepSetThumbnail) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSetThumbnail) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepSetThumbnail) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSetThumbnail) Post(builder Builder, _ io.Writer) PipelineBehavior { // Find best icon from attachments - factory := renderer.factory() + factory := builder.factory() - objectType := renderer.service().ObjectType() - objectID := renderer.objectID() - object := renderer.object() + objectType := builder.service().ObjectType() + objectID := builder.objectID() + object := builder.object() attachments, err := factory.Attachment().QueryByObjectID(objectType, objectID) if err != nil { - return Halt().WithError(derp.NewBadRequestError("render.StepSetThumbnail.Post", "Error listing attachments")) + return Halt().WithError(derp.NewBadRequestError("build.StepSetThumbnail.Post", "Error listing attachments")) } // Scan all attachments and use the first one that is an image. - schema := renderer.schema() + schema := builder.schema() for _, attachment := range attachments { if attachment.MimeCategory() == "image" { @@ -41,17 +41,17 @@ func (step StepSetThumbnail) Post(renderer Renderer, _ io.Writer) PipelineBehavi // Special case for User objects (this should always be "imageId") if objectType == "User" { if err := schema.Set(object, step.Path, attachment.AttachmentID.Hex()); err != nil { - return Halt().WithError(derp.NewInternalError("render.StepSetThumbnail.Post", "Invalid path for non-user object (A)", step.Path)) + return Halt().WithError(derp.NewInternalError("build.StepSetThumbnail.Post", "Invalid path for non-user object (A)", step.Path)) } return nil } // Standard path for all other records - imageURL := renderer.Permalink() + imageURL := builder.Permalink() imageURL = imageURL + "/attachments/" + attachment.AttachmentID.Hex() if err := schema.Set(object, step.Path, imageURL); err != nil { - return Halt().WithError(derp.NewInternalError("render.StepSetThumbnail.Post", "Invalid path for non-user object (B)", step.Path)) + return Halt().WithError(derp.NewInternalError("build.StepSetThumbnail.Post", "Invalid path for non-user object (B)", step.Path)) } return nil } @@ -59,7 +59,7 @@ func (step StepSetThumbnail) Post(renderer Renderer, _ io.Writer) PipelineBehavi // Fall through means that we can't find any images. Set the Thumbnail to an empty string. if err := schema.Set(object, step.Path, ""); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSetThumbnail.Post", "Error setting thumbnail")) + return Halt().WithError(derp.Wrap(err, "build.StepSetThumbnail.Post", "Error setting thumbnail")) } // Success! diff --git a/render/step_Sort.go b/build/step_Sort.go similarity index 52% rename from render/step_Sort.go rename to build/step_Sort.go index 4fc8789a1..74ccb240a 100644 --- a/render/step_Sort.go +++ b/build/step_Sort.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -15,20 +15,20 @@ type StepSort struct { Message string } -func (step StepSort) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSort) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepSort) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSort) Post(builder Builder, _ io.Writer) PipelineBehavior { var transaction struct { Keys []string `form:"keys"` } // Collect form POST information - if err := bind(renderer.request(), &transaction); err != nil { - return Halt().WithError(derp.NewBadRequestError("render.StepSort.Post", "Error binding body")) + if err := bind(builder.request(), &transaction); err != nil { + return Halt().WithError(derp.NewBadRequestError("build.StepSort.Post", "Error binding body")) } for index, id := range transaction.Keys { @@ -40,26 +40,26 @@ func (step StepSort) Post(renderer Renderer, _ io.Writer) PipelineBehavior { objectID, err := primitive.ObjectIDFromHex(id) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSort.Post", "Invalid objectId", id)) + return Halt().WithError(derp.Wrap(err, "build.StepSort.Post", "Invalid objectId", id)) } criteria := exp.Equal(step.Keys, objectID) // Try to load the object from the database - object, err := renderer.service().ObjectLoad(criteria) + object, err := builder.service().ObjectLoad(criteria) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSort.Post", "Error loading object with criteria: ", criteria)) + return Halt().WithError(derp.Wrap(err, "build.StepSort.Post", "Error loading object with criteria: ", criteria)) } // Use the object schema to set the new sort rank - if err := renderer.schema().Set(object, "rank", newRank); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSort.Post", "Error setting new rank", objectID, step.Values, newRank)) + if err := builder.schema().Set(object, "rank", newRank); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepSort.Post", "Error setting new rank", objectID, step.Values, newRank)) } // Try to save back to the database - if err := renderer.service().ObjectSave(object, step.Message); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSort.Post", "Error saving record tot he database", object)) + if err := builder.service().ObjectSave(object, step.Message); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepSort.Post", "Error saving record tot he database", object)) } } diff --git a/render/step_SortAttachments.go b/build/step_SortAttachments.go similarity index 62% rename from render/step_SortAttachments.go rename to build/step_SortAttachments.go index cf5bedc54..999daecae 100644 --- a/render/step_SortAttachments.go +++ b/build/step_SortAttachments.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -16,23 +16,23 @@ type StepSortAttachments struct { Message string } -func (step StepSortAttachments) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSortAttachments) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepSortAttachments) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSortAttachments) Post(builder Builder, _ io.Writer) PipelineBehavior { var transaction struct { Keys []string `form:"keys"` } // Collect form POST information - if err := bind(renderer.request(), &transaction); err != nil { - return Halt().WithError(derp.NewBadRequestError("render.StepSortAttachments.Post", "Error binding body")) + if err := bind(builder.request(), &transaction); err != nil { + return Halt().WithError(derp.NewBadRequestError("build.StepSortAttachments.Post", "Error binding body")) } - factory := renderer.factory() + factory := builder.factory() attachmentService := factory.Attachment() for index, id := range transaction.Keys { @@ -44,16 +44,16 @@ func (step StepSortAttachments) Post(renderer Renderer, _ io.Writer) PipelineBeh attachmentID, err := primitive.ObjectIDFromHex(id) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSortAttachments.Post", "Invalid attachmentId", id)) + return Halt().WithError(derp.Wrap(err, "build.StepSortAttachments.Post", "Invalid attachmentId", id)) } - criteria := exp.Equal("streamId", renderer.objectID()). + criteria := exp.Equal("streamId", builder.objectID()). AndEqual(step.Keys, attachmentID). AndEqual("deleteDate", 0) // Try to load the attachment from the database if err := attachmentService.Load(criteria, &attachment); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSortAttachments.Post", "Error loading attachment with criteria: ", criteria)) + return Halt().WithError(derp.Wrap(err, "build.StepSortAttachments.Post", "Error loading attachment with criteria: ", criteria)) } // If the rank for this attachment has not changed, then don't waste time saving it again. @@ -65,7 +65,7 @@ func (step StepSortAttachments) Post(renderer Renderer, _ io.Writer) PipelineBeh // Try to save back to the database if err := attachmentService.Save(&attachment, step.Message); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSortAttachments.Post", "Error saving record tot he database", attachment)) + return Halt().WithError(derp.Wrap(err, "build.StepSortAttachments.Post", "Error saving record tot he database", attachment)) } } diff --git a/render/step_SortWidgets.go b/build/step_SortWidgets.go similarity index 69% rename from render/step_SortWidgets.go rename to build/step_SortWidgets.go index 4f7112b59..aef16dd94 100644 --- a/render/step_SortWidgets.go +++ b/build/step_SortWidgets.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,32 +13,32 @@ import ( // StepSortWidgets represents an action-step that can edit/update Container in a streamDraft. type StepSortWidgets struct{} -func (step StepSortWidgets) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepSortWidgets) Get(builder Builder, buffer io.Writer) PipelineBehavior { return nil } -func (step StepSortWidgets) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepSortWidgets) Post(builder Builder, _ io.Writer) PipelineBehavior { - streamRenderer, ok := renderer.(*Stream) + streamBuilder, ok := builder.(*Stream) if !ok { - return Halt().WithError(derp.NewInternalError("render.StepSortWidgets.Post", "edit-widgets can only be used on Stream transaction")) + return Halt().WithError(derp.NewInternalError("build.StepSortWidgets.Post", "edit-widgets can only be used on Stream transaction")) } // Collect required services - factory := streamRenderer._factory + factory := streamBuilder._factory widgetService := factory.Widget() // Collect transaction from form POST transaction := mapof.NewString() - if err := bind(renderer.request(), &transaction); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSortWidgets.Post", "Error binding form transaction")) + if err := bind(builder.request(), &transaction); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepSortWidgets.Post", "Error binding form transaction")) } // Set up some variables - stream := streamRenderer._stream - template := streamRenderer.template() + stream := streamBuilder._stream + template := streamBuilder.template() newWidgets := model.NewStreamWidgets() // Find and organize the selected widgets diff --git a/render/step_TriggerEvent.go b/build/step_TriggerEvent.go similarity index 58% rename from render/step_TriggerEvent.go rename to build/step_TriggerEvent.go index 412f603d3..9030986c5 100644 --- a/render/step_TriggerEvent.go +++ b/build/step_TriggerEvent.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -11,12 +11,12 @@ type StepTriggerEvent struct { Value *template.Template } -func (step StepTriggerEvent) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepTriggerEvent) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepTriggerEvent) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - value := executeTemplate(step.Value, renderer) +func (step StepTriggerEvent) Post(builder Builder, _ io.Writer) PipelineBehavior { + value := executeTemplate(step.Value, builder) return Continue().WithEvent(step.Event, value) } diff --git a/render/step_UnPublish.go b/build/step_UnPublish.go similarity index 58% rename from render/step_UnPublish.go rename to build/step_UnPublish.go index dac43b2bb..459fcda9c 100644 --- a/render/step_UnPublish.go +++ b/build/step_UnPublish.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -12,36 +12,36 @@ type StepUnPublish struct { Role string } -func (step StepUnPublish) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepUnPublish) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } // Post updates the stream with the current date as the "PublishDate" -func (step StepUnPublish) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepUnPublish) Post(builder Builder, _ io.Writer) PipelineBehavior { - const location = "render.StepUnPublish.Post" + const location = "build.StepUnPublish.Post" // Require that the user is signed in to perform this action - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "User is not authenticated", nil)) } - streamRenderer := renderer.(*Stream) - factory := streamRenderer.factory() + streamBuilder := builder.(*Stream) + factory := streamBuilder.factory() // Try to load the User from the Database userService := factory.User() user := model.NewUser() - if err := userService.LoadByID(streamRenderer.AuthenticatedID(), &user); err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error loading user", streamRenderer.AuthenticatedID())) + if err := userService.LoadByID(streamBuilder.AuthenticatedID(), &user); err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error loading user", streamBuilder.AuthenticatedID())) } // Try to Publish the Stream to ActivityPub streamService := factory.Stream() - if err := streamService.UnPublish(&user, streamRenderer._stream); err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error publishing stream", streamRenderer._stream)) + if err := streamService.UnPublish(&user, streamBuilder._stream); err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error publishing stream", streamBuilder._stream)) } return nil diff --git a/render/step_UploadAttachment.go b/build/step_UploadAttachment.go similarity index 85% rename from render/step_UploadAttachment.go rename to build/step_UploadAttachment.go index b35873f27..105145293 100644 --- a/render/step_UploadAttachment.go +++ b/build/step_UploadAttachment.go @@ -1,4 +1,4 @@ -package render +package build import ( "encoding/json" @@ -9,19 +9,19 @@ import ( "github.com/benpate/rosetta/mapof" ) -// StepUploadAttachment represents an action that can upload attachments. It can only be used on a StreamRenderer +// StepUploadAttachment represents an action that can upload attachments. It can only be used on a StreamBuilder type StepUploadAttachment struct { Maximum int } -func (step StepUploadAttachment) Get(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepUploadAttachment) Get(builder Builder, _ io.Writer) PipelineBehavior { return nil } -func (step StepUploadAttachment) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepUploadAttachment) Post(builder Builder, buffer io.Writer) PipelineBehavior { // Read the multipart form from the request - form, err := multipartForm(renderer.request()) + form, err := multipartForm(builder.request()) if err != nil { return Halt().WithError(derp.Wrap(err, "handler.StepUploadAttachment.Post", "Error reading multipart form.")) @@ -37,11 +37,11 @@ func (step StepUploadAttachment) Post(renderer Renderer, buffer io.Writer) Pipel step.Maximum = 1 } - factory := renderer.factory() + factory := builder.factory() attachmentService := factory.Attachment() - objectID := renderer.objectID() - objectType := renderer.service().ObjectType() + objectID := builder.objectID() + objectType := builder.service().ObjectType() // Special case: If we're uploading a draft, then we need to attach the document to the parent stream. if objectType == "StreamDraft" { @@ -83,12 +83,12 @@ func (step StepUploadAttachment) Post(renderer Renderer, buffer io.Writer) Pipel response := mapof.Any{ "success": 1, "file": mapof.Any{ - "url": attachment.CalcURL(renderer.Host()), + "url": attachment.CalcURL(builder.Host()), "height": attachment.Height, "width": attachment.Width, }, "data": mapof.Any{ - "filePath": attachment.CalcURL(renderer.Host()), + "filePath": attachment.CalcURL(builder.Host()), }, } diff --git a/render/step_ViewFeed.go b/build/step_ViewFeed.go similarity index 64% rename from render/step_ViewFeed.go rename to build/step_ViewFeed.go index 354e303e9..6fb31a1f6 100644 --- a/render/step_ViewFeed.go +++ b/build/step_ViewFeed.go @@ -1,4 +1,4 @@ -package render +package build import ( "encoding/json" @@ -16,35 +16,35 @@ import ( accept "github.com/timewasted/go-accept-headers" ) -// StepViewFeed represents an action-step that can render a Stream into HTML +// StepViewFeed represents an action-step that can build a Stream into HTML type StepViewFeed struct{} -// Get renders the Stream HTML to the context -func (step StepViewFeed) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +// Get builds the Stream HTML to the context +func (step StepViewFeed) Get(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepViewFeed.Get" + const location = "build.StepViewFeed.Get" - factory := renderer.factory() + factory := builder.factory() // Get all child streams from the database - children, err := factory.Stream().ListByParent(renderer.objectID()) + children, err := factory.Stream().ListByParent(builder.objectID()) if err != nil { return Halt().WithError(derp.Wrap(err, location, "Error querying child streams")) } - mimeType := step.detectMimeType(renderer) + mimeType := step.detectMimeType(builder) // Special case for JSONFeed if mimeType == model.MimeTypeJSONFeed { - return step.asJSONFeed(renderer, buffer, children) + return step.asJSONFeed(builder, buffer, children) } // Initialize the result RSS feed result := feeds.Feed{ - Title: renderer.PageTitle(), - Description: renderer.Summary(), - Link: &feeds.Link{Href: renderer.Permalink()}, + Title: builder.PageTitle(), + Description: builder.Summary(), + Link: &feeds.Link{Href: builder.Permalink()}, Author: &feeds.Author{Name: ""}, Created: time.Now(), } @@ -78,14 +78,14 @@ func (step StepViewFeed) Get(renderer Renderer, buffer io.Writer) PipelineBehavi } } -func (step StepViewFeed) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepViewFeed) Post(builder Builder, _ io.Writer) PipelineBehavior { return nil } -func (step StepViewFeed) detectMimeType(renderer Renderer) string { +func (step StepViewFeed) detectMimeType(builder Builder) string { // First, try to get the format from the query string - switch renderer.QueryParam("format") { + switch builder.QueryParam("format") { case "json": return model.MimeTypeJSONFeed case "atom": @@ -95,7 +95,7 @@ func (step StepViewFeed) detectMimeType(renderer Renderer) string { } // Otherwise, get the format from the "Accept" header - header := renderer.request().Header + header := builder.request().Header if result, err := accept.Negotiate(header.Get("Accept"), model.MimeTypeJSONFeed, model.MimeTypeAtom, model.MimeTypeRSS, model.MimeTypeXML, model.MimeTypeXMLText); err == nil { return result @@ -105,30 +105,30 @@ func (step StepViewFeed) detectMimeType(renderer Renderer) string { return model.MimeTypeJSONFeed } -func (step StepViewFeed) asJSONFeed(renderer Renderer, buffer io.Writer, children data.Iterator) PipelineBehavior { +func (step StepViewFeed) asJSONFeed(builder Builder, buffer io.Writer, children data.Iterator) PipelineBehavior { feed := jsonfeed.Feed{ Version: "https://jsonfeed.org/version/1.1", - Title: renderer.PageTitle(), - HomePageURL: renderer.Permalink(), - FeedURL: renderer.Permalink() + "/feed?format=json", - Description: renderer.Summary(), + Title: builder.PageTitle(), + HomePageURL: builder.Permalink(), + FeedURL: builder.Permalink() + "/feed?format=json", + Description: builder.Summary(), Hubs: []jsonfeed.Hub{ { Type: "WebSub", - URL: renderer.Permalink() + "/websub", + URL: builder.Permalink() + "/websub", }, }, } feed.Items = slice.Map(iterator.Slice(children, model.NewStream), convert.StreamToJsonFeed) - renderer.response().Header().Add("Content-Type", model.MimeTypeJSONFeed) + builder.response().Header().Add("Content-Type", model.MimeTypeJSONFeed) bytes, err := json.Marshal(feed) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepViewFeed.asJSONFeed", "Error generating JSONFeed")) + return Halt().WithError(derp.Wrap(err, "build.StepViewFeed.asJSONFeed", "Error generating JSONFeed")) } // nolint:errcheck diff --git a/render/step_ViewHTML.go b/build/step_ViewHTML.go similarity index 65% rename from render/step_ViewHTML.go rename to build/step_ViewHTML.go index e0d9c7ad4..d99a6e15d 100644 --- a/render/step_ViewHTML.go +++ b/build/step_ViewHTML.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -8,32 +8,32 @@ import ( "github.com/benpate/rosetta/compare" ) -// StepViewHTML represents an action-step that can render a Stream into HTML +// StepViewHTML represents an action-step that can build a Stream into HTML type StepViewHTML struct { File string Method string } -// Get renders the Stream HTML to the context -func (step StepViewHTML) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +// Get builds the Stream HTML to the context +func (step StepViewHTML) Get(builder Builder, buffer io.Writer) PipelineBehavior { if step.Method != "post" { - return step.execute(renderer, buffer) + return step.execute(builder, buffer) } return nil } -func (step StepViewHTML) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepViewHTML) Post(builder Builder, buffer io.Writer) PipelineBehavior { if step.Method != "get" { - return step.execute(renderer, buffer) + return step.execute(builder, buffer) } return nil } -func (step StepViewHTML) execute(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepViewHTML) execute(builder Builder, buffer io.Writer) PipelineBehavior { /* TODO: MEDIUM: Re-implement client-side caching later. Caching leads to problems on INDEX-ONLY pages because you may have added/changed/deleted a child @@ -43,7 +43,7 @@ func (step StepViewHTML) execute(renderer Renderer, buffer io.Writer) PipelineBe // Validate If-None-Match Header if etag := requestHeader.Get("If-None-Match"); etag != "" { - if etag == renderer.object().ETag() { + if etag == builder.object().ETag() { context.Response().WriteHeader(http.StatusNotModified) return nil } @@ -52,7 +52,7 @@ func (step StepViewHTML) execute(renderer Renderer, buffer io.Writer) PipelineBe // Validate If-Modified-Since Header if modifiedSince := requestHeader.Get("If-Modified-Since"); modifiedSince != "" { if modifiedSinceDate, err := time.Parse(time.RFC3339, modifiedSince); err == nil { - if modifiedSinceDate.UnixMilli() >= renderer.object().Updated() { + if modifiedSinceDate.UnixMilli() >= builder.object().Updated() { context.Response().WriteHeader(http.StatusNotModified) return nil } @@ -61,7 +61,7 @@ func (step StepViewHTML) execute(renderer Renderer, buffer io.Writer) PipelineBe */ // TODO: LOW: We can do a better job with caching. If a page is public, then caching should be public, too. - header := renderer.response().Header() + header := builder.response().Header() header.Set("Vary", "Cookie, HX-Request") // header.Set("Cache-Control", "private") @@ -70,17 +70,17 @@ func (step StepViewHTML) execute(renderer Renderer, buffer io.Writer) PipelineBe if step.File != "" { filename = step.File } else { - filename = renderer.ActionID() + filename = builder.ActionID() } - if err := renderer.executeTemplate(buffer, filename, renderer); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepViewHTML.Get", "Error executing template")) + if err := builder.executeTemplate(buffer, filename, builder); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepViewHTML.Get", "Error executing template")) } // TODO: MEDIUM: Re-implement caching. Will need to automatically compute the "Vary" header. // If we have a valid object, then try to set ETag headers. - if object := renderer.object(); compare.NotNil(object) { + if object := builder.object(); compare.NotNil(object) { return Continue(). WithHeader("Last-Modified", time.UnixMilli(object.Updated()).Format(time.RFC3339)). WithHeader("ETag", object.ETag()) diff --git a/render/step_ViewJSONLD.go b/build/step_ViewJSONLD.go similarity index 55% rename from render/step_ViewJSONLD.go rename to build/step_ViewJSONLD.go index 6e6eab8cb..209f6344c 100644 --- a/render/step_ViewJSONLD.go +++ b/build/step_ViewJSONLD.go @@ -1,4 +1,4 @@ -package render +package build import ( "encoding/json" @@ -9,34 +9,34 @@ import ( accept "github.com/timewasted/go-accept-headers" ) -// StepViewJSONLD represents an action-step that can render a Stream into HTML +// StepViewJSONLD represents an action-step that can build a Stream into HTML type StepViewJSONLD struct { Method string } -// Get renders the Stream HTML to the context -func (step StepViewJSONLD) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +// Get builds the Stream HTML to the context +func (step StepViewJSONLD) Get(builder Builder, buffer io.Writer) PipelineBehavior { if step.Method != "post" { - return step.execute(renderer, buffer) + return step.execute(builder, buffer) } return nil } -func (step StepViewJSONLD) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepViewJSONLD) Post(builder Builder, buffer io.Writer) PipelineBehavior { if step.Method != "get" { - return step.execute(renderer, buffer) + return step.execute(builder, buffer) } return nil } -func (step StepViewJSONLD) execute(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepViewJSONLD) execute(builder Builder, buffer io.Writer) PipelineBehavior { // Try to negotiate the correct content type - acceptHeader := renderer.request().Header.Get("Accept") + acceptHeader := builder.request().Header.Get("Accept") accept, err := accept.Negotiate(acceptHeader, model.MimeTypeHTML, model.MimeTypeActivityPub, model.MimeTypeJSONLD, model.MimeTypeJSON) // If there is an error in content negotiation, then no JSON-LD for you @@ -55,19 +55,19 @@ func (step StepViewJSONLD) execute(renderer Renderer, buffer io.Writer) Pipeline // JSON-LD FOR YOU!!!! - // Now, try to get a JSONLDGetter from the renderer - if getter, ok := renderer.object().(model.JSONLDGetter); ok { + // Now, try to get a JSONLDGetter from the builder + if getter, ok := builder.object().(model.JSONLDGetter); ok { // Write the object as JSON result, err := json.Marshal(getter.GetJSONLD()) if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepViewJSONLD.Get", "Error marshalling JSONLD")) + return Halt().WithError(derp.Wrap(err, "build.StepViewJSONLD.Get", "Error marshalling JSONLD")) } // Write the JSON to the output buffer if _, err := buffer.Write(result); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepViewJSONLD.Get", "Error writing JSONLD to buffer")) + return Halt().WithError(derp.Wrap(err, "build.StepViewJSONLD.Get", "Error writing JSONLD to buffer")) } // Done. Return result as pure JSON. @@ -75,5 +75,5 @@ func (step StepViewJSONLD) execute(renderer Renderer, buffer io.Writer) Pipeline } // If you're here, that means the template designer used step on a non-JSONLDGetter object type. Shame. - return Halt().WithError(derp.NewNotFoundError("render.StepViewJSONLD.Get", "Object does not implement JSONLDGetter interface")) + return Halt().WithError(derp.NewNotFoundError("build.StepViewJSONLD.Get", "Object does not implement JSONLDGetter interface")) } diff --git a/render/step_WebSub.go b/build/step_WebSub.go similarity index 59% rename from render/step_WebSub.go rename to build/step_WebSub.go index 52a8140ba..99520786e 100644 --- a/render/step_WebSub.go +++ b/build/step_WebSub.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -11,23 +11,23 @@ import ( "github.com/timewasted/go-accept-headers" ) -// StepWebSub represents an action-step that can render a Stream into HTML +// StepWebSub represents an action-step that can build a Stream into HTML type StepWebSub struct { } // Get is not required by WebSub. So let's redirect to the primary action. -func (step StepWebSub) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepWebSub) Get(builder Builder, buffer io.Writer) PipelineBehavior { // TODO: MEDIUM: This may not jive with the new PipelineBehavior model. Check accordingly. - newLocation := list.RemoveLast(renderer.URL(), list.DelimiterSlash) - if err := redirect(renderer.response(), http.StatusSeeOther, newLocation); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepWebSub.Get", "Error writing redirection", newLocation)) + newLocation := list.RemoveLast(builder.URL(), list.DelimiterSlash) + if err := redirect(builder.response(), http.StatusSeeOther, newLocation); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepWebSub.Get", "Error writing redirection", newLocation)) } return nil } // Post accepts a WebSub request, verifies it, and potentially creates a new Follower record. -func (step StepWebSub) Post(renderer Renderer, _ io.Writer) PipelineBehavior { +func (step StepWebSub) Post(builder Builder, _ io.Writer) PipelineBehavior { var transaction struct { Mode string `form:"hub.mode"` @@ -37,14 +37,14 @@ func (step StepWebSub) Post(renderer Renderer, _ io.Writer) PipelineBehavior { LeaseSeconds int `form:"hub.lease_seconds"` } - if err := bind(renderer.request(), &transaction); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepWebSub.Post", "Error parsing form data")) + if err := bind(builder.request(), &transaction); err != nil { + return Halt().WithError(derp.Wrap(err, "build.StepWebSub.Post", "Error parsing form data")) } // Try to validate and save the follower via the queue. - factory := renderer.factory() + factory := builder.factory() - format, err := accept.Negotiate(renderer.request().Header.Get("Accept"), model.MimeTypeJSONFeed, model.MimeTypeAtom, model.MimeTypeRSS, model.MimeTypeXML, model.MimeTypeXMLText) + format, err := accept.Negotiate(builder.request().Header.Get("Accept"), model.MimeTypeJSONFeed, model.MimeTypeAtom, model.MimeTypeRSS, model.MimeTypeXML, model.MimeTypeXMLText) if err != nil { format = model.MimeTypeJSONFeed @@ -54,8 +54,8 @@ func (step StepWebSub) Post(renderer Renderer, _ io.Writer) PipelineBehavior { factory.Queue().Push(service.NewTaskCreateWebSubFollower( factory.Follower(), factory.Locator(), - renderer.objectType(), - renderer.objectID(), + builder.objectType(), + builder.objectID(), format, transaction.Mode, transaction.Topic, @@ -67,7 +67,7 @@ func (step StepWebSub) Post(renderer Renderer, _ io.Writer) PipelineBehavior { // TODO: MEDIUM: This may not jive with the new PipelineBehavior model. Check accordingly. // Set Status Code 202 (Accepted) to conform to WebSub spec // https://www.w3.org/TR/websub/#subscription-response-details - renderer.response().WriteHeader(202) + builder.response().WriteHeader(202) return nil } diff --git a/render/step_WebSub_test.go b/build/step_WebSub_test.go similarity index 99% rename from render/step_WebSub_test.go rename to build/step_WebSub_test.go index 1259cb178..f47ddc56f 100644 --- a/render/step_WebSub_test.go +++ b/build/step_WebSub_test.go @@ -1,4 +1,4 @@ -package render +package build import ( "testing" diff --git a/render/step_WithChildren.go b/build/step_WithChildren.go similarity index 63% rename from render/step_WithChildren.go rename to build/step_WithChildren.go index 119a8aa47..63ead5145 100644 --- a/render/step_WithChildren.go +++ b/build/step_WithChildren.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,19 +13,19 @@ type StepWithChildren struct { SubSteps []step.Step } -func (step StepWithChildren) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepWithChildren) Get(builder Builder, buffer io.Writer) PipelineBehavior { return nil } // Post updates the stream with approved data from the request body. -func (step StepWithChildren) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepWithChildren) Post(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepWithChildren.Post" + const location = "build.StepWithChildren.Post" - factory := renderer.factory() - streamRenderer := renderer.(*Stream) + factory := builder.factory() + streamBuilder := builder.(*Stream) - children, err := factory.Stream().ListByParent(streamRenderer._stream.ParentID) + children, err := factory.Stream().ListByParent(streamBuilder._stream.ParentID) if err != nil { return Halt().WithError(derp.Wrap(err, location, "Error listing children")) @@ -36,15 +36,15 @@ func (step StepWithChildren) Post(renderer Renderer, buffer io.Writer) PipelineB for children.Next(&child) { - // Make a renderer with the new child stream + // Make a builder with the new child stream // TODO: LOW: Is "view" really the best action to use here?? - childStream, err := NewStreamWithoutTemplate(streamRenderer.factory(), streamRenderer.request(), streamRenderer.response(), &child, "") + childStream, err := NewStreamWithoutTemplate(streamBuilder.factory(), streamBuilder.request(), streamBuilder.response(), &child, "") if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error creating renderer for child")) + return Halt().WithError(derp.Wrap(err, location, "Error creating builder for child")) } - // Execute the POST render pipeline on the child + // Execute the POST build pipeline on the child childResult := Pipeline(step.SubSteps).Post(factory, &childStream, buffer) childResult.Error = derp.Wrap(result.Error, location, "Error executing steps for child") diff --git a/build/step_WithDraft.go b/build/step_WithDraft.go new file mode 100644 index 000000000..b66e9f4af --- /dev/null +++ b/build/step_WithDraft.go @@ -0,0 +1,53 @@ +package build + +import ( + "io" + + "github.com/EmissarySocial/emissary/model/step" + "github.com/benpate/derp" +) + +// StepWithDraft represents an action-step that can update the data.DataMap custom data stored in a Stream +type StepWithDraft struct { + SubSteps []step.Step +} + +// Get displays a form where users can update stream data +func (step StepWithDraft) Get(builder Builder, buffer io.Writer) PipelineBehavior { + + const location = "build.StepWithDraft.Get" + + factory := builder.factory() + streamBuilder := builder.(*Stream) + draftBuilder, err := streamBuilder.draftBuilder() + + if err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error getting draft builder")) + } + + // Execute the POST build pipeline on the parent + status := Pipeline(step.SubSteps).Get(factory, &draftBuilder, buffer) + status.Error = derp.Wrap(status.Error, location, "Error executing steps on draft") + + return UseResult(status) +} + +// Post updates the stream with approved data from the request body. +func (step StepWithDraft) Post(builder Builder, buffer io.Writer) PipelineBehavior { + + const location = "build.StepWithDraft.Post" + + factory := builder.factory() + streamBuilder := builder.(*Stream) + draftBuilder, err := streamBuilder.draftBuilder() + + if err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error getting draft builder")) + } + + // Execute the POST build pipeline on the parent + result := Pipeline(step.SubSteps).Post(factory, &draftBuilder, buffer) + result.Error = derp.Wrap(result.Error, location, "Error executing steps on draft") + + return UseResult(result) +} diff --git a/render/step_WithFolder.go b/build/step_WithFolder.go similarity index 51% rename from render/step_WithFolder.go rename to build/step_WithFolder.go index 579e25992..09e2a9b22 100644 --- a/render/step_WithFolder.go +++ b/build/step_WithFolder.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,33 +13,33 @@ type StepWithFolder struct { SubSteps []step.Step } -func (step StepWithFolder) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) +func (step StepWithFolder) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) } // Post updates the stream with approved data from the request body. -func (step StepWithFolder) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) +func (step StepWithFolder) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) } -func (step StepWithFolder) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { +func (step StepWithFolder) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - const location = "render.StepWithFolder.execute" + const location = "build.StepWithFolder.execute" - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "Anonymous user is not authorized to perform this action")) } // Collect required services and values - factory := renderer.factory() + factory := builder.factory() folderService := factory.Folder() - folderToken := renderer.QueryParam("folderId") + folderToken := builder.QueryParam("folderId") folder := model.NewFolder() - folder.UserID = renderer.AuthenticatedID() + folder.UserID = builder.AuthenticatedID() // If we have a real ID, then try to load the folder from the database if (folderToken != "") && (folderToken != "new") { - if err := folderService.LoadByToken(renderer.AuthenticatedID(), folderToken, &folder); err != nil { + if err := folderService.LoadByToken(builder.AuthenticatedID(), folderToken, &folder); err != nil { if actionMethod == ActionMethodGet { return Halt().WithError(derp.Wrap(err, location, "Unable to load Folder", folderToken)) } @@ -47,15 +47,15 @@ func (step StepWithFolder) execute(renderer Renderer, buffer io.Writer, actionMe } } - // Create a new renderer tied to the Folder record - subRenderer, err := NewModel(factory, renderer.request(), renderer.response(), &folder, renderer.template(), renderer.ActionID()) + // Create a new builder tied to the Folder record + subBuilder, err := NewModel(factory, builder.request(), builder.response(), &folder, builder.template(), builder.ActionID()) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-renderer")) + return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-builder")) } - // Execute the POST render pipeline on the child - result := Pipeline(step.SubSteps).Execute(factory, subRenderer, buffer, actionMethod) + // Execute the POST build pipeline on the child + result := Pipeline(step.SubSteps).Execute(factory, subBuilder, buffer, actionMethod) result.Error = derp.Wrap(result.Error, location, "Error executing steps for child") return UseResult(result) diff --git a/render/step_WithFollower.go b/build/step_WithFollower.go similarity index 50% rename from render/step_WithFollower.go rename to build/step_WithFollower.go index 6feba266b..736cb1293 100644 --- a/render/step_WithFollower.go +++ b/build/step_WithFollower.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,48 +13,48 @@ type StepWithFollower struct { SubSteps []step.Step } -func (step StepWithFollower) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) +func (step StepWithFollower) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) } // Post updates the stream with approved data from the request body. -func (step StepWithFollower) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) +func (step StepWithFollower) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) } -func (step StepWithFollower) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { +func (step StepWithFollower) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - const location = "render.StepWithFollower.execute" + const location = "build.StepWithFollower.execute" - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "Anonymous user is not authorized to perform this action")) } // Collect required services and values - factory := renderer.factory() + factory := builder.factory() followerService := factory.Follower() - followerToken := renderer.QueryParam("followerId") + followerToken := builder.QueryParam("followerId") follower := model.NewFollower() - follower.ParentID = renderer.AuthenticatedID() + follower.ParentID = builder.AuthenticatedID() // Try to load the Follower record (unless we're creating a NEW record) if (followerToken != "") && (followerToken != "new") { - if err := followerService.LoadByToken(renderer.AuthenticatedID(), followerToken, &follower); err != nil { + if err := followerService.LoadByToken(builder.AuthenticatedID(), followerToken, &follower); err != nil { if actionMethod == ActionMethodGet { return Halt().WithError(derp.Wrap(err, location, "Unable to load Follower via ID", followerToken)) } } } - // Create a new renderer tied to the Follower record - subRenderer, err := NewModel(factory, renderer.request(), renderer.response(), &follower, renderer.template(), renderer.ActionID()) + // Create a new builder tied to the Follower record + subBuilder, err := NewModel(factory, builder.request(), builder.response(), &follower, builder.template(), builder.ActionID()) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-renderer")) + return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-builder")) } - // Execute the render pipeline on the Follower record - result := Pipeline(step.SubSteps).Execute(factory, subRenderer, buffer, actionMethod) + // Execute the build pipeline on the Follower record + result := Pipeline(step.SubSteps).Execute(factory, subBuilder, buffer, actionMethod) result.Error = derp.Wrap(result.Error, location, "Error executing steps for child") return UseResult(result) diff --git a/render/step_WithFollowing.go b/build/step_WithFollowing.go similarity index 52% rename from render/step_WithFollowing.go rename to build/step_WithFollowing.go index a5e516d07..4c35c952a 100644 --- a/render/step_WithFollowing.go +++ b/build/step_WithFollowing.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,33 +13,33 @@ type StepWithFollowing struct { SubSteps []step.Step } -func (step StepWithFollowing) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) +func (step StepWithFollowing) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) } // Post updates the stream with approved data from the request body. -func (step StepWithFollowing) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) +func (step StepWithFollowing) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) } -func (step StepWithFollowing) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { +func (step StepWithFollowing) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - const location = "render.StepWithFollowing.execute" + const location = "build.StepWithFollowing.execute" - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "Anonymous user is not authorized to perform this action")) } // Collect required services and values - factory := renderer.factory() + factory := builder.factory() followingService := factory.Following() - token := renderer.QueryParam("followingId") + token := builder.QueryParam("followingId") following := model.NewFollowing() - following.UserID = renderer.AuthenticatedID() + following.UserID = builder.AuthenticatedID() // If we have a real ID, then try to load the following from the database if (token != "") && (token != "new") { - if err := followingService.LoadByToken(renderer.AuthenticatedID(), token, &following); err != nil { + if err := followingService.LoadByToken(builder.AuthenticatedID(), token, &following); err != nil { if actionMethod == ActionMethodGet { return Halt().WithError(derp.Wrap(err, location, "Unable to load Following", token)) } @@ -47,15 +47,15 @@ func (step StepWithFollowing) execute(renderer Renderer, buffer io.Writer, actio } } - // Create a new renderer tied to the Following record - subRenderer, err := NewModel(factory, renderer.request(), renderer.response(), &following, renderer.template(), renderer.ActionID()) + // Create a new builder tied to the Following record + subBuilder, err := NewModel(factory, builder.request(), builder.response(), &following, builder.template(), builder.ActionID()) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-renderer")) + return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-builder")) } - // Execute the POST render pipeline on the child - result := Pipeline(step.SubSteps).Execute(factory, subRenderer, buffer, actionMethod) + // Execute the POST build pipeline on the child + result := Pipeline(step.SubSteps).Execute(factory, subBuilder, buffer, actionMethod) if result.Error != nil { return Halt().WithError(derp.Wrap(result.Error, location, "Error executing steps for child")) diff --git a/render/step_WithMessage.go b/build/step_WithMessage.go similarity index 56% rename from render/step_WithMessage.go rename to build/step_WithMessage.go index 7c27b5467..3104dc7c2 100644 --- a/render/step_WithMessage.go +++ b/build/step_WithMessage.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -14,50 +14,50 @@ type StepWithMessage struct { SubSteps []step.Step } -func (step StepWithMessage) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) +func (step StepWithMessage) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) } // Post updates the message with data from the request body. -func (step StepWithMessage) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) +func (step StepWithMessage) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) } -func (step StepWithMessage) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { +func (step StepWithMessage) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - const location = "render.StepWithMessage.execute" + const location = "build.StepWithMessage.execute" - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "Anonymous user is not authorized to perform this action")) } // Parse the MessageID from the query string - messageID, err := primitive.ObjectIDFromHex(renderer.QueryParam("messageId")) + messageID, err := primitive.ObjectIDFromHex(builder.QueryParam("messageId")) if err != nil { return Halt().WithError(derp.Wrap(err, location, "MessageID must be a valid hex string")) } // Collect required services and values - factory := renderer.factory() + factory := builder.factory() inboxService := factory.Inbox() message := model.NewMessage() - userID := renderer.AuthenticatedID() + userID := builder.AuthenticatedID() // If we have a real ID, then try to load the message from the database if err := inboxService.LoadByID(userID, messageID, &message); err != nil { return Halt().WithError(derp.Wrap(err, location, "Unable to load Message", messageID)) } - // Create a new renderer tied to the Message record - subRenderer, err := NewModel(factory, renderer.request(), renderer.response(), &message, renderer.template(), renderer.ActionID()) + // Create a new builder tied to the Message record + subBuilder, err := NewModel(factory, builder.request(), builder.response(), &message, builder.template(), builder.ActionID()) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-renderer")) + return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-builder")) } - // Execute the POST render pipeline on the child - result := Pipeline(step.SubSteps).Execute(factory, subRenderer, buffer, actionMethod) + // Execute the POST build pipeline on the child + result := Pipeline(step.SubSteps).Execute(factory, subBuilder, buffer, actionMethod) result.Error = derp.Wrap(result.Error, location, "Error executing steps for child") return UseResult(result) diff --git a/build/step_WithNextSibling.go b/build/step_WithNextSibling.go new file mode 100644 index 000000000..f72486a80 --- /dev/null +++ b/build/step_WithNextSibling.go @@ -0,0 +1,53 @@ +package build + +import ( + "io" + + "github.com/EmissarySocial/emissary/model" + "github.com/EmissarySocial/emissary/model/step" + "github.com/benpate/derp" +) + +// StepWithNextSibling represents an action-step that can update the data.DataMap custom data stored in a Stream +type StepWithNextSibling struct { + SubSteps []step.Step +} + +func (step StepWithNextSibling) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) +} + +// Post executes the subSteps on the parent Stream +func (step StepWithNextSibling) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) +} + +// Post executes the subSteps on the parent Stream +func (step StepWithNextSibling) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { + + const location = "build.StepWithNextSibling.Post" + + var sibling model.Stream + + factory := builder.factory() + streamBuilder := builder.(*Stream) + stream := streamBuilder._stream + + if err := factory.Stream().LoadNextSibling(stream.ParentID, stream.Rank, &sibling); err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error listing parent")) + } + + // Make a builder with the new parent stream + // TODO: LOW: Is "view" really the best action to use here?? + siblingBuilder, err := NewStreamWithoutTemplate(streamBuilder.factory(), streamBuilder.request(), streamBuilder.response(), &sibling, "view") + + if err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error creating builder for sibling")) + } + + // execute the POST build pipeline on the parent + result := Pipeline(step.SubSteps).Execute(factory, &siblingBuilder, buffer, actionMethod) + result.Error = derp.Wrap(result.Error, location, "Error executing steps for parent") + + return UseResult(result) +} diff --git a/render/step_WithParent.go b/build/step_WithParent.go similarity index 55% rename from render/step_WithParent.go rename to build/step_WithParent.go index 1f2aa5ee3..512ce6503 100644 --- a/render/step_WithParent.go +++ b/build/step_WithParent.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,33 +13,33 @@ type StepWithParent struct { SubSteps []step.Step } -func (step StepWithParent) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepWithParent) Get(builder Builder, buffer io.Writer) PipelineBehavior { return nil } // Post executes the subSteps on the parent Stream -func (step StepWithParent) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { +func (step StepWithParent) Post(builder Builder, buffer io.Writer) PipelineBehavior { - const location = "render.StepWithParent.Post" + const location = "build.StepWithParent.Post" var parent model.Stream - factory := renderer.factory() - streamRenderer := renderer.(*Stream) + factory := builder.factory() + streamBuilder := builder.(*Stream) - if err := factory.Stream().LoadByID(streamRenderer._stream.ParentID, &parent); err != nil { + if err := factory.Stream().LoadByID(streamBuilder._stream.ParentID, &parent); err != nil { return Halt().WithError(derp.Wrap(err, location, "Error listing parent")) } - // Make a renderer with the new parent stream + // Make a builder with the new parent stream // TODO: LOW: Is "view" really the best action to use here?? - parentStream, err := NewStreamWithoutTemplate(streamRenderer.factory(), streamRenderer.request(), streamRenderer.response(), &parent, "") + parentStream, err := NewStreamWithoutTemplate(streamBuilder.factory(), streamBuilder.request(), streamBuilder.response(), &parent, "") if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error creating renderer for parent")) + return Halt().WithError(derp.Wrap(err, location, "Error creating builder for parent")) } - // Execute the POST render pipeline on the parent + // Execute the POST build pipeline on the parent result := Pipeline(step.SubSteps).Post(factory, &parentStream, buffer) result.Error = derp.Wrap(result.Error, location, "Error executing steps for parent") return UseResult(result) diff --git a/build/step_WithPrevSibling.go b/build/step_WithPrevSibling.go new file mode 100644 index 000000000..3e134bf10 --- /dev/null +++ b/build/step_WithPrevSibling.go @@ -0,0 +1,52 @@ +package build + +import ( + "io" + + "github.com/EmissarySocial/emissary/model" + "github.com/EmissarySocial/emissary/model/step" + "github.com/benpate/derp" +) + +// StepWithPrevSibling represents an action-step that can update the data.DataMap custom data stored in a Stream +type StepWithPrevSibling struct { + SubSteps []step.Step +} + +func (step StepWithPrevSibling) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) +} + +// Post executes the subSteps on the parent Stream +func (step StepWithPrevSibling) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) +} + +// Post executes the subSteps on the parent Stream +func (step StepWithPrevSibling) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { + + const location = "build.StepWithPrevSibling.execute" + + var sibling model.Stream + + factory := builder.factory() + streamBuilder := builder.(*Stream) + stream := streamBuilder._stream + + if err := factory.Stream().LoadPrevSibling(stream.ParentID, stream.Rank, &sibling); err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error listing parent")) + } + + // Make a builder with the new parent stream + // TODO: Is "view" really the best action to use here?? + siblingBuilder, err := NewStreamWithoutTemplate(streamBuilder.factory(), streamBuilder.request(), streamBuilder.response(), &sibling, "view") + + if err != nil { + return Halt().WithError(derp.Wrap(err, location, "Error creating builder for sibling")) + } + + // Execute the POST build pipeline on the parent + result := Pipeline(step.SubSteps).Execute(factory, &siblingBuilder, buffer, actionMethod) + result.Error = derp.Wrap(result.Error, location, "Error executing steps for parent") + return UseResult(result) +} diff --git a/render/step_WithResponse.go b/build/step_WithResponse.go similarity index 59% rename from render/step_WithResponse.go rename to build/step_WithResponse.go index f09e4a3bb..5b85acac9 100644 --- a/render/step_WithResponse.go +++ b/build/step_WithResponse.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -14,27 +14,27 @@ type StepWithResponse struct { SubSteps []step.Step } -func (step StepWithResponse) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) +func (step StepWithResponse) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) } // Post updates the stream with approved data from the request body. -func (step StepWithResponse) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) +func (step StepWithResponse) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) } -func (step StepWithResponse) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { +func (step StepWithResponse) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - const location = "render.StepWithResponse.doStep" + const location = "build.StepWithResponse.doStep" - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "Anonymous user is not authorized to perform this action")) } // Collect required services and values - factory := renderer.factory() + factory := builder.factory() responseService := factory.Response() - responseToken := renderer.QueryParam("responseId") + responseToken := builder.QueryParam("responseId") response := model.NewResponse() // If we have a real ID, then try to load the response from the database @@ -49,15 +49,15 @@ func (step StepWithResponse) execute(renderer Renderer, buffer io.Writer, action } } - // Create a new renderer tied to the Response record - subRenderer, err := NewModel(factory, renderer.request(), renderer.response(), &response, renderer.template(), renderer.ActionID()) + // Create a new builder tied to the Response record + subBuilder, err := NewModel(factory, builder.request(), builder.response(), &response, builder.template(), builder.ActionID()) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-renderer")) + return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-builder")) } - // Execute the POST render pipeline on the child - result := Pipeline(step.SubSteps).Execute(factory, subRenderer, buffer, actionMethod) + // Execute the POST build pipeline on the child + result := Pipeline(step.SubSteps).Execute(factory, subBuilder, buffer, actionMethod) result.Error = derp.Wrap(result.Error, location, "Error executing steps for child") return UseResult(result) diff --git a/render/step_WithRule.go b/build/step_WithRule.go similarity index 50% rename from render/step_WithRule.go rename to build/step_WithRule.go index 9831e8f93..66f28765d 100644 --- a/render/step_WithRule.go +++ b/build/step_WithRule.go @@ -1,4 +1,4 @@ -package render +package build import ( "io" @@ -13,32 +13,32 @@ type StepWithRule struct { SubSteps []step.Step } -func (step StepWithRule) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) +func (step StepWithRule) Get(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodGet) } // Post updates the stream with approved data from the request body. -func (step StepWithRule) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) +func (step StepWithRule) Post(builder Builder, buffer io.Writer) PipelineBehavior { + return step.execute(builder, buffer, ActionMethodPost) } -func (step StepWithRule) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { +func (step StepWithRule) execute(builder Builder, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - const location = "render.StepWithRule.doStep" + const location = "build.StepWithRule.doStep" - if !renderer.IsAuthenticated() { + if !builder.IsAuthenticated() { return Halt().WithError(derp.NewUnauthorizedError(location, "Anonymous user is not authorized to perform this action")) } // Collect required services and values - factory := renderer.factory() + factory := builder.factory() ruleService := factory.Rule() - ruleToken := renderer.QueryParam("ruleId") + ruleToken := builder.QueryParam("ruleId") rule := model.NewRule() - rule.UserID = renderer.AuthenticatedID() + rule.UserID = builder.AuthenticatedID() if (ruleToken != "") && (ruleToken != "new") { - if err := ruleService.LoadByToken(renderer.AuthenticatedID(), ruleToken, &rule); err != nil { + if err := ruleService.LoadByToken(builder.AuthenticatedID(), ruleToken, &rule); err != nil { if actionMethod == ActionMethodGet { return Halt().WithError(derp.Wrap(err, location, "Unable to load Rule", ruleToken)) } @@ -46,15 +46,15 @@ func (step StepWithRule) execute(renderer Renderer, buffer io.Writer, actionMeth } } - // Create a new renderer tied to the Rule record - subRenderer, err := NewModel(factory, renderer.request(), renderer.response(), &rule, renderer.template(), renderer.ActionID()) + // Create a new builder tied to the Rule record + subBuilder, err := NewModel(factory, builder.request(), builder.response(), &rule, builder.template(), builder.ActionID()) if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-renderer")) + return Halt().WithError(derp.Wrap(err, location, "Unable to create sub-builder")) } - // Execute the POST render pipeline on the child - reesult := Pipeline(step.SubSteps).Execute(factory, subRenderer, buffer, actionMethod) + // Execute the POST build pipeline on the child + reesult := Pipeline(step.SubSteps).Execute(factory, subBuilder, buffer, actionMethod) reesult.Error = derp.Wrap(reesult.Error, location, "Error executing steps for child") return UseResult(reesult) diff --git a/build/step_do.go b/build/step_do.go new file mode 100644 index 000000000..2fae58e95 --- /dev/null +++ b/build/step_do.go @@ -0,0 +1,41 @@ +package build + +import ( + "io" + + "github.com/benpate/derp" +) + +// StepDo represents an action-step that sends an HTMX 'forward' to a new page. +type StepDo struct { + Action string +} + +func (step StepDo) Get(builder Builder, buffer io.Writer) PipelineBehavior { + + const location = "build.StepDo.Get" + + action, ok := builder.template().Actions[step.Action] + + if !ok { + return Halt().WithError(derp.NewBadRequestError(location, "Action not found", step.Action)) + } + + result := Pipeline(action.Steps).Get(builder.factory(), builder, buffer) + return UseResult(result) +} + +// Post updates the stream with approved data from the request body. +func (step StepDo) Post(builder Builder, buffer io.Writer) PipelineBehavior { + + const location = "build.StepDo.Post" + + action, ok := builder.template().Actions[step.Action] + + if !ok { + return Halt().WithError(derp.NewBadRequestError(location, "Action not found", step.Action)) + } + + result := Pipeline(action.Steps).Post(builder.factory(), builder, buffer) + return UseResult(result) +} diff --git a/render/urlvalues_test.go b/build/urlvalues_test.go similarity index 98% rename from render/urlvalues_test.go rename to build/urlvalues_test.go index 3206cdb7c..60fbdf373 100644 --- a/render/urlvalues_test.go +++ b/build/urlvalues_test.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" diff --git a/render/utilities.go b/build/utilities.go similarity index 95% rename from render/utilities.go rename to build/utilities.go index 5784ee3d2..7d478a168 100644 --- a/render/utilities.go +++ b/build/utilities.go @@ -1,4 +1,4 @@ -package render +package build import ( "bytes" @@ -26,7 +26,7 @@ func WrapInlineSuccess(response http.ResponseWriter, message any) error { response.WriteHeader(http.StatusOK) _, err := response.Write([]byte(`` + convert.String(message) + ``)) - return derp.Wrap(err, "render.WrapInlineSuccess", "Error writing response", message) + return derp.Wrap(err, "build.WrapInlineSuccess", "Error writing response", message) } // WrapInlineError sends an error message to the #htmx-response-message element @@ -39,7 +39,7 @@ func WrapInlineError(response http.ResponseWriter, err error) error { response.WriteHeader(http.StatusOK) if _, writeError := response.Write([]byte(`` + derp.Message(err) + ``)); writeError != nil { - return derp.Wrap(writeError, "render.WrapInlineError", "Error writing response", err) + return derp.Wrap(writeError, "build.WrapInlineError", "Error writing response", err) } return nil @@ -215,7 +215,7 @@ func executeTemplate(template TemplateLike, data any) string { var buffer bytes.Buffer if err := template.Execute(&buffer, data); err != nil { - derp.Report(derp.Wrap(err, "render.executeTemplate", "Error executing template", data)) + derp.Report(derp.Wrap(err, "build.executeTemplate", "Error executing template", data)) return "" } @@ -263,7 +263,7 @@ func bindBody(request *http.Request, result any) error { func multipartForm(request *http.Request) (*multipart.Form, error) { if err := request.ParseMultipartForm(32 << 20); err != nil { - return nil, derp.Wrap(err, "render.multipartForm", "Error parsing multipart form") + return nil, derp.Wrap(err, "build.multipartForm", "Error parsing multipart form") } return request.MultipartForm, nil diff --git a/domain/factory.go b/domain/factory.go index 2ac3222eb..8d2421fbb 100644 --- a/domain/factory.go +++ b/domain/factory.go @@ -4,9 +4,9 @@ import ( "context" "strings" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/config" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/service" "github.com/EmissarySocial/emissary/tools/set" "github.com/benpate/data" @@ -197,7 +197,7 @@ func (factory *Factory) Refresh(domain config.Domain, providers []config.Provide factory.Theme(), factory.User(), factory.Provider(), - render.FuncMap(factory.Icons()), + build.FuncMap(factory.Icons()), ) // Populate EncryptionKey Service diff --git a/handler/activitypub_user/profile.go b/handler/activitypub_user/profile.go index 0cfcc50d8..f81a373ab 100644 --- a/handler/activitypub_user/profile.go +++ b/handler/activitypub_user/profile.go @@ -13,7 +13,7 @@ import ( func RenderProfileJSONLD(context echo.Context, factory *domain.Factory, user *model.User) error { - const location = "handler.activitypub.renderProfileJSONLD" + const location = "handler.activitypub.buildProfileJSONLD" // Try to load the key from the Datbase keyService := factory.EncryptionKey() diff --git a/handler/admin.go b/handler/admin.go index a415a324e..e0bfad063 100644 --- a/handler/admin.go +++ b/handler/admin.go @@ -1,9 +1,9 @@ package handler import ( + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/domain" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/rosetta/first" @@ -14,17 +14,17 @@ import ( // GetAdmin handles GET requests func GetAdmin(factoryManager *server.Factory) echo.HandlerFunc { - return renderAdmin(factoryManager, render.ActionMethodGet) + return buildAdmin(factoryManager, build.ActionMethodGet) } // PostAdmin handles POST/DELETE requests func PostAdmin(factoryManager *server.Factory) echo.HandlerFunc { - return renderAdmin(factoryManager, render.ActionMethodPost) + return buildAdmin(factoryManager, build.ActionMethodPost) } -func renderAdmin(factoryManager *server.Factory, actionMethod render.ActionMethod) echo.HandlerFunc { +func buildAdmin(factoryManager *server.Factory, actionMethod build.ActionMethod) echo.HandlerFunc { - const location = "handler.adminRenderer" + const location = "handler.adminBuilder" return func(ctx echo.Context) error { @@ -43,7 +43,7 @@ func renderAdmin(factoryManager *server.Factory, actionMethod render.ActionMetho } // Parse admin parameters - templateID, actionID, objectID := renderAdmin_ParsePath(ctx) + templateID, actionID, objectID := buildAdmin_ParsePath(ctx) // Try to load the Template templateService := factory.Template() @@ -53,19 +53,19 @@ func renderAdmin(factoryManager *server.Factory, actionMethod render.ActionMetho return err } - // Locate and populate the renderer - renderer, err := renderAdmin_GetRenderer(factory, sterankoContext, template, actionID, objectID) + // Locate and populate the builder + builder, err := buildAdmin_GetBuilder(factory, sterankoContext, template, actionID, objectID) if err != nil { - return derp.Wrap(err, location, "Error generating renderer") + return derp.Wrap(err, location, "Error generating builder") } // Success!! - return renderHTML(factory, sterankoContext, renderer, actionMethod) + return buildHTML(factory, sterankoContext, builder, actionMethod) } } -func renderAdmin_ParsePath(ctx echo.Context) (string, string, primitive.ObjectID) { +func buildAdmin_ParsePath(ctx echo.Context) (string, string, primitive.ObjectID) { // First parameter is always the templateID templateID := first.String(ctx.Param("param1"), "domain") @@ -82,11 +82,11 @@ func renderAdmin_ParsePath(ctx echo.Context) (string, string, primitive.ObjectID return templateID, actionID, primitive.NilObjectID } -func renderAdmin_GetRenderer(factory *domain.Factory, ctx *steranko.Context, template model.Template, actionID string, objectID primitive.ObjectID) (render.Renderer, error) { +func buildAdmin_GetBuilder(factory *domain.Factory, ctx *steranko.Context, template model.Template, actionID string, objectID primitive.ObjectID) (build.Builder, error) { - const location = "handler.renderAdmin_GetRenderer" + const location = "handler.buildAdmin_GetBuilder" - // Create the correct renderer for this controller + // Create the correct builder for this controller switch template.Model { case "rule": @@ -101,10 +101,10 @@ func renderAdmin_GetRenderer(factory *domain.Factory, ctx *steranko.Context, tem } } - return render.NewRule(factory, ctx.Request(), ctx.Response(), &rule, template, actionID) + return build.NewRule(factory, ctx.Request(), ctx.Response(), &rule, template, actionID) case "domain": - return render.NewDomain(factory, ctx.Request(), ctx.Response(), template, actionID) + return build.NewDomain(factory, ctx.Request(), ctx.Response(), template, actionID) case "group": group := model.NewGroup() @@ -116,7 +116,7 @@ func renderAdmin_GetRenderer(factory *domain.Factory, ctx *steranko.Context, tem } } - return render.NewGroup(factory, ctx.Request(), ctx.Response(), template, &group, actionID) + return build.NewGroup(factory, ctx.Request(), ctx.Response(), template, &group, actionID) case "stream": stream := model.NewStream() @@ -128,7 +128,7 @@ func renderAdmin_GetRenderer(factory *domain.Factory, ctx *steranko.Context, tem } } - return render.NewNavigation(factory, ctx.Request(), ctx.Response(), template, &stream, actionID) + return build.NewNavigation(factory, ctx.Request(), ctx.Response(), template, &stream, actionID) case "user": user := model.NewUser() @@ -140,7 +140,7 @@ func renderAdmin_GetRenderer(factory *domain.Factory, ctx *steranko.Context, tem } } - return render.NewUser(factory, ctx.Request(), ctx.Response(), template, &user, actionID) + return build.NewUser(factory, ctx.Request(), ctx.Response(), template, &user, actionID) default: return nil, derp.NewNotFoundError(location, "Template MODEL must be one of: 'rule', 'domain', 'group', 'stream', or 'user'", template.Model) diff --git a/handler/attachment.go b/handler/attachment.go index e0f7af1dc..50dfbce00 100644 --- a/handler/attachment.go +++ b/handler/attachment.go @@ -3,8 +3,8 @@ package handler import ( "net/http" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/rosetta/list" @@ -61,8 +61,8 @@ func GetAttachment(factoryManager *server.Factory) echo.HandlerFunc { } // Try to find the action requested by the user. This also enforces user permissions... - if _, err := render.NewStreamWithoutTemplate(factory, ctx.Request(), ctx.Response(), &stream, "view"); err != nil { - return derp.Wrap(err, location, "Cannot create renderer") + if _, err := build.NewStreamWithoutTemplate(factory, ctx.Request(), ctx.Response(), &stream, "view"); err != nil { + return derp.Wrap(err, location, "Cannot create builder") } // Retrieve the file from the mediaserver diff --git a/handler/html.go b/handler/html.go index 9a1165436..6b8a35229 100644 --- a/handler/html.go +++ b/handler/html.go @@ -4,22 +4,22 @@ import ( "bytes" "net/http" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/domain" - "github.com/EmissarySocial/emissary/render" "github.com/benpate/derp" "github.com/labstack/echo/v4" ) -// renderHTML collects the logic to render complete vs. partial HTML pages. -func renderHTML(factory *domain.Factory, ctx echo.Context, renderer render.Renderer, actionMethod render.ActionMethod) error { +// buildHTML collects the logic to build complete vs. partial HTML pages. +func buildHTML(factory *domain.Factory, ctx echo.Context, builder build.Builder, actionMethod build.ActionMethod) error { - const location = "handler.renderHTML" + const location = "handler.buildHTML" var partialPage bytes.Buffer // Execute the action pipeline - pipeline := render.Pipeline(renderer.Action().Steps) + pipeline := build.Pipeline(builder.Action().Steps) - status := pipeline.Execute(factory, renderer, &partialPage, actionMethod) + status := pipeline.Execute(factory, builder, &partialPage, actionMethod) if status.Error != nil { return derp.Wrap(status.Error, location, "Error executing action pipeline") @@ -29,17 +29,17 @@ func renderHTML(factory *domain.Factory, ctx echo.Context, renderer render.Rende status.Apply(ctx.Response()) // Partial page requests can be completed here. - if renderer.IsPartialRequest() || status.FullPage { + if builder.IsPartialRequest() || status.FullPage { return ctx.HTML(status.GetStatusCode(), partialPage.String()) } - // Full Page requests require the theme service to wrap the rendered content + // Full Page requests require the theme service to wrap the builded content htmlTemplate := factory.Domain().Theme().HTMLTemplate - renderer.SetContent(partialPage.String()) + builder.SetContent(partialPage.String()) var fullPage bytes.Buffer - if err := htmlTemplate.ExecuteTemplate(&fullPage, "page", renderer); err != nil { - return derp.Wrap(err, location, "Error rendering full-page content") + if err := htmlTemplate.ExecuteTemplate(&fullPage, "page", builder); err != nil { + return derp.Wrap(err, location, "Error building full-page content") } return ctx.HTML(http.StatusOK, fullPage.String()) diff --git a/handler/inbox.go b/handler/inbox.go index 46daed832..e2a1d7872 100644 --- a/handler/inbox.go +++ b/handler/inbox.go @@ -1,8 +1,8 @@ package handler import ( + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/rosetta/first" @@ -12,18 +12,18 @@ import ( // GetInbox handles GET requests func GetInbox(serverFactory *server.Factory) echo.HandlerFunc { - return renderInbox(serverFactory, render.ActionMethodGet) + return buildInbox(serverFactory, build.ActionMethodGet) } // PostInbox handles POST/DELETE requests func PostInbox(serverFactory *server.Factory) echo.HandlerFunc { - return renderInbox(serverFactory, render.ActionMethodPost) + return buildInbox(serverFactory, build.ActionMethodPost) } -// renderInbox is the common Inbox handler for both GET and POST requests -func renderInbox(serverFactory *server.Factory, actionMethod render.ActionMethod) echo.HandlerFunc { +// buildInbox is the common Inbox handler for both GET and POST requests +func buildInbox(serverFactory *server.Factory, actionMethod build.ActionMethod) echo.HandlerFunc { - const location = "handler.renderInbox" + const location = "handler.buildInbox" return func(context echo.Context) error { @@ -55,16 +55,16 @@ func renderInbox(serverFactory *server.Factory, actionMethod render.ActionMethod actionID := first.String(context.Param("action"), "inbox") if ok, err := handleJSONLD(context, &user); ok { - return derp.Wrap(err, location, "Error rendering JSON-LD") + return derp.Wrap(err, location, "Error building JSON-LD") } - renderer, err := render.NewInbox(factory, context.Request(), context.Response(), &user, actionID) + builder, err := build.NewInbox(factory, context.Request(), context.Response(), &user, actionID) if err != nil { - return derp.Wrap(err, location, "Error creating renderer") + return derp.Wrap(err, location, "Error creating builder") } - // Forward to the standard page renderer to complete the job - return renderHTML(factory, sterankoContext, renderer, actionMethod) + // Forward to the standard page builder to complete the job + return buildHTML(factory, sterankoContext, builder, actionMethod) } } diff --git a/handler/oauth-server.go b/handler/oauth-server.go index 621155dd8..a1a109bb6 100644 --- a/handler/oauth-server.go +++ b/handler/oauth-server.go @@ -4,8 +4,8 @@ import ( "net/http" "net/url" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/EmissarySocial/emissary/service" "github.com/benpate/derp" @@ -35,17 +35,17 @@ func GetOAuthAuthorization(serverFactory *server.Factory) echo.HandlerFunc { return derp.NewInternalError(location, "Invalid Domain.") } - // Load the OAuth Renderer - renderer, err := render.NewOAuthAuthorization(factory, transaction) + // Load the OAuth Builder + builder, err := build.NewOAuthAuthorization(factory, transaction) if err != nil { - return derp.Wrap(err, location, "Error Generating Renderer") + return derp.Wrap(err, location, "Error Generating Builder") } // Render the template template := factory.Domain().Theme().HTMLTemplate - if err := template.ExecuteTemplate(ctx.Response(), "oauth", renderer); err != nil { + if err := template.ExecuteTemplate(ctx.Response(), "oauth", builder); err != nil { return derp.Wrap(err, location, "Error executing template") } diff --git a/handler/outbox.go b/handler/outbox.go index dcecd4260..11d7549fe 100644 --- a/handler/outbox.go +++ b/handler/outbox.go @@ -3,9 +3,9 @@ package handler import ( "net/http" + "github.com/EmissarySocial/emissary/build" activitypub "github.com/EmissarySocial/emissary/handler/activitypub_user" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/mediaserver" @@ -17,12 +17,12 @@ import ( // GetOutbox handles GET requests func GetOutbox(serverFactory *server.Factory) echo.HandlerFunc { - return renderOutbox(serverFactory, render.ActionMethodGet) + return buildOutbox(serverFactory, build.ActionMethodGet) } // PostOutbox handles POST/DELETE requests func PostOutbox(serverFactory *server.Factory) echo.HandlerFunc { - return renderOutbox(serverFactory, render.ActionMethodPost) + return buildOutbox(serverFactory, build.ActionMethodPost) } func GetProfileAvatar(serverFactory *server.Factory) echo.HandlerFunc { @@ -89,10 +89,10 @@ func GetProfileAvatar(serverFactory *server.Factory) echo.HandlerFunc { } } -// renderOutbox is the common Outbox handler for both GET and POST requests -func renderOutbox(serverFactory *server.Factory, actionMethod render.ActionMethod) echo.HandlerFunc { +// buildOutbox is the common Outbox handler for both GET and POST requests +func buildOutbox(serverFactory *server.Factory, actionMethod build.ActionMethod) echo.HandlerFunc { - const location = "handler.renderOutbox" + const location = "handler.buildOutbox" return func(context echo.Context) error { @@ -122,7 +122,7 @@ func renderOutbox(serverFactory *server.Factory, actionMethod render.ActionMetho } if !isUserVisible(sterankoContext, &user) { - return derp.NewNotFoundError("handler.renderOutbox", "User not found") + return derp.NewNotFoundError("handler.buildOutbox", "User not found") } if isJSONLDRequest(sterankoContext) { @@ -133,17 +133,17 @@ func renderOutbox(serverFactory *server.Factory, actionMethod render.ActionMetho actionID := first.String(context.Param("action"), "view") if ok, err := handleJSONLD(context, &user); ok { - return derp.Wrap(err, location, "Error rendering JSON-LD") + return derp.Wrap(err, location, "Error building JSON-LD") } - renderer, err := render.NewOutbox(factory, context.Request(), context.Response(), &user, actionID) + builder, err := build.NewOutbox(factory, context.Request(), context.Response(), &user, actionID) if err != nil { - return derp.Wrap(err, location, "Error creating renderer") + return derp.Wrap(err, location, "Error creating builder") } - // Forward to the standard page renderer to complete the job - return renderHTML(factory, sterankoContext, renderer, actionMethod) + // Forward to the standard page builder to complete the job + return buildHTML(factory, sterankoContext, builder, actionMethod) } } diff --git a/handler/qrcode.go b/handler/qrcode.go index 3303c180d..be97744cf 100644 --- a/handler/qrcode.go +++ b/handler/qrcode.go @@ -14,7 +14,7 @@ import ( type StepQRCode struct { } -// Get renders the Stream HTML to the context +// Get builds the Stream HTML to the context func GetQRCode(fm *server.Factory) echo.HandlerFunc { return func(ctx echo.Context) error { @@ -32,14 +32,14 @@ func GetQRCode(fm *server.Factory) echo.HandlerFunc { qrc, err := qrcode.New(url) if err != nil { - return derp.Wrap(err, "render.StepQRCode.Get", "Error generating QR Code") + return derp.Wrap(err, "build.StepQRCode.Get", "Error generating QR Code") } w := standard.NewWithWriter(AsWriteCloser{ctx.Response().Writer}) // "save" file to the writer if err := qrc.Save(w); err != nil { - return derp.Wrap(err, "render.StepQRCode.Get", "Error writing image") + return derp.Wrap(err, "build.StepQRCode.Get", "Error writing image") } return nil diff --git a/handler/setup_domain.go b/handler/setup_domain.go index 76e45b521..357ab19e1 100644 --- a/handler/setup_domain.go +++ b/handler/setup_domain.go @@ -4,9 +4,9 @@ import ( _ "embed" "net/http" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/config" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/form" @@ -45,7 +45,7 @@ func SetupDomainGet(factory *server.Factory) echo.HandlerFunc { return derp.Wrap(err, "handler.SetupDomainGet", "Error generating form") } - result := render.WrapModalForm(ctx.Response(), "/domains/"+domain.DomainID, formHTML) + result := build.WrapModalForm(ctx.Response(), "/domains/"+domain.DomainID, formHTML) return ctx.HTML(200, result) } @@ -64,24 +64,24 @@ func SetupDomainPost(factory *server.Factory) echo.HandlerFunc { input := mapof.Any{} if err := (&echo.DefaultBinder{}).BindBody(ctx, &input); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error binding form input")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error binding form input")) } s := schema.New(config.DomainSchema()) if err := s.SetAll(&domain, input); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error setting config values")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error setting config values")) } if err := s.Validate(&domain); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error validating config values")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error validating config values")) } if err := factory.PutDomain(domain); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error saving domain")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "handler.SetupDomainPost", "Error saving domain")) } - render.CloseModal(ctx) + build.CloseModal(ctx) return ctx.NoContent(http.StatusOK) } } @@ -99,7 +99,7 @@ func SetupDomainDelete(factory *server.Factory) echo.HandlerFunc { } // Close the modal and return OK - render.RefreshPage(ctx) + build.RefreshPage(ctx) return ctx.NoContent(http.StatusOK) } } diff --git a/handler/setup_oauth.go b/handler/setup_oauth.go index fb737c156..6280f8cb2 100644 --- a/handler/setup_oauth.go +++ b/handler/setup_oauth.go @@ -4,8 +4,8 @@ import ( "html/template" "net/http" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/config" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/EmissarySocial/emissary/tools/dataset" "github.com/EmissarySocial/emissary/tools/set" @@ -51,7 +51,7 @@ func SetupOAuthGet(factory *server.Factory, templates *template.Template) echo.H } // Wrap the form in a modal dialog - result := render.WrapModalForm(ctx.Response(), "/oauth/"+oAuthProviderID, formHTML) + result := build.WrapModalForm(ctx.Response(), "/oauth/"+oAuthProviderID, formHTML) return ctx.HTML(200, result) } @@ -104,7 +104,7 @@ func SetupOAuthPost(factory *server.Factory, templates *template.Template) echo. } // Success! - render.CloseModal(ctx) + build.CloseModal(ctx) return ctx.NoContent(http.StatusOK) } } diff --git a/handler/setup_server.go b/handler/setup_server.go index 2a77a2ff0..5c9a5b441 100644 --- a/handler/setup_server.go +++ b/handler/setup_server.go @@ -6,8 +6,8 @@ import ( "net/http" "time" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/form" @@ -30,17 +30,17 @@ func SetupPageGet(factory *server.Factory, templates *template.Template, templat if useWrapper { if err := templates.ExecuteTemplate(ctx.Response().Writer, "_header.html", config); err != nil { - derp.Report(render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.getIndex", "Error rendering index page"))) + derp.Report(build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.getIndex", "Error building index page"))) } } if err := templates.ExecuteTemplate(ctx.Response().Writer, templateID, config); err != nil { - derp.Report(render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.getIndex", "Error rendering index page"))) + derp.Report(build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.getIndex", "Error building index page"))) } if useWrapper { if err := templates.ExecuteTemplate(ctx.Response().Writer, "_footer.html", config); err != nil { - derp.Report(render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.getIndex", "Error rendering index page"))) + derp.Report(build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.getIndex", "Error building index page"))) } } @@ -81,7 +81,7 @@ func SetupServerGet(factory *server.Factory) echo.HandlerFunc { } // Return the form - return ctx.HTML(http.StatusOK, render.WrapForm(uri, result, "cancel-button:hide")) + return ctx.HTML(http.StatusOK, build.WrapForm(uri, result, "cancel-button:hide")) } } @@ -93,7 +93,7 @@ func SetupServerPost(factory *server.Factory) echo.HandlerFunc { data := mapof.NewAny() if err := ctx.Bind(&data); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverPost", "Error parsing form data")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverPost", "Error parsing form data")) } // Data schema and UI schema @@ -106,7 +106,7 @@ func SetupServerPost(factory *server.Factory) echo.HandlerFunc { element, asTable, err := getSetupForm(section) if err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverTable", "Invalid table name")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverTable", "Invalid table name")) } // Write Table-formatted forms. @@ -115,12 +115,12 @@ func SetupServerPost(factory *server.Factory) echo.HandlerFunc { // Apply the changes to the configuration if err := widget.Do(ctx.Request().URL, data); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverTable", "Error saving form data")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverTable", "Error saving form data")) } // Try to save the configuration to the persistent storage if err := factory.UpdateConfig(config); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.postServer", "Internal error saving config. Try again later.")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.postServer", "Internal error saving config. Try again later.")) } // Redraw the table @@ -132,16 +132,16 @@ func SetupServerPost(factory *server.Factory) echo.HandlerFunc { // Apply the changes to the configuration if err := form.SetAll(&config, data, nil); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverPost", "Error saving form data", data)) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.serverPost", "Error saving form data", data)) } // Try to save the configuration to the persistent storage if err := factory.UpdateConfig(config); err != nil { - return render.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.postServer", "Internal error saving config. Try again later.")) + return build.WrapInlineError(ctx.Response(), derp.Wrap(err, "setup.postServer", "Internal error saving config. Try again later.")) } // Success! - return render.WrapInlineSuccess(ctx.Response(), "Record Updated at: "+time.Now().Format(time.TimeOnly)) + return build.WrapInlineSuccess(ctx.Response(), "Record Updated at: "+time.Now().Format(time.TimeOnly)) } } diff --git a/handler/setup_users.go b/handler/setup_users.go index 027a40869..bd240a0c2 100644 --- a/handler/setup_users.go +++ b/handler/setup_users.go @@ -5,10 +5,10 @@ import ( "html/template" "net/http" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/config" "github.com/EmissarySocial/emissary/domain" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/rosetta/mapof" @@ -33,11 +33,11 @@ func SetupDomainUsersGet(serverFactory *server.Factory, templates *template.Temp modal, err := displayDomainUsersModal(domainConfig, factory, templates) if err != nil { - return derp.Wrap(err, location, "Error rendering modal") + return derp.Wrap(err, location, "Error building modal") } // Wrap it as a modal - return ctx.HTML(http.StatusOK, render.WrapModal(ctx.Response(), modal, "class:large")) + return ctx.HTML(http.StatusOK, build.WrapModal(ctx.Response(), modal, "class:large")) } } @@ -81,7 +81,7 @@ func SetupDomainUserPost(serverFactory *server.Factory, templates *template.Temp modal, err := displayDomainUsersModal(domainConfig, factory, templates) if err != nil { - return derp.Wrap(err, location, "Error rendering modal") + return derp.Wrap(err, location, "Error building modal") } return ctx.HTML(http.StatusOK, modal) @@ -151,7 +151,7 @@ func SetupDomainUserDelete(serverFactory *server.Factory, templates *template.Te modal, err := displayDomainUsersModal(domainConfig, factory, templates) if err != nil { - return derp.Wrap(err, location, "Error rendering modal") + return derp.Wrap(err, location, "Error building modal") } return ctx.HTML(http.StatusOK, modal) diff --git a/handler/signin.go b/handler/signin.go index b00225e5d..8b4ae6086 100644 --- a/handler/signin.go +++ b/handler/signin.go @@ -162,7 +162,7 @@ func GetResetCode(serverFactory *server.Factory) echo.HandlerFunc { return derp.Wrap(err, "handler.GetResetCode", "Error loading user") } - // Try to render the HTML response + // Try to build the HTML response template := factory.Domain().Theme().HTMLTemplate object := mapof.Any{ diff --git a/handler/startup.go b/handler/startup.go index d00ad4e1d..fa52ceaf3 100644 --- a/handler/startup.go +++ b/handler/startup.go @@ -3,7 +3,7 @@ package handler import ( "net/http" - "github.com/EmissarySocial/emissary/render" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/benpate/rosetta/first" @@ -32,7 +32,7 @@ func GetStartup(serverFactory *server.Factory) echo.HandlerFunc { return derp.NewUnauthorizedError(location, "Unauthorized") } - // Collect parameters to render + // Collect parameters to build templateService := factory.Template() template, err := templateService.LoadAdmin("startup") @@ -42,18 +42,18 @@ func GetStartup(serverFactory *server.Factory) echo.HandlerFunc { actionID := first.String(ctx.Param("action"), "page") - // Get a Renderer for this page (also authenticates admin permissions) - renderer, err := render.NewDomain(factory, ctx.Request(), ctx.Response(), template, actionID) + // Get a Builder for this page (also authenticates admin permissions) + builder, err := build.NewDomain(factory, ctx.Request(), ctx.Response(), template, actionID) if err != nil { - return derp.Wrap(err, location, "Error creating renderer") + return derp.Wrap(err, location, "Error creating builder") } // Render the HTML page. - result, err := renderer.Render() + result, err := builder.Render() if err != nil { - return derp.Wrap(err, location, "Error rendering page") + return derp.Wrap(err, location, "Error building page") } // Return the HTML page to the browser diff --git a/handler/stream.go b/handler/stream.go index d58655d48..f250dcd63 100644 --- a/handler/stream.go +++ b/handler/stream.go @@ -3,9 +3,9 @@ package handler import ( "net/http" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/handler/activitypub_stream" "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/server" "github.com/benpate/derp" "github.com/labstack/echo/v4" @@ -22,25 +22,25 @@ func GetStream(serverFactory *server.Factory) echo.HandlerFunc { return activitypub_stream.GetJSONLD(serverFactory)(ctx) } - // Otherwise, just render the stream normally - return renderStream(serverFactory, render.ActionMethodGet)(ctx) + // Otherwise, just build the stream normally + return buildStream(serverFactory, build.ActionMethodGet)(ctx) } } // GetStreamWithAction handles GET requests with a specified action func GetStreamWithAction(serverFactory *server.Factory) echo.HandlerFunc { - return renderStream(serverFactory, render.ActionMethodGet) + return buildStream(serverFactory, build.ActionMethodGet) } // PostStreamWithAction handles POST requests with a specified action func PostStreamWithAction(serverFactory *server.Factory) echo.HandlerFunc { - return renderStream(serverFactory, render.ActionMethodPost) + return buildStream(serverFactory, build.ActionMethodPost) } -// renderStream is the common Stream handler for both GET and POST requests -func renderStream(serverFactory *server.Factory, actionMethod render.ActionMethod) echo.HandlerFunc { +// buildStream is the common Stream handler for both GET and POST requests +func buildStream(serverFactory *server.Factory, actionMethod build.ActionMethod) echo.HandlerFunc { - const location = "handler.renderStream" + const location = "handler.buildStream" return func(ctx echo.Context) error { @@ -71,23 +71,23 @@ func renderStream(serverFactory *server.Factory, actionMethod render.ActionMetho actionID := getActionID(ctx) if ok, err := handleJSONLD(ctx, streamService.JSONLDGetter(&stream)); ok { - return derp.Wrap(err, location, "Error rendering JSON-LD") + return derp.Wrap(err, location, "Error building JSON-LD") } - renderer, err := render.NewStreamWithoutTemplate(factory, ctx.Request(), ctx.Response(), &stream, actionID) + builder, err := build.NewStreamWithoutTemplate(factory, ctx.Request(), ctx.Response(), &stream, actionID) if err != nil { - return derp.Wrap(err, location, "Error creating Renderer") + return derp.Wrap(err, location, "Error creating Builder") } // Add webmention link header per: // https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint - if actionMethod == render.ActionMethodGet { + if actionMethod == build.ActionMethodGet { ctx.Response().Header().Set("Link", "/.webmention; rel=\"webmention\"") } - if err := renderHTML(factory, ctx, &renderer, actionMethod); err != nil { - return derp.Wrap(err, location, "Error rendering page") + if err := buildHTML(factory, ctx, &builder, actionMethod); err != nil { + return derp.Wrap(err, location, "Error building page") } return nil diff --git a/model/message.go b/model/message.go index 325b21154..29a9792b3 100644 --- a/model/message.go +++ b/model/message.go @@ -106,7 +106,7 @@ func (message Message) NotRead() bool { // SetState implements the model.StateSetter interface, and // updates the message.StateID by wrapping the MarkXXX() methods. // This method is primarily used by HTML templates in the -// render pipeline. Services and handlers written in Go should +// build pipeline. Services and handlers written in Go should // probably use MarkRead(), MarkUnread(), etc. directly. func (message *Message) SetState(stateID string) { diff --git a/model/step/addModelObject.go b/model/step/addModelObject.go index ec04bd159..c80df4dd1 100644 --- a/model/step/addModelObject.go +++ b/model/step/addModelObject.go @@ -36,5 +36,5 @@ func NewAddModelObject(stepInfo mapof.Any) (AddModelObject, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step AddModelObject) AmStep() {} diff --git a/model/step/addStream.go b/model/step/addStream.go index 5b60903f3..2d94efec8 100644 --- a/model/step/addStream.go +++ b/model/step/addStream.go @@ -55,5 +55,5 @@ func NewAddStream(stepInfo mapof.Any) (AddStream, error) { return result, nil } -// AmStep is here to verify that this struct is a render pipeline step +// AmStep is here to verify that this struct is a build pipeline step func (step AddStream) AmStep() {} diff --git a/model/step/asConfirmation.go b/model/step/asConfirmation.go index b58f8df23..b0ad83b76 100644 --- a/model/step/asConfirmation.go +++ b/model/step/asConfirmation.go @@ -22,5 +22,5 @@ func NewAsConfirmation(stepInfo mapof.Any) (AsConfirmation, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step AsConfirmation) AmStep() {} diff --git a/model/step/asModal.go b/model/step/asModal.go index de19ac53b..83be8bfc7 100644 --- a/model/step/asModal.go +++ b/model/step/asModal.go @@ -28,5 +28,5 @@ func NewAsModal(stepInfo mapof.Any) (AsModal, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step AsModal) AmStep() {} diff --git a/model/step/asTooltip.go b/model/step/asTooltip.go index 7ea3c47f5..66927a19a 100644 --- a/model/step/asTooltip.go +++ b/model/step/asTooltip.go @@ -24,5 +24,5 @@ func NewAsTooltip(stepInfo mapof.Any) (AsTooltip, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step AsTooltip) AmStep() {} diff --git a/model/step/delete.go b/model/step/delete.go index 750d742a4..5cb466e5f 100644 --- a/model/step/delete.go +++ b/model/step/delete.go @@ -37,5 +37,5 @@ func NewDelete(stepInfo mapof.Any) (Delete, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step Delete) AmStep() {} diff --git a/model/step/deleteAttachments.go b/model/step/deleteAttachments.go index 49c95788a..7ca98bb47 100644 --- a/model/step/deleteAttachments.go +++ b/model/step/deleteAttachments.go @@ -2,7 +2,7 @@ package step import "github.com/benpate/rosetta/mapof" -// DeleteAttachments represents an action that can upload attachments. It can only be used on a StreamRenderer +// DeleteAttachments represents an action that can upload attachments. It can only be used on a StreamBuilder type DeleteAttachments struct { All bool } @@ -14,5 +14,5 @@ func NewDeleteAttachments(stepInfo mapof.Any) (DeleteAttachments, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step DeleteAttachments) AmStep() {} diff --git a/model/step/do.go b/model/step/do.go index 2cfb5b5ab..eb6e65230 100644 --- a/model/step/do.go +++ b/model/step/do.go @@ -16,5 +16,5 @@ func NewDo(stepInfo mapof.Any) (Do, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step Do) AmStep() {} diff --git a/model/step/editConnection.go b/model/step/editConnection.go index ebdf0c281..fbbfa6173 100644 --- a/model/step/editConnection.go +++ b/model/step/editConnection.go @@ -9,5 +9,5 @@ func NewEditConnection(stepInfo mapof.Any) (EditConnection, error) { return EditConnection{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step EditConnection) AmStep() {} diff --git a/model/step/editContent.go b/model/step/editContent.go index 6cbeb1195..218379844 100644 --- a/model/step/editContent.go +++ b/model/step/editContent.go @@ -19,5 +19,5 @@ func NewEditContent(stepInfo mapof.Any) (EditContent, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step EditContent) AmStep() {} diff --git a/model/step/editModelObject.go b/model/step/editModelObject.go index 5e8f92de2..b2bb55846 100644 --- a/model/step/editModelObject.go +++ b/model/step/editModelObject.go @@ -44,5 +44,5 @@ func NewEditModelObject(stepInfo mapof.Any) (EditModelObject, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step EditModelObject) AmStep() {} diff --git a/model/step/editTable.go b/model/step/editTable.go index 72e26bca7..2f0435704 100644 --- a/model/step/editTable.go +++ b/model/step/editTable.go @@ -27,5 +27,5 @@ func NewTableEditor(stepInfo mapof.Any) (TableEditor, error) { }, nil } -// AmStep is here to verify that this struct is a render pipeline step +// AmStep is here to verify that this struct is a build pipeline step func (step TableEditor) AmStep() {} diff --git a/model/step/editWidget.go b/model/step/editWidget.go index fb9ab3dc1..b63d650ec 100644 --- a/model/step/editWidget.go +++ b/model/step/editWidget.go @@ -5,7 +5,7 @@ import ( ) // EditWidget represents an action-step that locates an existing widget and -// creates a renderer for it. +// creates a builder for it. type EditWidget struct{} // NewEditWidget returns a fully initialized EditWidget object @@ -13,5 +13,5 @@ func NewEditWidget(stepInfo mapof.Any) (EditWidget, error) { return EditWidget{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step EditWidget) AmStep() {} diff --git a/model/step/forwardTo.go b/model/step/forwardTo.go index 9da0b010e..df2f8b4b1 100644 --- a/model/step/forwardTo.go +++ b/model/step/forwardTo.go @@ -28,5 +28,5 @@ func NewForwardTo(stepInfo mapof.Any) (ForwardTo, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step ForwardTo) AmStep() {} diff --git a/model/step/halt.go b/model/step/halt.go index 05058ff81..15c51143d 100644 --- a/model/step/halt.go +++ b/model/step/halt.go @@ -12,5 +12,5 @@ func NewHalt(stepInfo mapof.Any) (Halt, error) { return Halt{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step Halt) AmStep() {} diff --git a/model/step/ifCondition.go b/model/step/ifCondition.go index e10aad91d..586daa0f4 100644 --- a/model/step/ifCondition.go +++ b/model/step/ifCondition.go @@ -46,5 +46,5 @@ func NewIfCondition(stepInfo mapof.Any) (IfCondition, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step IfCondition) AmStep() {} diff --git a/model/step/inlineError.go b/model/step/inlineError.go index b2cfe5016..1b2acf9b6 100644 --- a/model/step/inlineError.go +++ b/model/step/inlineError.go @@ -25,5 +25,5 @@ func NewInlineError(stepInfo mapof.Any) (InlineError, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step InlineError) AmStep() {} diff --git a/model/step/inlineSuccess.go b/model/step/inlineSuccess.go index e13e94a83..63b111c0c 100644 --- a/model/step/inlineSuccess.go +++ b/model/step/inlineSuccess.go @@ -25,5 +25,5 @@ func NewInlineSuccess(stepInfo mapof.Any) (InlineSuccess, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step InlineSuccess) AmStep() {} diff --git a/model/step/processContent.go b/model/step/processContent.go index f99f34b07..cecbe67de 100644 --- a/model/step/processContent.go +++ b/model/step/processContent.go @@ -21,5 +21,5 @@ func NewProcessContent(stepInfo mapof.Any) (ProcessContent, error) { }, nil } -// AmStep is here to verify that this struct is a render pipeline step +// AmStep is here to verify that this struct is a build pipeline step func (step ProcessContent) AmStep() {} diff --git a/model/step/promoteDraft.go b/model/step/promoteDraft.go index 8a5117c66..472555013 100644 --- a/model/step/promoteDraft.go +++ b/model/step/promoteDraft.go @@ -16,5 +16,5 @@ func NewStreamPromoteDraft(stepInfo mapof.Any) (StreamPromoteDraft, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step StreamPromoteDraft) AmStep() {} diff --git a/model/step/publish.go b/model/step/publish.go index ac03d96e6..6a4d00d06 100644 --- a/model/step/publish.go +++ b/model/step/publish.go @@ -10,5 +10,5 @@ func NewPublish(stepInfo mapof.Any) (Publish, error) { return Publish{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step Publish) AmStep() {} diff --git a/model/step/redirectTo.go b/model/step/redirectTo.go index eb1872824..0ccb16cae 100644 --- a/model/step/redirectTo.go +++ b/model/step/redirectTo.go @@ -28,5 +28,5 @@ func NewRedirectTo(stepInfo mapof.Any) (RedirectTo, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step RedirectTo) AmStep() {} diff --git a/model/step/refreshPage.go b/model/step/refreshPage.go index e77aa7446..d7f0f0bee 100644 --- a/model/step/refreshPage.go +++ b/model/step/refreshPage.go @@ -10,5 +10,5 @@ func NewRefreshPage(stepInfo mapof.Any) (RefreshPage, error) { return RefreshPage{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step RefreshPage) AmStep() {} diff --git a/model/step/reloadPage.go b/model/step/reloadPage.go index 004e5d33a..7774695b1 100644 --- a/model/step/reloadPage.go +++ b/model/step/reloadPage.go @@ -10,5 +10,5 @@ func NewReloadPage(stepInfo mapof.Any) (ReloadPage, error) { return ReloadPage{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step ReloadPage) AmStep() {} diff --git a/model/step/removeEvent.go b/model/step/removeEvent.go index 939fb3c62..9f2472cca 100644 --- a/model/step/removeEvent.go +++ b/model/step/removeEvent.go @@ -17,5 +17,5 @@ func NewRemoveEvent(stepInfo mapof.Any) (RemoveEvent, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step RemoveEvent) AmStep() {} diff --git a/model/step/save.go b/model/step/save.go index fc80335b9..8d4367e61 100644 --- a/model/step/save.go +++ b/model/step/save.go @@ -26,5 +26,5 @@ func NewSave(stepInfo mapof.Any) (Save, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step Save) AmStep() {} diff --git a/model/step/sendEmail.go b/model/step/sendEmail.go index 7b29bace7..afd6419e0 100644 --- a/model/step/sendEmail.go +++ b/model/step/sendEmail.go @@ -16,5 +16,5 @@ func NewSendEmail(stepInfo mapof.Any) (SendEmail, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SendEmail) AmStep() {} diff --git a/model/step/serverRedirect.go b/model/step/serverRedirect.go index 248a046a8..099deca07 100644 --- a/model/step/serverRedirect.go +++ b/model/step/serverRedirect.go @@ -20,5 +20,5 @@ func NewServerRedirect(stepInfo mapof.Any) (ServerRedirect, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step ServerRedirect) AmStep() {} diff --git a/model/step/setData.go b/model/step/setData.go index a0573c65f..904f898c1 100644 --- a/model/step/setData.go +++ b/model/step/setData.go @@ -38,5 +38,5 @@ func NewSetData(stepInfo mapof.Any) (SetData, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetData) AmStep() {} diff --git a/model/step/setHeader.go b/model/step/setHeader.go index e3b91de12..f4ec0fb55 100644 --- a/model/step/setHeader.go +++ b/model/step/setHeader.go @@ -31,5 +31,5 @@ func NewSetHeader(stepInfo mapof.Any) (SetHeader, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetHeader) AmStep() {} diff --git a/model/step/setQueryParam.go b/model/step/setQueryParam.go index 81f8bc932..26e731e3a 100644 --- a/model/step/setQueryParam.go +++ b/model/step/setQueryParam.go @@ -34,5 +34,5 @@ func NewSetQueryParam(stepInfo mapof.Any) (SetQueryParam, error) { return result, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetQueryParam) AmStep() {} diff --git a/model/step/setRenderData.go b/model/step/setRenderData.go index c43c4655d..420a0cff9 100644 --- a/model/step/setRenderData.go +++ b/model/step/setRenderData.go @@ -8,7 +8,7 @@ import ( "github.com/benpate/rosetta/mapof" ) -// SetRenderData represents an action-step that can update the custom data stored in a renderer +// SetRenderData represents an action-step that can update the custom data stored in a builder type SetRenderData struct { Values map[string]*template.Template // values to set directly into the object } @@ -34,5 +34,5 @@ func NewSetRenderData(stepInfo mapof.Any) (SetRenderData, error) { return result, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetRenderData) AmStep() {} diff --git a/model/step/setResponse.go b/model/step/setResponse.go index 317c496de..a0b2a255f 100644 --- a/model/step/setResponse.go +++ b/model/step/setResponse.go @@ -11,5 +11,5 @@ func NewSetResponse(stepInfo mapof.Any) (SetResponse, error) { return SetResponse{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetResponse) AmStep() {} diff --git a/model/step/setSimpleSharing.go b/model/step/setSimpleSharing.go index 72986e45e..7d2f03f3e 100644 --- a/model/step/setSimpleSharing.go +++ b/model/step/setSimpleSharing.go @@ -22,5 +22,5 @@ func NewSetSimpleSharing(stepInfo mapof.Any) (SetSimpleSharing, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetSimpleSharing) AmStep() {} diff --git a/model/step/setState.go b/model/step/setState.go index ae4399151..b0199b880 100644 --- a/model/step/setState.go +++ b/model/step/setState.go @@ -14,5 +14,5 @@ func NewSetState(stepInfo mapof.Any) (SetState, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetState) AmStep() {} diff --git a/model/step/setThumbnail.go b/model/step/setThumbnail.go index fd19fb0fd..2bda2f631 100644 --- a/model/step/setThumbnail.go +++ b/model/step/setThumbnail.go @@ -13,5 +13,5 @@ func NewSetThumbnail(stepInfo mapof.Any) (SetThumbnail, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SetThumbnail) AmStep() {} diff --git a/model/step/sort.go b/model/step/sort.go index eb7707b98..2da663cdf 100644 --- a/model/step/sort.go +++ b/model/step/sort.go @@ -21,5 +21,5 @@ func NewSort(stepInfo mapof.Any) (Sort, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step Sort) AmStep() {} diff --git a/model/step/sortAttachments.go b/model/step/sortAttachments.go index c3c303759..76bf3e0de 100644 --- a/model/step/sortAttachments.go +++ b/model/step/sortAttachments.go @@ -21,5 +21,5 @@ func NewSortAttachments(stepInfo mapof.Any) (SortAttachments, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SortAttachments) AmStep() {} diff --git a/model/step/sortWidgets.go b/model/step/sortWidgets.go index bac72b4ca..f92446d2a 100644 --- a/model/step/sortWidgets.go +++ b/model/step/sortWidgets.go @@ -12,5 +12,5 @@ func NewSortWidgets(stepInfo mapof.Any) (SortWidgets, error) { return SortWidgets{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step SortWidgets) AmStep() {} diff --git a/model/step/step.go b/model/step/step.go index 3802beef5..602efc462 100644 --- a/model/step/step.go +++ b/model/step/step.go @@ -1,5 +1,5 @@ -// Package Step encapsulates the DATA required for each pipeline step in the renderer. -// This package does not contain any rendering functions (that's in /render) but these +// Package Step encapsulates the DATA required for each pipeline step in the builder. +// This package does not contain any building functions (that's in /build) but these // objects know how to parse and "compile" raw data into the arguments required to execute // each step. package step @@ -199,7 +199,7 @@ func New(stepInfo mapof.Any) (Step, error) { return nil, derp.NewInternalError("model.step.New", "Unrecognized step type", stepInfo) } -// NewPipeline parses a series of render steps into a new array +// NewPipeline parses a series of build steps into a new array func NewPipeline[T ~map[string]any](stepInfo []T) ([]Step, error) { const location = "model.step.NewPipeline" diff --git a/model/step/triggerEvent.go b/model/step/triggerEvent.go index ac1a79d14..a74a7ec7d 100644 --- a/model/step/triggerEvent.go +++ b/model/step/triggerEvent.go @@ -28,5 +28,5 @@ func NewTriggerEvent(stepInfo mapof.Any) (TriggerEvent, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step TriggerEvent) AmStep() {} diff --git a/model/step/unPublish.go b/model/step/unPublish.go index 7257bbd83..89d697f50 100644 --- a/model/step/unPublish.go +++ b/model/step/unPublish.go @@ -14,5 +14,5 @@ func NewUnPublish(stepInfo mapof.Any) (UnPublish, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step UnPublish) AmStep() {} diff --git a/model/step/uploadAttachment.go b/model/step/uploadAttachment.go index ff991e25b..f8c35fbc6 100644 --- a/model/step/uploadAttachment.go +++ b/model/step/uploadAttachment.go @@ -2,7 +2,7 @@ package step import "github.com/benpate/rosetta/mapof" -// UploadAttachment represents an action that can upload attachments. It can only be used on a StreamRenderer +// UploadAttachment represents an action that can upload attachments. It can only be used on a StreamBuilder type UploadAttachment struct { Maximum int } @@ -14,5 +14,5 @@ func NewUploadAttachment(stepInfo mapof.Any) (UploadAttachment, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step UploadAttachment) AmStep() {} diff --git a/model/step/viewFeed.go b/model/step/viewFeed.go index ee120f421..525e48145 100644 --- a/model/step/viewFeed.go +++ b/model/step/viewFeed.go @@ -2,7 +2,7 @@ package step import "github.com/benpate/rosetta/mapof" -// ViewFeed represents an action-step that can render a Stream into HTML +// ViewFeed represents an action-step that can build a Stream into HTML type ViewFeed struct { } @@ -12,5 +12,5 @@ func NewViewFeed(stepInfo mapof.Any) (ViewFeed, error) { return ViewFeed{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step ViewFeed) AmStep() {} diff --git a/model/step/viewHTML.go b/model/step/viewHTML.go index 8767f616b..ea19b137c 100644 --- a/model/step/viewHTML.go +++ b/model/step/viewHTML.go @@ -5,7 +5,7 @@ import ( "github.com/benpate/rosetta/mapof" ) -// ViewHTML represents an action-step that can render a Stream into HTML +// ViewHTML represents an action-step that can build a Stream into HTML type ViewHTML struct { File string Method string @@ -20,5 +20,5 @@ func NewViewHTML(stepInfo mapof.Any) (ViewHTML, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step ViewHTML) AmStep() {} diff --git a/model/step/viewJSONLD.go b/model/step/viewJSONLD.go index fe00e1643..5ca73a1d8 100644 --- a/model/step/viewJSONLD.go +++ b/model/step/viewJSONLD.go @@ -5,7 +5,7 @@ import ( "github.com/benpate/rosetta/mapof" ) -// ViewJSONLD represents an action-step that can render a Stream into HTML +// ViewJSONLD represents an action-step that can build a Stream into HTML type ViewJSONLD struct { Method string } @@ -18,5 +18,5 @@ func NewViewJSONLD(stepInfo mapof.Any) (ViewJSONLD, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step ViewJSONLD) AmStep() {} diff --git a/model/step/webSub.go b/model/step/webSub.go index 10aac5a64..2cb7f4a86 100644 --- a/model/step/webSub.go +++ b/model/step/webSub.go @@ -2,7 +2,7 @@ package step import "github.com/benpate/rosetta/mapof" -// WebSub represents an action-step that can render a Stream into HTML +// WebSub represents an action-step that can build a Stream into HTML type WebSub struct { } @@ -11,5 +11,5 @@ func NewWebSub(stepInfo mapof.Any) (WebSub, error) { return WebSub{}, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WebSub) AmStep() {} diff --git a/model/step/withChildren.go b/model/step/withChildren.go index aec01a787..563e6dd5c 100644 --- a/model/step/withChildren.go +++ b/model/step/withChildren.go @@ -27,5 +27,5 @@ func NewWithChildren(stepInfo mapof.Any) (WithChildren, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithChildren) AmStep() {} diff --git a/model/step/withDraft.go b/model/step/withDraft.go index 3b2af6d0c..2ce473b1c 100644 --- a/model/step/withDraft.go +++ b/model/step/withDraft.go @@ -14,7 +14,7 @@ type WithDraft struct { // NewWithDraft returns a fully initialized WithDraft object func NewWithDraft(stepInfo mapof.Any) (WithDraft, error) { - const location = "render.NewWithDraft" + const location = "build.NewWithDraft" subSteps, err := NewPipeline(convert.SliceOfMap(stepInfo["steps"])) @@ -27,5 +27,5 @@ func NewWithDraft(stepInfo mapof.Any) (WithDraft, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithDraft) AmStep() {} diff --git a/model/step/withFolder.go b/model/step/withFolder.go index 7b29db178..49ca22763 100644 --- a/model/step/withFolder.go +++ b/model/step/withFolder.go @@ -27,5 +27,5 @@ func NewWithFolder(stepInfo mapof.Any) (WithFolder, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithFolder) AmStep() {} diff --git a/model/step/withFollower.go b/model/step/withFollower.go index 50a84b341..8b94e266c 100644 --- a/model/step/withFollower.go +++ b/model/step/withFollower.go @@ -27,5 +27,5 @@ func NewWithFollower(stepInfo mapof.Any) (WithFollower, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithFollower) AmStep() {} diff --git a/model/step/withFollowing.go b/model/step/withFollowing.go index eb190918b..b8dcd927c 100644 --- a/model/step/withFollowing.go +++ b/model/step/withFollowing.go @@ -27,5 +27,5 @@ func NewWithFollowing(stepInfo mapof.Any) (WithFollowing, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithFollowing) AmStep() {} diff --git a/model/step/withMessage.go b/model/step/withMessage.go index 6402c3c61..ffcaf2d67 100644 --- a/model/step/withMessage.go +++ b/model/step/withMessage.go @@ -27,5 +27,5 @@ func NewWithMessage(stepInfo mapof.Any) (WithMessage, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithMessage) AmStep() {} diff --git a/model/step/withNextSibling.go b/model/step/withNextSibling.go index cf194bd43..43f2a1c8f 100644 --- a/model/step/withNextSibling.go +++ b/model/step/withNextSibling.go @@ -27,5 +27,5 @@ func NewWithNextSibling(stepInfo mapof.Any) (WithNextSibling, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithNextSibling) AmStep() {} diff --git a/model/step/withParent.go b/model/step/withParent.go index d5b38426c..73bb6f08d 100644 --- a/model/step/withParent.go +++ b/model/step/withParent.go @@ -14,7 +14,7 @@ type WithParent struct { // NewWithParent returns a fully initialized WithParent object func NewWithParent(stepInfo mapof.Any) (WithParent, error) { - const location = "render.NewWithParent" + const location = "build.NewWithParent" subSteps, err := NewPipeline(convert.SliceOfMap(stepInfo["steps"])) @@ -27,5 +27,5 @@ func NewWithParent(stepInfo mapof.Any) (WithParent, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithParent) AmStep() {} diff --git a/model/step/withPrevSibling.go b/model/step/withPrevSibling.go index 4a898a6e7..faf983794 100644 --- a/model/step/withPrevSibling.go +++ b/model/step/withPrevSibling.go @@ -27,5 +27,5 @@ func NewWithPrevSibling(stepInfo mapof.Any) (WithPrevSibling, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithPrevSibling) AmStep() {} diff --git a/model/step/withResponse.go b/model/step/withResponse.go index 6f2f0bf09..3f3e0e41b 100644 --- a/model/step/withResponse.go +++ b/model/step/withResponse.go @@ -27,5 +27,5 @@ func NewWithResponse(stepInfo mapof.Any) (WithResponse, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithResponse) AmStep() {} diff --git a/model/step/withRule.go b/model/step/withRule.go index e5d16508f..85de15157 100644 --- a/model/step/withRule.go +++ b/model/step/withRule.go @@ -27,5 +27,5 @@ func NewWithRule(stepInfo mapof.Any) (WithRule, error) { }, nil } -// AmStep is here only to verify that this struct is a render pipeline step +// AmStep is here only to verify that this struct is a build pipeline step func (step WithRule) AmStep() {} diff --git a/model/stream.go b/model/stream.go index 2a653c3a7..2dec62f79 100644 --- a/model/stream.go +++ b/model/stream.go @@ -22,7 +22,7 @@ type Stream struct { ParentIDs id.Slice `json:"parentIds" bson:"parentIds"` // List of all parent IDs, including the current parent. This is used to generate "breadcrumbs" for the Stream. Rank int `json:"rank" bson:"rank"` // If Template uses a custom sort order, then this is the value used to determine the position of this Stream. NavigationID string `json:"navigationId" bson:"navigationId"` // Unique identifier of the "top-level" Stream that this record falls within. - TemplateID string `json:"templateId" bson:"templateId"` // Unique identifier (name) of the Template to use when rendering this Stream in HTML. + TemplateID string `json:"templateId" bson:"templateId"` // Unique identifier (name) of the Template to use when building this Stream in HTML. ParentTemplateID string `json:"parentTemplateId" bson:"parentTemplateId"` // Unique identifier (name) of the parent's Template. StateID string `json:"stateId" bson:"stateId"` // Unique identifier of the State this Stream is in. This is used to populate the State information from the Template service at load time. SocialRole string `json:"socialRole,omitempty" bson:"socialRole,omitempty"` // Role to use for this Stream in social integrations (Article, Note, Image, etc) @@ -34,7 +34,7 @@ type Stream struct { Summary string `json:"summary,omitempty" bson:"summary,omitempty"` // Brief summary of the document ImageURL string `json:"imageUrl,omitempty" bson:"imageUrl,omitempty"` // URL of the cover image for this document's image Content Content `json:"content,omitempty" bson:"content,omitempty"` // Body content object for this Stream. - Widgets set.Slice[StreamWidget] `json:"widgets,omitempty" bson:"widgets,omitempty"` // Additional widgets to include when rendering this Stream. + Widgets set.Slice[StreamWidget] `json:"widgets,omitempty" bson:"widgets,omitempty"` // Additional widgets to include when building this Stream. Tags sliceof.Object[Tag] `json:"tags,omitempty" bson:"tags,omitempty"` // List of tags that are associated with this document Data mapof.Any `json:"data,omitempty" bson:"data,omitempty"` // Set of data to populate into the Template. This is validated by the JSON-Schema of the Template. AttributedTo PersonLink `json:"attributedTo,omitempty" bson:"attributedTo,omitempty"` // List of people who are attributed to this document diff --git a/model/streamSummary.go b/model/streamSummary.go index 595300c0b..6281dd8de 100644 --- a/model/streamSummary.go +++ b/model/streamSummary.go @@ -10,12 +10,12 @@ type StreamSummary struct { ObjectID primitive.ObjectID `json:"streamId" bson:"_id"` // Unique identifier of this Stream. (NOT USED PUBLICLY) ParentObjectID primitive.ObjectID `json:"parentId" bson:"parentId"` // Unique identifier of the "parent" stream. (NOT USED PUBLICLY) Token string `json:"token" bson:"token"` // Unique value that identifies this element in the URL - TemplateID string `json:"templateId" bson:"templateId"` // Unique identifier (name) of the Template to use when rendering this Stream in HTML. + TemplateID string `json:"templateId" bson:"templateId"` // Unique identifier (name) of the Template to use when building this Stream in HTML. URL string `json:"url,omitempty" bson:"url,omitempty"` // URL of the original document Label string `json:"label,omitempty" bson:"label,omitempty"` // Label/Title of the document Summary string `json:"summary,omitempty" bson:"summary,omitempty"` // Brief summary of the document Content Content `json:"content,omitempty" bson:"content,omitempty"` // Content of the document - Data mapof.Any `json:"data,omitempty" bson:"data,omitempty"` // Additional data that is specific to the Template used to render this Stream + Data mapof.Any `json:"data,omitempty" bson:"data,omitempty"` // Additional data that is specific to the Template used to build this Stream ImageURL string `json:"imageUrl,omitempty" bson:"imageUrl,omitempty"` // URL of the cover image for this document's image AttributedTo PersonLink `json:"attributedTo,omitempty" bson:"attributedTo,omitempty"` // List of people who are attributed to this document InReplyTo string `json:"inReplyTo,omitempty" bson:"inReplyTo,omitempty"` // If this stream is a reply to another stream or web page, then this links to the original document. diff --git a/model/streamWidget.go b/model/streamWidget.go index c6b798abd..cb87ad211 100644 --- a/model/streamWidget.go +++ b/model/streamWidget.go @@ -12,7 +12,7 @@ type StreamWidget struct { Label string `json:"label" bson:"label"` Data mapof.Any `json:"data" bson:"data"` - // These values are not stored in the database, but injected during rendering + // These values are not stored in the database, but injected during building Stream *Stream `json:"-" bson:"-"` Widget Widget `json:"-" bson:"-"` } diff --git a/model/template.go b/model/template.go index 50a50e93b..736fc4066 100644 --- a/model/template.go +++ b/model/template.go @@ -12,7 +12,7 @@ import ( "github.com/benpate/rosetta/sliceof" ) -// Template represents an HTML template used for rendering Streams +// Template represents an HTML template used for building Streams type Template struct { TemplateID string `json:"templateId" bson:"templateId"` // Internal name/token other objects (like streams) will use to reference this Template. URL string `json:"url" bson:"url"` // URL where this template is published diff --git a/model/theme.go b/model/theme.go index 3a59e69bd..085047421 100644 --- a/model/theme.go +++ b/model/theme.go @@ -10,7 +10,7 @@ import ( "github.com/benpate/rosetta/mapof" ) -// Theme represents an HTML template used for rendering all hard-coded application elements (but not dynamic streams) +// Theme represents an HTML template used for building all hard-coded application elements (but not dynamic streams) type Theme struct { ThemeID string `json:"themeID" bson:"themeID"` // Internal name/token other objects (like streams) will use to reference this Theme. Extends []string `json:"extends" bson:"extends"` // List of other themes that this theme extends diff --git a/render/step_AddModelObject.go b/render/step_AddModelObject.go deleted file mode 100644 index cd1fb870d..000000000 --- a/render/step_AddModelObject.go +++ /dev/null @@ -1,83 +0,0 @@ -package render - -import ( - "io" - - "github.com/EmissarySocial/emissary/model/step" - "github.com/benpate/derp" - "github.com/benpate/form" -) - -// StepAddModelObject is an action that can add new model objects of any type -type StepAddModelObject struct { - Form form.Element - Defaults []step.Step -} - -// Get displays a modal form that lets users enter data for their new model object. -func (step StepAddModelObject) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - - factory := renderer.factory() - schema := renderer.schema() - object := renderer.object() - - // First, try to execute any "default" steps so that the object is initialized - result := Pipeline(step.Defaults).Get(factory, renderer, buffer) - - if result.Halt { - result.Error = derp.Wrap(result.Error, "render.StepAddModelObject.Get", "Error executing default steps") - return UseResult(result) - } - - // Try to render the Form HTML - formHTML, err := form.Editor(schema, step.Form, object, renderer.lookupProvider()) - - if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepAddModelObject.Get", "Error generating form")) - } - - formHTML = WrapForm(renderer.URL(), formHTML) - - // Wrap formHTML as a modal dialog - // nolint:errcheck - io.WriteString(buffer, formHTML) - return nil -} - -// Post initializes a new model object, populates it with data from the form, then saves it to the database. -func (step StepAddModelObject) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - - // This finds/creates a new object in the renderer - factory := renderer.factory() - request := renderer.request() - object := renderer.object() - schema := renderer.schema() - - // Execute any "default" steps so that the object is initialized - result := Pipeline(step.Defaults).Post(factory, renderer, buffer) - - if result.Halt { - result.Error = derp.Wrap(result.Error, "render.StepAddModelObject.Post", "Error executing default steps") - return UseResult(result) - } - - // Parse form information - if err := request.ParseForm(); err != nil { - return Halt().WithError(derp.Wrap(err, "render.AddModelObject.Post", "Error parsing form data")) - } - - // Try to set each path from the Form into the renderer. Note: schema.Set also converts and validated inputs before setting. - for key, value := range request.Form { - if err := schema.Set(object, key, value); err != nil { - return Halt().WithError(derp.Wrap(err, "render.AddModelObject.Post", "Error setting path value", key, value)) - } - } - - // Save the object to the database - if err := renderer.service().ObjectSave(object, "Created"); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepAddModelObject.Post", "Error saving model object to database")) - } - - // Success! - return nil -} diff --git a/render/step_EditContent.go b/render/step_EditContent.go deleted file mode 100644 index 46c099e32..000000000 --- a/render/step_EditContent.go +++ /dev/null @@ -1,73 +0,0 @@ -package render - -import ( - "bytes" - "io" - - "github.com/EmissarySocial/emissary/model" - "github.com/benpate/derp" - "github.com/benpate/rosetta/mapof" -) - -// StepEditContent represents an action-step that can edit/update Container in a streamDraft. -type StepEditContent struct { - Filename string - Format string -} - -func (step StepEditContent) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - - if err := renderer.executeTemplate(buffer, step.Filename, renderer); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditContent.Get", "Error executing template")) - } - - return nil -} - -func (step StepEditContent) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - - var rawContent string - - // Require that we're working with a Stream - stream, ok := renderer.object().(*model.Stream) - - if !ok { - return Halt().WithError(derp.NewInternalError("render.StepEditContent.Post", "step: EditContent can only be used on a Stream")) - } - - // Try to read the content from the request body - switch step.Format { - - // EditorJS writes directly to the request body - case model.ContentFormatEditorJS: - var buffer bytes.Buffer - - if _, err := io.Copy(&buffer, renderer.request().Body); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditContent.Post", "Error reading request data")) - } - - rawContent = buffer.String() - - // All other types are a Form post - default: - - body := mapof.NewAny() - if err := bind(renderer.request(), &body); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditContent.Post", "Error parsing request data")) - } - - rawContent, _ = body.GetStringOK("content") - } - - // Set the new Content value in the Stream - contentService := renderer.factory().Content() - stream.Content = contentService.New(step.Format, rawContent) - - // Try to save the object back to the database - if err := renderer.service().ObjectSave(stream, "Content edited"); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepEditContent.Post", "Error saving stream")) - } - - // Success! - return nil -} diff --git a/render/step_Error.go b/render/step_Error.go deleted file mode 100644 index acbcbc08c..000000000 --- a/render/step_Error.go +++ /dev/null @@ -1,20 +0,0 @@ -package render - -import ( - "io" - - "github.com/EmissarySocial/emissary/model/step" - "github.com/benpate/derp" -) - -type StepError struct { - Original step.Step -} - -func (step StepError) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return Halt().WithError(derp.NewInternalError("render.StepError", "Unrecognized Pipeline Step", "This should never happen", renderer.ActionID(), renderer.Action(), renderer.Action().Steps, renderer.object(), step.Original)) -} - -func (step StepError) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - return Halt().WithError(derp.NewInternalError("render.StepError", "Unrecognized Pipeline Step", "This should never happen", step.Original)) -} diff --git a/render/step_InlineError.go b/render/step_InlineError.go deleted file mode 100644 index 33494af0d..000000000 --- a/render/step_InlineError.go +++ /dev/null @@ -1,26 +0,0 @@ -package render - -import ( - "io" - "text/template" -) - -// StepInlineError represents an action-step that can render a Stream into HTML -type StepInlineError struct { - Message *template.Template -} - -// Get renders the Stream HTML to the context -func (step StepInlineError) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return nil -} - -func (step StepInlineError) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - result := executeTemplate(step.Message, renderer) - - if _, err := buffer.Write([]byte(`` + result + ``)); err != nil { - return Halt().WithError(err) - } - - return Halt().WithHeader("HX-Reswap", "innerHTML").WithHeader("HX-Retarget", "#htmx-response-message") -} diff --git a/render/step_InlineSuccess.go b/render/step_InlineSuccess.go deleted file mode 100644 index 021d67c25..000000000 --- a/render/step_InlineSuccess.go +++ /dev/null @@ -1,25 +0,0 @@ -package render - -import ( - "io" - "text/template" -) - -// StepInlineSuccess represents an action-step that can render a Stream into HTML -type StepInlineSuccess struct { - Message *template.Template -} - -// Get renders the Stream HTML to the context -func (step StepInlineSuccess) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return nil -} - -func (step StepInlineSuccess) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - result := executeTemplate(step.Message, renderer) - - if _, err := buffer.Write([]byte(`` + result + ``)); err != nil { - return Halt().WithError(err) - } - return Halt().WithHeader("HX-Reswap", "innerHTML").WithHeader("HX-Retarget", "#htmx-response-message") -} diff --git a/render/step_PromoteDraft.go b/render/step_PromoteDraft.go deleted file mode 100644 index 77e994beb..000000000 --- a/render/step_PromoteDraft.go +++ /dev/null @@ -1,37 +0,0 @@ -package render - -import ( - "io" - - "github.com/benpate/derp" -) - -// StepStreamPromoteDraft represents an action-step that can copy the Container from a StreamDraft into its corresponding Stream -type StepStreamPromoteDraft struct { - StateID string -} - -func (step StepStreamPromoteDraft) Get(renderer Renderer, _ io.Writer) PipelineBehavior { - return nil -} - -// Post copies relevant information from the draft into the primary stream, then deletes the draft -func (step StepStreamPromoteDraft) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - - streamRenderer := renderer.(*Stream) - - factory := renderer.factory() - - // Try to load the draft from the database, overwriting the stream already in the renderer - stream, err := factory.StreamDraft().Promote(renderer.objectID(), step.StateID) - - if err != nil { - return Halt().WithError(derp.Wrap(err, "renderer.StepStreamPromoteDraft.Post", "Error publishing draft")) - } - - // Push the newly updated stream back to the renderer so that subsequent - // steps (e.g. publish) can use the correct data. - streamRenderer._stream = &stream - - return nil -} diff --git a/render/step_SendEmail.go b/render/step_SendEmail.go deleted file mode 100644 index 8363fcc68..000000000 --- a/render/step_SendEmail.go +++ /dev/null @@ -1,47 +0,0 @@ -package render - -import ( - "io" - - "github.com/benpate/derp" -) - -// StepSendEmail represents an action-step that can send a named email to a recipient -type StepSendEmail struct { - Email string -} - -func (step StepSendEmail) Get(_ Renderer, _ io.Writer) PipelineBehavior { - return nil -} - -// Post saves the object to the database -func (step StepSendEmail) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - factory := renderer.factory() - emailService := factory.Email() - - userRenderer, ok := renderer.(User) - - if !ok { - return Halt().WithError(derp.NewInternalError("render.StepSendEmail.Post", "Invalid Renderer", "Renderer must be Admin/User")) - } - - switch step.Email { - - case "welcome": - - if err := emailService.SendWelcome(userRenderer._user); err != nil { - return Halt().WithError(err) - } - - case "password-reset": - if err := emailService.SendPasswordReset(userRenderer._user); err != nil { - return Halt().WithError(err) - } - - default: - return Halt().WithError(derp.NewInternalError("render.StepSendEmail.Post", "Invalid email name", "Name must be 'welcome' or 'password-reset'")) - } - - return nil -} diff --git a/render/step_ServerRedirect.go b/render/step_ServerRedirect.go deleted file mode 100644 index 768585d21..000000000 --- a/render/step_ServerRedirect.go +++ /dev/null @@ -1,54 +0,0 @@ -package render - -import ( - "io" - - "github.com/benpate/derp" -) - -// StepServerRedirect represents an action-step that continues rendering the output stream as -// a GET request to a new action. -type StepServerRedirect struct { - On string // "get" or "post" or "both" - Action string -} - -func (step StepServerRedirect) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - - if step.On == "post" { - return nil - } - - return step.redirect(renderer, buffer) -} - -// Post updates the stream with approved data from the request body. -func (step StepServerRedirect) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - if step.On == "get" { - return nil - } - - return step.redirect(renderer, renderer.response()) -} - -// redirect creates a new renderer on this object with the requested Action and then continues as a GET request. -func (step StepServerRedirect) redirect(renderer Renderer, buffer io.Writer) PipelineBehavior { - - newRenderer, err := renderer.clone(step.Action) - - if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepServerRedirect.Redirect", "Error creating new renderer")) - } - - result, err := newRenderer.Render() - - if err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepServerRedirect.Redirect", "Error rendering new page")) - } - - if _, err := buffer.Write([]byte(result)); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepServerRedirect.Redirect", "Error writing output buffer")) - } - - return nil -} diff --git a/render/step_SetHeader.go b/render/step_SetHeader.go deleted file mode 100644 index e1a719641..000000000 --- a/render/step_SetHeader.go +++ /dev/null @@ -1,44 +0,0 @@ -package render - -import ( - "bytes" - "io" - "text/template" - - "github.com/benpate/derp" -) - -// StepSetHeader represents an action-step that can update the custom data stored in a Stream -type StepSetHeader struct { - Method string - Name string - Value *template.Template -} - -func (step StepSetHeader) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - if step.Method == "post" { - return nil - } - return step.setHeader(renderer) -} - -// Post updates the stream with approved data from the request body. -func (step StepSetHeader) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - if step.Method == "get" { - return nil - } - return step.setHeader(renderer) -} - -func (step StepSetHeader) setHeader(renderer Renderer) PipelineBehavior { - - var value bytes.Buffer - - if err := step.Value.Execute(&value, renderer); err != nil { - return Halt().WithError(derp.Wrap(err, "render.StepSetHeader.Post", "Error executing template", step.Value)) - } - - renderer.response().Header().Set(step.Name, value.String()) - - return nil -} diff --git a/render/step_SetQueryParam.go b/render/step_SetQueryParam.go deleted file mode 100644 index 042272a11..000000000 --- a/render/step_SetQueryParam.go +++ /dev/null @@ -1,33 +0,0 @@ -package render - -import ( - "io" - "text/template" -) - -// StepSetQueryParam represents an action-step that sets values to the request query string -type StepSetQueryParam struct { - Values map[string]*template.Template -} - -// Get displays a form where users can update stream data -func (step StepSetQueryParam) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.Do(renderer) -} - -// Post updates the stream with approved data from the request body. -func (step StepSetQueryParam) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - return step.Do(renderer) -} - -func (step StepSetQueryParam) Do(renderer Renderer) PipelineBehavior { - query := renderer.request().URL.Query() - - for key, value := range step.Values { - queryValue := executeTemplate(value, renderer) - query.Set(key, queryValue) - } - - renderer.request().URL.RawQuery = query.Encode() - return nil -} diff --git a/render/step_SetRenderData.go b/render/step_SetRenderData.go deleted file mode 100644 index fbeabd207..000000000 --- a/render/step_SetRenderData.go +++ /dev/null @@ -1,30 +0,0 @@ -package render - -import ( - "io" - "text/template" -) - -// StepSetRenderData represents an action-step that sets values to the request query string -type StepSetRenderData struct { - Values map[string]*template.Template -} - -// Get displays a form where users can update stream data -func (step StepSetRenderData) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.Do(renderer) -} - -// Post updates the stream with approved data from the request body. -func (step StepSetRenderData) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - return step.Do(renderer) -} - -func (step StepSetRenderData) Do(renderer Renderer) PipelineBehavior { - for key, value := range step.Values { - queryValue := executeTemplate(value, renderer) - renderer.setString(key, queryValue) - } - - return nil -} diff --git a/render/step_SetState.go b/render/step_SetState.go deleted file mode 100644 index f405e4e00..000000000 --- a/render/step_SetState.go +++ /dev/null @@ -1,36 +0,0 @@ -package render - -import ( - "io" - - "github.com/benpate/derp" -) - -// StepSetState represents an action-step that can change a Stream's state -type StepSetState struct { - State string -} - -func (step StepSetState) Get(renderer Renderer, _ io.Writer) PipelineBehavior { - return nil -} - -// Post updates the stream with configured data, and moves the stream to a new state -func (step StepSetState) Post(renderer Renderer, _ io.Writer) PipelineBehavior { - - // If the renderer is a StateSetter, then try to update the state - if setter, ok := renderer.(StateSetter); ok { - - // This action may still fail (for instance) if the renderer wraps - // a model object that is not a `model.StateSetter` - if err := setter.setState(step.State); err != nil { - return Halt().WithError(derp.Wrap(err, "render.stepSetState.Post", "Error setting state")) - } - - // Success - return nil - } - - // Failure (obv) - return Halt().WithError(derp.NewInternalError("render.stepSetState.Post", "Renderer does not implement StateSetter interface")) -} diff --git a/render/step_WithDraft.go b/render/step_WithDraft.go deleted file mode 100644 index 0697681ee..000000000 --- a/render/step_WithDraft.go +++ /dev/null @@ -1,53 +0,0 @@ -package render - -import ( - "io" - - "github.com/EmissarySocial/emissary/model/step" - "github.com/benpate/derp" -) - -// StepWithDraft represents an action-step that can update the data.DataMap custom data stored in a Stream -type StepWithDraft struct { - SubSteps []step.Step -} - -// Get displays a form where users can update stream data -func (step StepWithDraft) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - - const location = "render.StepWithDraft.Get" - - factory := renderer.factory() - streamRenderer := renderer.(*Stream) - draftRenderer, err := streamRenderer.draftRenderer() - - if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error getting draft renderer")) - } - - // Execute the POST render pipeline on the parent - status := Pipeline(step.SubSteps).Get(factory, &draftRenderer, buffer) - status.Error = derp.Wrap(status.Error, location, "Error executing steps on draft") - - return UseResult(status) -} - -// Post updates the stream with approved data from the request body. -func (step StepWithDraft) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - - const location = "render.StepWithDraft.Post" - - factory := renderer.factory() - streamRenderer := renderer.(*Stream) - draftRenderer, err := streamRenderer.draftRenderer() - - if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error getting draft renderer")) - } - - // Execute the POST render pipeline on the parent - result := Pipeline(step.SubSteps).Post(factory, &draftRenderer, buffer) - result.Error = derp.Wrap(result.Error, location, "Error executing steps on draft") - - return UseResult(result) -} diff --git a/render/step_WithNextSibling.go b/render/step_WithNextSibling.go deleted file mode 100644 index 4f775d229..000000000 --- a/render/step_WithNextSibling.go +++ /dev/null @@ -1,53 +0,0 @@ -package render - -import ( - "io" - - "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/model/step" - "github.com/benpate/derp" -) - -// StepWithNextSibling represents an action-step that can update the data.DataMap custom data stored in a Stream -type StepWithNextSibling struct { - SubSteps []step.Step -} - -func (step StepWithNextSibling) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) -} - -// Post executes the subSteps on the parent Stream -func (step StepWithNextSibling) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) -} - -// Post executes the subSteps on the parent Stream -func (step StepWithNextSibling) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - - const location = "render.StepWithNextSibling.Post" - - var sibling model.Stream - - factory := renderer.factory() - streamRenderer := renderer.(*Stream) - stream := streamRenderer._stream - - if err := factory.Stream().LoadNextSibling(stream.ParentID, stream.Rank, &sibling); err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error listing parent")) - } - - // Make a renderer with the new parent stream - // TODO: LOW: Is "view" really the best action to use here?? - siblingRenderer, err := NewStreamWithoutTemplate(streamRenderer.factory(), streamRenderer.request(), streamRenderer.response(), &sibling, "view") - - if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error creating renderer for sibling")) - } - - // execute the POST render pipeline on the parent - result := Pipeline(step.SubSteps).Execute(factory, &siblingRenderer, buffer, actionMethod) - result.Error = derp.Wrap(result.Error, location, "Error executing steps for parent") - - return UseResult(result) -} diff --git a/render/step_WithPrevSibling.go b/render/step_WithPrevSibling.go deleted file mode 100644 index 09aab811f..000000000 --- a/render/step_WithPrevSibling.go +++ /dev/null @@ -1,52 +0,0 @@ -package render - -import ( - "io" - - "github.com/EmissarySocial/emissary/model" - "github.com/EmissarySocial/emissary/model/step" - "github.com/benpate/derp" -) - -// StepWithPrevSibling represents an action-step that can update the data.DataMap custom data stored in a Stream -type StepWithPrevSibling struct { - SubSteps []step.Step -} - -func (step StepWithPrevSibling) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodGet) -} - -// Post executes the subSteps on the parent Stream -func (step StepWithPrevSibling) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - return step.execute(renderer, buffer, ActionMethodPost) -} - -// Post executes the subSteps on the parent Stream -func (step StepWithPrevSibling) execute(renderer Renderer, buffer io.Writer, actionMethod ActionMethod) PipelineBehavior { - - const location = "render.StepWithPrevSibling.execute" - - var sibling model.Stream - - factory := renderer.factory() - streamRenderer := renderer.(*Stream) - stream := streamRenderer._stream - - if err := factory.Stream().LoadPrevSibling(stream.ParentID, stream.Rank, &sibling); err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error listing parent")) - } - - // Make a renderer with the new parent stream - // TODO: Is "view" really the best action to use here?? - siblingRenderer, err := NewStreamWithoutTemplate(streamRenderer.factory(), streamRenderer.request(), streamRenderer.response(), &sibling, "view") - - if err != nil { - return Halt().WithError(derp.Wrap(err, location, "Error creating renderer for sibling")) - } - - // Execute the POST render pipeline on the parent - result := Pipeline(step.SubSteps).Execute(factory, &siblingRenderer, buffer, actionMethod) - result.Error = derp.Wrap(result.Error, location, "Error executing steps for parent") - return UseResult(result) -} diff --git a/render/step_do.go b/render/step_do.go deleted file mode 100644 index ffe043232..000000000 --- a/render/step_do.go +++ /dev/null @@ -1,41 +0,0 @@ -package render - -import ( - "io" - - "github.com/benpate/derp" -) - -// StepDo represents an action-step that sends an HTMX 'forward' to a new page. -type StepDo struct { - Action string -} - -func (step StepDo) Get(renderer Renderer, buffer io.Writer) PipelineBehavior { - - const location = "render.StepDo.Get" - - action, ok := renderer.template().Actions[step.Action] - - if !ok { - return Halt().WithError(derp.NewBadRequestError(location, "Action not found", step.Action)) - } - - result := Pipeline(action.Steps).Get(renderer.factory(), renderer, buffer) - return UseResult(result) -} - -// Post updates the stream with approved data from the request body. -func (step StepDo) Post(renderer Renderer, buffer io.Writer) PipelineBehavior { - - const location = "render.StepDo.Post" - - action, ok := renderer.template().Actions[step.Action] - - if !ok { - return Halt().WithError(derp.NewBadRequestError(location, "Action not found", step.Action)) - } - - result := Pipeline(action.Steps).Post(renderer.factory(), renderer, buffer) - return UseResult(result) -} diff --git a/server.go b/server.go index 7c9308bad..d260c03dc 100644 --- a/server.go +++ b/server.go @@ -242,9 +242,9 @@ func makeStandardRoutes(factory *server.Factory, e *echo.Echo) { e.DELETE("/:stream", handler.PostStreamWithAction(factory)) // Hard-coded routes for additional stream services - e.GET("/:stream/attachments/:attachment", handler.GetAttachment(factory)) // TODO: LOW: Can Stream Attachments be moved into a custom render step? - e.GET("/:stream/sse", handler.ServerSentEvent(factory)) // TODO: LOW: Can SSE be moved into a custom render step? - e.GET("/:stream/qrcode", handler.GetQRCode(factory)) // TODO: LOW: Can QR Codes be moved into a custom render step? + e.GET("/:stream/attachments/:attachment", handler.GetAttachment(factory)) // TODO: LOW: Can Stream Attachments be moved into a custom build step? + e.GET("/:stream/sse", handler.ServerSentEvent(factory)) // TODO: LOW: Can SSE be moved into a custom build step? + e.GET("/:stream/qrcode", handler.GetQRCode(factory)) // TODO: LOW: Can QR Codes be moved into a custom build step? // Profile Pages // NOTE: these are rewritten from /@:userId by the rewrite middleware diff --git a/server/factory.go b/server/factory.go index e2b648827..00ea7066e 100644 --- a/server/factory.go +++ b/server/factory.go @@ -7,9 +7,9 @@ import ( "strings" "sync" + "github.com/EmissarySocial/emissary/build" "github.com/EmissarySocial/emissary/config" "github.com/EmissarySocial/emissary/domain" - "github.com/EmissarySocial/emissary/render" "github.com/EmissarySocial/emissary/service" "github.com/EmissarySocial/emissary/tools/ascache" "github.com/EmissarySocial/emissary/tools/ascacherules" @@ -498,7 +498,7 @@ func (factory *Factory) Widget() *service.Widget { // FuncMap returns the global funcMap (used by all templates) func (factory *Factory) FuncMap() template.FuncMap { - return render.FuncMap(factory.Icons()) + return build.FuncMap(factory.Icons()) } // Icons returns the global icon collection diff --git a/service/outbox_publish.go b/service/outbox_publish.go index 3437cc76e..0a4bcdf97 100644 --- a/service/outbox_publish.go +++ b/service/outbox_publish.go @@ -107,7 +107,7 @@ func (service Outbox) sendNotifications_WebSub(parentType string, parentID primi // sendNotifications_WebMention sends WebMention updates to external websites that are // mentioned in this stream. This is here (and not in the outbox service) -// because we need to render the content in order to discover outbound links. +// because we need to build the content in order to discover outbound links. func (service *Outbox) sendNotifications_WebMention(activity mapof.Any) { // Locate the object ID for this acticity