diff --git a/activitypub/controllers/outbox.go b/activitypub/controllers/outbox.go index 5aa75df0f..0dbcb5a03 100644 --- a/activitypub/controllers/outbox.go +++ b/activitypub/controllers/outbox.go @@ -62,7 +62,7 @@ func ActorObjectHandler(w http.ResponseWriter, r *http.Request) { if err != nil { w.WriteHeader(http.StatusNotFound) return - // controllers.WriteSimpleResponse(w, false, err.Error()) + // responses.WriteSimpleResponse(w, false, err.Error()) } if _, err := w.Write([]byte(object)); err != nil { diff --git a/controllers/admin.go b/controllers/admin.go deleted file mode 100644 index 47a400bc0..000000000 --- a/controllers/admin.go +++ /dev/null @@ -1,13 +0,0 @@ -package controllers - -import ( - "net/http" - - "github.com/owncast/owncast/core/rtmp" -) - -// DisconnectInboundConnection will force-disconnect an inbound stream. -func DisconnectInboundConnection(w http.ResponseWriter, r *http.Request) { - rtmp.Disconnect() - w.WriteHeader(http.StatusOK) -} diff --git a/controllers/admin/appearance.go b/controllers/admin/appearance.go deleted file mode 100644 index 4904ec325..000000000 --- a/controllers/admin/appearance.go +++ /dev/null @@ -1,35 +0,0 @@ -package admin - -import ( - "encoding/json" - "net/http" - - "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core/data" -) - -// SetCustomColorVariableValues sets the custom color variables. -func SetCustomColorVariableValues(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - type request struct { - Value map[string]string `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var values request - - if err := decoder.Decode(&values); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update appearance variable values") - return - } - - if err := data.SetCustomColorVariableValues(values.Value); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "custom appearance variables updated") -} diff --git a/controllers/admin/config.go b/controllers/admin/config.go deleted file mode 100644 index 5fd78e7df..000000000 --- a/controllers/admin/config.go +++ /dev/null @@ -1,880 +0,0 @@ -package admin - -import ( - "encoding/json" - "fmt" - "net" - "net/http" - "net/netip" - "os" - "path/filepath" - "reflect" - "strings" - - "github.com/owncast/owncast/activitypub/outbox" - "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core/chat" - "github.com/owncast/owncast/core/data" - "github.com/owncast/owncast/core/user" - "github.com/owncast/owncast/core/webhooks" - "github.com/owncast/owncast/models" - "github.com/owncast/owncast/utils" - log "github.com/sirupsen/logrus" - "github.com/teris-io/shortid" -) - -// ConfigValue is a container object that holds a value, is encoded, and saved to the database. -type ConfigValue struct { - Value interface{} `json:"value"` -} - -// SetTags will handle the web config request to set tags. -func SetTags(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValues, success := getValuesFromRequest(w, r) - if !success { - return - } - - tagStrings := make([]string, 0) - for _, tag := range configValues { - tagStrings = append(tagStrings, strings.TrimLeft(tag.Value.(string), "#")) - } - - if err := data.SetServerMetadataTags(tagStrings); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - // Update Fediverse followers about this change. - if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetStreamTitle will handle the web config request to set the current stream title. -func SetStreamTitle(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - value := configValue.Value.(string) - - if err := data.SetStreamTitle(value); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - if value != "" { - sendSystemChatAction(fmt.Sprintf("Stream title changed to **%s**", value), true) - go webhooks.SendStreamStatusEvent(models.StreamTitleUpdated) - } - controllers.WriteSimpleResponse(w, true, "changed") -} - -// ExternalSetStreamTitle will change the stream title on behalf of an external integration API request. -func ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { - SetStreamTitle(w, r) -} - -func sendSystemChatAction(messageText string, ephemeral bool) { - if err := chat.SendSystemAction(messageText, ephemeral); err != nil { - log.Errorln(err) - } -} - -// SetServerName will handle the web config request to set the server's name. -func SetServerName(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetServerName(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - // Update Fediverse followers about this change. - if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetServerSummary will handle the web config request to set the about/summary text. -func SetServerSummary(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetServerSummary(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - // Update Fediverse followers about this change. - if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetCustomOfflineMessage will set a message to display when the server is offline. -func SetCustomOfflineMessage(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetCustomOfflineMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetServerWelcomeMessage will handle the web config request to set the welcome message text. -func SetServerWelcomeMessage(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetServerWelcomeMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetExtraPageContent will handle the web config request to set the page markdown content. -func SetExtraPageContent(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetExtraPageBodyContent(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetAdminPassword will handle the web config request to set the server admin password. -func SetAdminPassword(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetAdminPassword(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetLogo will handle a new logo image file being uploaded. -func SetLogo(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - value, ok := configValue.Value.(string) - if !ok { - controllers.WriteSimpleResponse(w, false, "unable to find image data") - return - } - bytes, extension, err := utils.DecodeBase64Image(value) - if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - imgPath := filepath.Join("data", "logo"+extension) - if err := os.WriteFile(imgPath, bytes, 0o600); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - if err := data.SetLogoPath("logo" + extension); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - if err := data.SetLogoUniquenessString(shortid.MustGenerate()); err != nil { - log.Error("Error saving logo uniqueness string: ", err) - } - - // Update Fediverse followers about this change. - if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetNSFW will handle the web config request to set the NSFW flag. -func SetNSFW(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetNSFW(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetFfmpegPath will handle the web config request to validate and set an updated copy of ffmpg. -func SetFfmpegPath(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - path := configValue.Value.(string) - if err := utils.VerifyFFMpegPath(path); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - if err := data.SetFfmpegPath(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} - -// SetWebServerPort will handle the web config request to set the server's HTTP port. -func SetWebServerPort(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if port, ok := configValue.Value.(float64); ok { - if (port < 1) || (port > 65535) { - controllers.WriteSimpleResponse(w, false, "Port number must be between 1 and 65535") - return - } - if err := data.SetHTTPPortNumber(port); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "HTTP port set") - return - } - - controllers.WriteSimpleResponse(w, false, "Invalid type or value, port must be a number") -} - -// SetWebServerIP will handle the web config request to set the server's HTTP listen address. -func SetWebServerIP(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if input, ok := configValue.Value.(string); ok { - if ip := net.ParseIP(input); ip != nil { - if err := data.SetHTTPListenAddress(ip.String()); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "HTTP listen address set") - return - } - - controllers.WriteSimpleResponse(w, false, "Invalid IP address") - return - } - controllers.WriteSimpleResponse(w, false, "Invalid type or value, IP address must be a string") -} - -// SetRTMPServerPort will handle the web config request to set the inbound RTMP port. -func SetRTMPServerPort(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetRTMPPortNumber(configValue.Value.(float64)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "rtmp port set") -} - -// SetServerURL will handle the web config request to set the full server URL. -func SetServerURL(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - rawValue, ok := configValue.Value.(string) - if !ok { - controllers.WriteSimpleResponse(w, false, "could not read server url") - return - } - - serverHostString := utils.GetHostnameFromURLString(rawValue) - if serverHostString == "" { - controllers.WriteSimpleResponse(w, false, "server url value invalid") - return - } - - // Block Private IP URLs - ipAddr, ipErr := netip.ParseAddr(utils.GetHostnameWithoutPortFromURLString(rawValue)) - - if ipErr == nil && ipAddr.IsPrivate() { - controllers.WriteSimpleResponse(w, false, "Server URL cannot be private") - return - } - - // Trim any trailing slash - serverURL := strings.TrimRight(rawValue, "/") - - if err := data.SetServerURL(serverURL); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "server url set") -} - -// SetSocketHostOverride will set the host override for the websocket. -func SetSocketHostOverride(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetWebsocketOverrideHost(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "websocket host override set") -} - -// SetDirectoryEnabled will handle the web config request to enable or disable directory registration. -func SetDirectoryEnabled(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetDirectoryEnabled(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - controllers.WriteSimpleResponse(w, true, "directory state changed") -} - -// SetStreamLatencyLevel will handle the web config request to set the stream latency level. -func SetStreamLatencyLevel(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetStreamLatencyLevel(configValue.Value.(float64)); err != nil { - controllers.WriteSimpleResponse(w, false, "error setting stream latency "+err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "set stream latency") -} - -// SetS3Configuration will handle the web config request to set the storage configuration. -func SetS3Configuration(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - type s3ConfigurationRequest struct { - Value models.S3 `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var newS3Config s3ConfigurationRequest - if err := decoder.Decode(&newS3Config); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update s3 config with provided values") - return - } - - if newS3Config.Value.Enabled { - if newS3Config.Value.Endpoint == "" || !utils.IsValidURL((newS3Config.Value.Endpoint)) { - controllers.WriteSimpleResponse(w, false, "s3 support requires an endpoint") - return - } - - if newS3Config.Value.AccessKey == "" || newS3Config.Value.Secret == "" { - controllers.WriteSimpleResponse(w, false, "s3 support requires an access key and secret") - return - } - - if newS3Config.Value.Region == "" { - controllers.WriteSimpleResponse(w, false, "s3 support requires a region and endpoint") - return - } - - if newS3Config.Value.Bucket == "" { - controllers.WriteSimpleResponse(w, false, "s3 support requires a bucket created for storing public video segments") - return - } - } - - if err := data.SetS3Config(newS3Config.Value); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - controllers.WriteSimpleResponse(w, true, "storage configuration changed") -} - -// SetStreamOutputVariants will handle the web config request to set the video output stream variants. -func SetStreamOutputVariants(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - type streamOutputVariantRequest struct { - Value []models.StreamOutputVariant `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var videoVariants streamOutputVariantRequest - if err := decoder.Decode(&videoVariants); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) - return - } - - if err := data.SetStreamOutputVariants(videoVariants.Value); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "stream output variants updated") -} - -// SetSocialHandles will handle the web config request to set the external social profile links. -func SetSocialHandles(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - type socialHandlesRequest struct { - Value []models.SocialHandle `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var socialHandles socialHandlesRequest - if err := decoder.Decode(&socialHandles); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update social handles with provided values") - return - } - - if err := data.SetSocialHandles(socialHandles.Value); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update social handles with provided values") - return - } - - // Update Fediverse followers about this change. - if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "social handles updated") -} - -// SetChatDisabled will disable chat functionality. -func SetChatDisabled(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update chat disabled") - return - } - - if err := data.SetChatDisabled(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "chat disabled status updated") -} - -// SetVideoCodec will change the codec used for video encoding. -func SetVideoCodec(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to change video codec") - return - } - - if err := data.SetVideoCodec(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update codec") - return - } - - controllers.WriteSimpleResponse(w, true, "video codec updated") -} - -// SetExternalActions will set the 3rd party actions for the web interface. -func SetExternalActions(w http.ResponseWriter, r *http.Request) { - type externalActionsRequest struct { - Value []models.ExternalAction `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var actions externalActionsRequest - if err := decoder.Decode(&actions); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update external actions with provided values") - return - } - - if err := data.SetExternalActions(actions.Value); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update external actions with provided values") - return - } - - controllers.WriteSimpleResponse(w, true, "external actions update") -} - -// SetCustomStyles will set the CSS string we insert into the page. -func SetCustomStyles(w http.ResponseWriter, r *http.Request) { - customStyles, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update custom styles") - return - } - - if err := data.SetCustomStyles(customStyles.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "custom styles updated") -} - -// SetCustomJavascript will set the Javascript string we insert into the page. -func SetCustomJavascript(w http.ResponseWriter, r *http.Request) { - customJavascript, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update custom javascript") - return - } - - if err := data.SetCustomJavascript(customJavascript.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "custom styles updated") -} - -// SetForbiddenUsernameList will set the list of usernames we do not allow to use. -func SetForbiddenUsernameList(w http.ResponseWriter, r *http.Request) { - type forbiddenUsernameListRequest struct { - Value []string `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var request forbiddenUsernameListRequest - if err := decoder.Decode(&request); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update forbidden usernames with provided values") - return - } - - if err := data.SetForbiddenUsernameList(request.Value); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "forbidden username list updated") -} - -// SetSuggestedUsernameList will set the list of suggested usernames that newly registered users are assigned if it isn't inferred otherwise (i.e. through a proxy). -func SetSuggestedUsernameList(w http.ResponseWriter, r *http.Request) { - type suggestedUsernameListRequest struct { - Value []string `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var request suggestedUsernameListRequest - - if err := decoder.Decode(&request); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update suggested usernames with provided values") - return - } - - if err := data.SetSuggestedUsernamesList(request.Value); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "suggested username list updated") -} - -// SetChatJoinMessagesEnabled will enable or disable the chat join messages. -func SetChatJoinMessagesEnabled(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update chat join messages enabled") - return - } - - if err := data.SetChatJoinMessagesEnabled(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "chat join message status updated") -} - -// SetHideViewerCount will enable or disable hiding the viewer count. -func SetHideViewerCount(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update hiding viewer count") - return - } - - if err := data.SetHideViewerCount(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "hide viewer count setting updated") -} - -// SetDisableSearchIndexing will set search indexing support. -func SetDisableSearchIndexing(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update search indexing") - return - } - - if err := data.SetDisableSearchIndexing(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "search indexing support updated") -} - -// SetVideoServingEndpoint will save the video serving endpoint. -func SetVideoServingEndpoint(w http.ResponseWriter, r *http.Request) { - endpoint, success := getValueFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") - return - } - - value, ok := endpoint.Value.(string) - if !ok { - controllers.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") - return - } - - if err := data.SetVideoServingEndpoint(value); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "custom video serving endpoint updated") -} - -func requirePOST(w http.ResponseWriter, r *http.Request) bool { - if r.Method != controllers.POST { - controllers.WriteSimpleResponse(w, false, r.Method+" not supported") - return false - } - - return true -} - -func getValueFromRequest(w http.ResponseWriter, r *http.Request) (ConfigValue, bool) { - decoder := json.NewDecoder(r.Body) - var configValue ConfigValue - if err := decoder.Decode(&configValue); err != nil { - log.Warnln(err) - controllers.WriteSimpleResponse(w, false, "unable to parse new value") - return configValue, false - } - - return configValue, true -} - -func getValuesFromRequest(w http.ResponseWriter, r *http.Request) ([]ConfigValue, bool) { - var values []ConfigValue - - decoder := json.NewDecoder(r.Body) - var configValue ConfigValue - if err := decoder.Decode(&configValue); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to parse array of values") - return values, false - } - - object := reflect.ValueOf(configValue.Value) - - for i := 0; i < object.Len(); i++ { - values = append(values, ConfigValue{Value: object.Index(i).Interface()}) - } - - return values, true -} - -// SetStreamKeys will set the valid stream keys. -func SetStreamKeys(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - type streamKeysRequest struct { - Value []models.StreamKey `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var streamKeys streamKeysRequest - if err := decoder.Decode(&streamKeys); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update stream keys with provided values") - return - } - - if len(streamKeys.Value) == 0 { - controllers.WriteSimpleResponse(w, false, "must provide at least one valid stream key") - return - } - - for _, streamKey := range streamKeys.Value { - if streamKey.Key == "" { - controllers.WriteSimpleResponse(w, false, "stream key cannot be empty") - return - } - } - - if err := data.SetStreamKeys(streamKeys.Value); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "changed") -} diff --git a/controllers/admin/disconnect.go b/controllers/admin/disconnect.go deleted file mode 100644 index d8bf93346..000000000 --- a/controllers/admin/disconnect.go +++ /dev/null @@ -1,21 +0,0 @@ -package admin - -import ( - "net/http" - - "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core" - - "github.com/owncast/owncast/core/rtmp" -) - -// DisconnectInboundConnection will force-disconnect an inbound stream. -func DisconnectInboundConnection(w http.ResponseWriter, r *http.Request) { - if !core.GetStatus().Online { - controllers.WriteSimpleResponse(w, false, "no inbound stream connected") - return - } - - rtmp.Disconnect() - controllers.WriteSimpleResponse(w, true, "inbound stream disconnected") -} diff --git a/controllers/admin/federation.go b/controllers/admin/federation.go deleted file mode 100644 index 46844c6e6..000000000 --- a/controllers/admin/federation.go +++ /dev/null @@ -1,179 +0,0 @@ -package admin - -import ( - "net/http" - - "github.com/owncast/owncast/activitypub" - "github.com/owncast/owncast/activitypub/outbox" - "github.com/owncast/owncast/activitypub/persistence" - "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core/data" -) - -// SendFederatedMessage will send a manual message to the fediverse. -func SendFederatedMessage(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - message, ok := configValue.Value.(string) - if !ok { - controllers.WriteSimpleResponse(w, false, "unable to send message") - return - } - - if err := activitypub.SendPublicFederatedMessage(message); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "sent") -} - -// SetFederationEnabled will set if Federation features are enabled. -func SetFederationEnabled(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetFederationEnabled(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - controllers.WriteSimpleResponse(w, true, "federation features saved") -} - -// SetFederationActivityPrivate will set if Federation features are private to followers. -func SetFederationActivityPrivate(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetFederationIsPrivate(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - // Update Fediverse followers about this change. - if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "federation private saved") -} - -// SetFederationShowEngagement will set if Fedivese engagement shows in chat. -func SetFederationShowEngagement(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetFederationShowEngagement(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - controllers.WriteSimpleResponse(w, true, "federation show engagement saved") -} - -// SetFederationUsername will set the local actor username used for federation activities. -func SetFederationUsername(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetFederationUsername(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "username saved") -} - -// SetFederationGoLiveMessage will set the federated message sent when the streamer goes live. -func SetFederationGoLiveMessage(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValue, success := getValueFromRequest(w, r) - if !success { - return - } - - if err := data.SetFederationGoLiveMessage(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "message saved") -} - -// SetFederationBlockDomains saves a list of domains to block on the Fediverse. -func SetFederationBlockDomains(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - configValues, success := getValuesFromRequest(w, r) - if !success { - controllers.WriteSimpleResponse(w, false, "unable to handle provided domains") - return - } - - domainStrings := make([]string, 0) - for _, domain := range configValues { - domainStrings = append(domainStrings, domain.Value.(string)) - } - - if err := data.SetBlockedFederatedDomains(domainStrings); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - controllers.WriteSimpleResponse(w, true, "saved") -} - -// GetFederatedActions will return the saved list of accepted inbound -// federated activities. -func GetFederatedActions(page int, pageSize int, w http.ResponseWriter, r *http.Request) { - offset := pageSize * page - - activities, total, err := persistence.GetInboundActivities(pageSize, offset) - if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) - return - } - - response := controllers.PaginatedResponse{ - Total: total, - Results: activities, - } - - controllers.WriteResponse(w, response) -} diff --git a/controllers/admin/notifications.go b/controllers/admin/notifications.go deleted file mode 100644 index 82bdb5f8c..000000000 --- a/controllers/admin/notifications.go +++ /dev/null @@ -1,60 +0,0 @@ -package admin - -import ( - "encoding/json" - "net/http" - - "github.com/owncast/owncast/controllers" - "github.com/owncast/owncast/core/data" - "github.com/owncast/owncast/models" -) - -// SetDiscordNotificationConfiguration will set the discord notification configuration. -func SetDiscordNotificationConfiguration(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - type request struct { - Value models.DiscordConfiguration `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var config request - if err := decoder.Decode(&config); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update discord config with provided values") - return - } - - if err := data.SetDiscordConfig(config.Value); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update discord config with provided values") - return - } - - controllers.WriteSimpleResponse(w, true, "updated discord config with provided values") -} - -// SetBrowserNotificationConfiguration will set the browser notification configuration. -func SetBrowserNotificationConfiguration(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { - return - } - - type request struct { - Value models.BrowserNotificationConfiguration `json:"value"` - } - - decoder := json.NewDecoder(r.Body) - var config request - if err := decoder.Decode(&config); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") - return - } - - if err := data.SetBrowserPushConfig(config.Value); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") - return - } - - controllers.WriteSimpleResponse(w, true, "updated browser push config with provided values") -} diff --git a/controllers/constants.go b/controllers/constants.go deleted file mode 100644 index 29b08442a..000000000 --- a/controllers/constants.go +++ /dev/null @@ -1,7 +0,0 @@ -package controllers - -// POST is the HTTP POST method. -const POST = "POST" - -// GET is the HTTP GET method. -const GET = "GET" diff --git a/controllers/hls.go b/controllers/hls.go deleted file mode 100644 index ba329db2c..000000000 --- a/controllers/hls.go +++ /dev/null @@ -1,55 +0,0 @@ -package controllers - -import ( - "net/http" - "path" - "path/filepath" - "strconv" - "strings" - - "github.com/owncast/owncast/config" - "github.com/owncast/owncast/core" - "github.com/owncast/owncast/core/data" - "github.com/owncast/owncast/models" - "github.com/owncast/owncast/utils" - "github.com/owncast/owncast/webserver/middleware" -) - -// HandleHLSRequest will manage all requests to HLS content. -func HandleHLSRequest(w http.ResponseWriter, r *http.Request) { - // Sanity check to limit requests to HLS file types. - if filepath.Ext(r.URL.Path) != ".m3u8" && filepath.Ext(r.URL.Path) != ".ts" { - w.WriteHeader(http.StatusNotFound) - return - } - - requestedPath := r.URL.Path - relativePath := strings.Replace(requestedPath, "/hls/", "", 1) - fullPath := filepath.Join(config.HLSStoragePath, relativePath) - - // If using external storage then only allow requests for the - // master playlist at stream.m3u8, no variants or segments. - if data.GetS3Config().Enabled && relativePath != "stream.m3u8" { - w.WriteHeader(http.StatusNotFound) - return - } - - // Handle playlists - if path.Ext(r.URL.Path) == ".m3u8" { - // Playlists should never be cached. - middleware.DisableCache(w) - - // Force the correct content type - w.Header().Set("Content-Type", "application/x-mpegURL") - - // Use this as an opportunity to mark this viewer as active. - viewer := models.GenerateViewerFromRequest(r) - core.SetViewerActive(&viewer) - } else { - cacheTime := utils.GetCacheDurationSecondsForPath(relativePath) - w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheTime)) - } - - middleware.EnableCors(w) - http.ServeFile(w, r, fullPath) -} diff --git a/webserver/handlers/adminApi.go b/webserver/handlers/adminApi.go deleted file mode 100644 index 5ac8282f4..000000000 --- a/webserver/handlers/adminApi.go +++ /dev/null @@ -1 +0,0 @@ -package handlers diff --git a/webserver/handlers/adminApiAppearanceConfig.go b/webserver/handlers/adminApiAppearanceConfig.go new file mode 100644 index 000000000..25f48ef09 --- /dev/null +++ b/webserver/handlers/adminApiAppearanceConfig.go @@ -0,0 +1,36 @@ +package handlers + +import ( + "encoding/json" + "net/http" + + "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/webserver/requests" + "github.com/owncast/owncast/webserver/responses" +) + +// SetCustomColorVariableValues sets the custom color variables. +func (h *Handlers) SetCustomColorVariableValues(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + type request struct { + Value map[string]string `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var values request + + if err := decoder.Decode(&values); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update appearance variable values") + return + } + + if err := data.SetCustomColorVariableValues(values.Value); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "custom appearance variables updated") +} diff --git a/controllers/admin/chat.go b/webserver/handlers/adminApiChatHandlers.go similarity index 59% rename from controllers/admin/chat.go rename to webserver/handlers/adminApiChatHandlers.go index 6c68b6dd4..08f452bda 100644 --- a/controllers/admin/chat.go +++ b/webserver/handlers/adminApiChatHandlers.go @@ -1,4 +1,4 @@ -package admin +package handlers // this is endpoint logic @@ -9,30 +9,31 @@ import ( "net/http" "strconv" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/chat/events" "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/utils" + "github.com/owncast/owncast/webserver/requests" + "github.com/owncast/owncast/webserver/responses" + log "github.com/sirupsen/logrus" ) // ExternalUpdateMessageVisibility updates an array of message IDs to have the same visiblity. -func ExternalUpdateMessageVisibility(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { - UpdateMessageVisibility(w, r) +func (h *Handlers) ExternalUpdateMessageVisibility(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + h.UpdateMessageVisibility(w, r) } // UpdateMessageVisibility updates an array of message IDs to have the same visiblity. -func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) { type messageVisibilityUpdateRequest struct { IDArray []string `json:"idArray"` Visible bool `json:"visible"` } - if r.Method != controllers.POST { - // nolint:goconst - controllers.WriteSimpleResponse(w, false, r.Method+" not supported") + if r.Method != http.MethodPost { + responses.WriteSimpleResponse(w, false, r.Method+" not supported") return } @@ -41,78 +42,78 @@ func UpdateMessageVisibility(w http.ResponseWriter, r *http.Request) { if err := decoder.Decode(&request); err != nil { log.Errorln(err) - controllers.WriteSimpleResponse(w, false, "") + responses.WriteSimpleResponse(w, false, "") return } if err := chat.SetMessagesVisibility(request.IDArray, request.Visible); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } - controllers.WriteSimpleResponse(w, true, "changed") + responses.WriteSimpleResponse(w, true, "changed") } // BanIPAddress will manually ban an IP address. -func BanIPAddress(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { +func (h *Handlers) BanIPAddress(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { return } - configValue, success := getValueFromRequest(w, r) + configValue, success := requests.GetValueFromRequest(w, r) if !success { - controllers.WriteSimpleResponse(w, false, "unable to ban IP address") + responses.WriteSimpleResponse(w, false, "unable to ban IP address") return } if err := data.BanIPAddress(configValue.Value.(string), "manually added"); err != nil { - controllers.WriteSimpleResponse(w, false, "error saving IP address ban") + responses.WriteSimpleResponse(w, false, "error saving IP address ban") return } - controllers.WriteSimpleResponse(w, true, "IP address banned") + responses.WriteSimpleResponse(w, true, "IP address banned") } // UnBanIPAddress will remove an IP address ban. -func UnBanIPAddress(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { +func (h *Handlers) UnBanIPAddress(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { return } - configValue, success := getValueFromRequest(w, r) + configValue, success := requests.GetValueFromRequest(w, r) if !success { - controllers.WriteSimpleResponse(w, false, "unable to unban IP address") + responses.WriteSimpleResponse(w, false, "unable to unban IP address") return } if err := data.RemoveIPAddressBan(configValue.Value.(string)); err != nil { - controllers.WriteSimpleResponse(w, false, "error removing IP address ban") + responses.WriteSimpleResponse(w, false, "error removing IP address ban") return } - controllers.WriteSimpleResponse(w, true, "IP address unbanned") + responses.WriteSimpleResponse(w, true, "IP address unbanned") } // GetIPAddressBans will return all the banned IP addresses. -func GetIPAddressBans(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetIPAddressBans(w http.ResponseWriter, r *http.Request) { bans, err := data.GetIPAddressBans() if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } - controllers.WriteResponse(w, bans) + responses.WriteResponse(w, bans) } // UpdateUserEnabled enable or disable a single user by ID. -func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) UpdateUserEnabled(w http.ResponseWriter, r *http.Request) { type blockUserRequest struct { UserID string `json:"userId"` Enabled bool `json:"enabled"` } - if r.Method != controllers.POST { - controllers.WriteSimpleResponse(w, false, r.Method+" not supported") + if r.Method != http.MethodPost { + responses.WriteSimpleResponse(w, false, r.Method+" not supported") return } @@ -121,19 +122,19 @@ func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) { if err := decoder.Decode(&request); err != nil { log.Errorln(err) - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } if request.UserID == "" { - controllers.WriteSimpleResponse(w, false, "must provide userId") + responses.WriteSimpleResponse(w, false, "must provide userId") return } // Disable/enable the user if err := user.SetEnabled(request.UserID, request.Enabled); err != nil { log.Errorln("error changing user enabled status", err) - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -142,7 +143,7 @@ func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) { if !request.Enabled { if err := chat.SetMessageVisibilityForUserID(request.UserID, request.Enabled); err != nil { log.Errorln("error changing user messages visibility", err) - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } } @@ -157,7 +158,7 @@ func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) { if err != nil { log.Errorln("error fetching clients for user: ", err) - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -180,26 +181,26 @@ func UpdateUserEnabled(w http.ResponseWriter, r *http.Request) { } } - controllers.WriteSimpleResponse(w, true, fmt.Sprintf("%s enabled: %t", request.UserID, request.Enabled)) + responses.WriteSimpleResponse(w, true, fmt.Sprintf("%s enabled: %t", request.UserID, request.Enabled)) } // GetDisabledUsers will return all the disabled users. -func GetDisabledUsers(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetDisabledUsers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") users := user.GetDisabledUsers() - controllers.WriteResponse(w, users) + responses.WriteResponse(w, users) } // UpdateUserModerator will set the moderator status for a user ID. -func UpdateUserModerator(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) UpdateUserModerator(w http.ResponseWriter, r *http.Request) { type request struct { UserID string `json:"userId"` IsModerator bool `json:"isModerator"` } - if r.Method != controllers.POST { - controllers.WriteSimpleResponse(w, false, r.Method+" not supported") + if r.Method != http.MethodPost { + responses.WriteSimpleResponse(w, false, r.Method+" not supported") return } @@ -207,13 +208,13 @@ func UpdateUserModerator(w http.ResponseWriter, r *http.Request) { var req request if err := decoder.Decode(&req); err != nil { - controllers.WriteSimpleResponse(w, false, "") + responses.WriteSimpleResponse(w, false, "") return } // Update the user object with new moderation access. if err := user.SetModerator(req.UserID, req.IsModerator); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -222,87 +223,87 @@ func UpdateUserModerator(w http.ResponseWriter, r *http.Request) { log.Debugln(err) } - controllers.WriteSimpleResponse(w, true, fmt.Sprintf("%s is moderator: %t", req.UserID, req.IsModerator)) + responses.WriteSimpleResponse(w, true, fmt.Sprintf("%s is moderator: %t", req.UserID, req.IsModerator)) } // GetModerators will return a list of moderator users. -func GetModerators(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetModerators(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") users := user.GetModeratorUsers() - controllers.WriteResponse(w, users) + responses.WriteResponse(w, users) } // GetChatMessages returns all of the chat messages, unfiltered. -func GetChatMessages(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetAdminChatMessages(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") messages := chat.GetChatModerationHistory() - controllers.WriteResponse(w, messages) + responses.WriteResponse(w, messages) } // SendSystemMessage will send an official "SYSTEM" message to chat on behalf of your server. -func SendSystemMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { +func (h *Handlers) SendSystemMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var message events.SystemMessageEvent if err := json.NewDecoder(r.Body).Decode(&message); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } if err := chat.SendSystemMessage(message.Body, false); err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) } - controllers.WriteSimpleResponse(w, true, "sent") + responses.WriteSimpleResponse(w, true, "sent") } // SendSystemMessageToConnectedClient will handle incoming requests to send a single message to a single connected client by ID. -func SendSystemMessageToConnectedClient(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { +func (h *Handlers) SendSystemMessageToConnectedClient(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") clientIDText, err := utils.ReadRestURLParameter(r, "clientId") if err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } clientIDNumeric, err := strconv.ParseUint(clientIDText, 10, 32) if err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } var message events.SystemMessageEvent if err := json.NewDecoder(r.Body).Decode(&message); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } chat.SendSystemMessageToClient(uint(clientIDNumeric), message.Body) - controllers.WriteSimpleResponse(w, true, "sent") + responses.WriteSimpleResponse(w, true, "sent") } // SendUserMessage will send a message to chat on behalf of a user. *Depreciated*. -func SendUserMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { +func (h *Handlers) SendUserMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - controllers.BadRequestHandler(w, errors.New("no longer supported. see /api/integrations/chat/send")) + responses.BadRequestHandler(w, errors.New("no longer supported. see /api/integrations/chat/send")) } // SendIntegrationChatMessage will send a chat message on behalf of an external chat integration. -func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { +func (h *Handlers) SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") name := integration.DisplayName if name == "" { - controllers.BadRequestHandler(w, errors.New("unknown integration for provided access token")) + responses.BadRequestHandler(w, errors.New("unknown integration for provided access token")) return } var event events.UserMessageEvent if err := json.NewDecoder(r.Body).Decode(&event); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } event.SetDefaults() @@ -310,7 +311,7 @@ func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.Respons event.Type = "CHAT" if event.Empty() { - controllers.BadRequestHandler(w, errors.New("invalid message")) + responses.BadRequestHandler(w, errors.New("invalid message")) return } @@ -323,22 +324,22 @@ func SendIntegrationChatMessage(integration user.ExternalAPIUser, w http.Respons } if err := chat.Broadcast(&event); err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } chat.SaveUserMessage(event) - controllers.WriteSimpleResponse(w, true, "sent") + responses.WriteSimpleResponse(w, true, "sent") } // SendChatAction will send a generic chat action. -func SendChatAction(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { +func (h *Handlers) SendChatAction(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") var message events.SystemActionEvent if err := json.NewDecoder(r.Body).Decode(&message); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } @@ -346,30 +347,30 @@ func SendChatAction(integration user.ExternalAPIUser, w http.ResponseWriter, r * message.RenderBody() if err := chat.SendSystemAction(message.Body, false); err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } - controllers.WriteSimpleResponse(w, true, "sent") + responses.WriteSimpleResponse(w, true, "sent") } // SetEnableEstablishedChatUserMode sets the requirement for a chat user // to be "established" for some time before taking part in chat. -func SetEnableEstablishedChatUserMode(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { +func (h *Handlers) SetEnableEstablishedChatUserMode(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { return } - configValue, success := getValueFromRequest(w, r) + configValue, success := requests.GetValueFromRequest(w, r) if !success { - controllers.WriteSimpleResponse(w, false, "unable to update chat established user only mode") + responses.WriteSimpleResponse(w, false, "unable to update chat established user only mode") return } if err := data.SetChatEstablishedUsersOnlyMode(configValue.Value.(bool)); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } - controllers.WriteSimpleResponse(w, true, "chat established users only mode updated") + responses.WriteSimpleResponse(w, true, "chat established users only mode updated") } diff --git a/webserver/handlers/adminApiConfigHandlers.go b/webserver/handlers/adminApiConfigHandlers.go new file mode 100644 index 000000000..d58f0492d --- /dev/null +++ b/webserver/handlers/adminApiConfigHandlers.go @@ -0,0 +1,814 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/owncast/owncast/activitypub/outbox" + "github.com/owncast/owncast/core/chat" + "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/core/webhooks" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/utils" + "github.com/owncast/owncast/webserver/requests" + "github.com/owncast/owncast/webserver/responses" + log "github.com/sirupsen/logrus" + "github.com/teris-io/shortid" +) + +// SetTags will handle the web config request to set tags. +func (h *Handlers) SetTags(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValues, success := requests.GetValuesFromRequest(w, r) + if !success { + return + } + + tagStrings := make([]string, 0) + for _, tag := range configValues { + tagStrings = append(tagStrings, strings.TrimLeft(tag.Value.(string), "#")) + } + + if err := data.SetServerMetadataTags(tagStrings); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + // Update Fediverse followers about this change. + if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetStreamTitle will handle the web config request to set the current stream title. +func (h *Handlers) SetStreamTitle(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + value := configValue.Value.(string) + + if err := data.SetStreamTitle(value); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + if value != "" { + sendSystemChatAction(fmt.Sprintf("Stream title changed to **%s**", value), true) + go webhooks.SendStreamStatusEvent(models.StreamTitleUpdated) + } + responses.WriteSimpleResponse(w, true, "changed") +} + +// ExternalSetStreamTitle will change the stream title on behalf of an external integration API request. +func (h *Handlers) ExternalSetStreamTitle(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + h.SetStreamTitle(w, r) +} + +func sendSystemChatAction(messageText string, ephemeral bool) { + if err := chat.SendSystemAction(messageText, ephemeral); err != nil { + log.Errorln(err) + } +} + +// SetServerName will handle the web config request to set the server's name. +func (h *Handlers) SetServerName(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetServerName(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + // Update Fediverse followers about this change. + if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetServerSummary will handle the web config request to set the about/summary text. +func (h *Handlers) SetServerSummary(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetServerSummary(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + // Update Fediverse followers about this change. + if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetCustomOfflineMessage will set a message to display when the server is offline. +func (h *Handlers) SetCustomOfflineMessage(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetCustomOfflineMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetServerWelcomeMessage will handle the web config request to set the welcome message text. +func (h *Handlers) SetServerWelcomeMessage(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetServerWelcomeMessage(strings.TrimSpace(configValue.Value.(string))); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetExtraPageContent will handle the web config request to set the page markdown content. +func (h *Handlers) SetExtraPageContent(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetExtraPageBodyContent(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetAdminPassword will handle the web config request to set the server admin password. +func (h *Handlers) SetAdminPassword(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetAdminPassword(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetLogo will handle a new logo image file being uploaded. +func (h *Handlers) SetLogo(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + value, ok := configValue.Value.(string) + if !ok { + responses.WriteSimpleResponse(w, false, "unable to find image data") + return + } + bytes, extension, err := utils.DecodeBase64Image(value) + if err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + imgPath := filepath.Join("data", "logo"+extension) + if err := os.WriteFile(imgPath, bytes, 0o600); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + if err := data.SetLogoPath("logo" + extension); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + if err := data.SetLogoUniquenessString(shortid.MustGenerate()); err != nil { + log.Error("Error saving logo uniqueness string: ", err) + } + + // Update Fediverse followers about this change. + if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetNSFW will handle the web config request to set the NSFW flag. +func (h *Handlers) SetNSFW(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetNSFW(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetFfmpegPath will handle the web config request to validate and set an updated copy of ffmpg. +func (h *Handlers) SetFfmpegPath(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + path := configValue.Value.(string) + if err := utils.VerifyFFMpegPath(path); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + if err := data.SetFfmpegPath(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} + +// SetWebServerPort will handle the web config request to set the server's HTTP port. +func (h *Handlers) SetWebServerPort(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if port, ok := configValue.Value.(float64); ok { + if (port < 1) || (port > 65535) { + responses.WriteSimpleResponse(w, false, "Port number must be between 1 and 65535") + return + } + if err := data.SetHTTPPortNumber(port); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "HTTP port set") + return + } + + responses.WriteSimpleResponse(w, false, "Invalid type or value, port must be a number") +} + +// SetWebServerIP will handle the web config request to set the server's HTTP listen address. +func (h *Handlers) SetWebServerIP(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if input, ok := configValue.Value.(string); ok { + if ip := net.ParseIP(input); ip != nil { + if err := data.SetHTTPListenAddress(ip.String()); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "HTTP listen address set") + return + } + + responses.WriteSimpleResponse(w, false, "Invalid IP address") + return + } + responses.WriteSimpleResponse(w, false, "Invalid type or value, IP address must be a string") +} + +// SetRTMPServerPort will handle the web config request to set the inbound RTMP port. +func (h *Handlers) SetRTMPServerPort(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetRTMPPortNumber(configValue.Value.(float64)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "rtmp port set") +} + +// SetServerURL will handle the web config request to set the full server URL. +func (h *Handlers) SetServerURL(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + rawValue, ok := configValue.Value.(string) + if !ok { + responses.WriteSimpleResponse(w, false, "could not read server url") + return + } + + serverHostString := utils.GetHostnameFromURLString(rawValue) + if serverHostString == "" { + responses.WriteSimpleResponse(w, false, "server url value invalid") + return + } + + // Trim any trailing slash + serverURL := strings.TrimRight(rawValue, "/") + + if err := data.SetServerURL(serverURL); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "server url set") +} + +// SetSocketHostOverride will set the host override for the websocket. +func (h *Handlers) SetSocketHostOverride(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetWebsocketOverrideHost(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "websocket host override set") +} + +// SetDirectoryEnabled will handle the web config request to enable or disable directory registration. +func (h *Handlers) SetDirectoryEnabled(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetDirectoryEnabled(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + responses.WriteSimpleResponse(w, true, "directory state changed") +} + +// SetStreamLatencyLevel will handle the web config request to set the stream latency level. +func (h *Handlers) SetStreamLatencyLevel(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetStreamLatencyLevel(configValue.Value.(float64)); err != nil { + responses.WriteSimpleResponse(w, false, "error setting stream latency "+err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "set stream latency") +} + +// SetS3Configuration will handle the web config request to set the storage configuration. +func (h *Handlers) SetS3Configuration(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + type s3ConfigurationRequest struct { + Value models.S3 `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var newS3Config s3ConfigurationRequest + if err := decoder.Decode(&newS3Config); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update s3 config with provided values") + return + } + + if newS3Config.Value.Enabled { + if newS3Config.Value.Endpoint == "" || !utils.IsValidURL((newS3Config.Value.Endpoint)) { + responses.WriteSimpleResponse(w, false, "s3 support requires an endpoint") + return + } + + if newS3Config.Value.AccessKey == "" || newS3Config.Value.Secret == "" { + responses.WriteSimpleResponse(w, false, "s3 support requires an access key and secret") + return + } + + if newS3Config.Value.Region == "" { + responses.WriteSimpleResponse(w, false, "s3 support requires a region and endpoint") + return + } + + if newS3Config.Value.Bucket == "" { + responses.WriteSimpleResponse(w, false, "s3 support requires a bucket created for storing public video segments") + return + } + } + + if err := data.SetS3Config(newS3Config.Value); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + responses.WriteSimpleResponse(w, true, "storage configuration changed") +} + +// SetStreamOutputVariants will handle the web config request to set the video output stream variants. +func (h *Handlers) SetStreamOutputVariants(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + type streamOutputVariantRequest struct { + Value []models.StreamOutputVariant `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var videoVariants streamOutputVariantRequest + if err := decoder.Decode(&videoVariants); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) + return + } + + if err := data.SetStreamOutputVariants(videoVariants.Value); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update video config with provided values "+err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "stream output variants updated") +} + +// SetSocialHandles will handle the web config request to set the external social profile links. +func (h *Handlers) SetSocialHandles(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + type socialHandlesRequest struct { + Value []models.SocialHandle `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var socialHandles socialHandlesRequest + if err := decoder.Decode(&socialHandles); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update social handles with provided values") + return + } + + if err := data.SetSocialHandles(socialHandles.Value); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update social handles with provided values") + return + } + + // Update Fediverse followers about this change. + if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "social handles updated") +} + +// SetChatDisabled will disable chat functionality. +func (h *Handlers) SetChatDisabled(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to update chat disabled") + return + } + + if err := data.SetChatDisabled(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "chat disabled status updated") +} + +// SetVideoCodec will change the codec used for video encoding. +func (h *Handlers) SetVideoCodec(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to change video codec") + return + } + + if err := data.SetVideoCodec(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update codec") + return + } + + responses.WriteSimpleResponse(w, true, "video codec updated") +} + +// SetExternalActions will set the 3rd party actions for the web interface. +func (h *Handlers) SetExternalActions(w http.ResponseWriter, r *http.Request) { + type externalActionsRequest struct { + Value []models.ExternalAction `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var actions externalActionsRequest + if err := decoder.Decode(&actions); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update external actions with provided values") + return + } + + if err := data.SetExternalActions(actions.Value); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update external actions with provided values") + return + } + + responses.WriteSimpleResponse(w, true, "external actions update") +} + +// SetCustomStyles will set the CSS string we insert into the page. +func (h *Handlers) SetCustomStyles(w http.ResponseWriter, r *http.Request) { + customStyles, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to update custom styles") + return + } + + if err := data.SetCustomStyles(customStyles.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "custom styles updated") +} + +// SetCustomJavascript will set the Javascript string we insert into the page. +func (h *Handlers) SetCustomJavascript(w http.ResponseWriter, r *http.Request) { + customJavascript, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to update custom javascript") + return + } + + if err := data.SetCustomJavascript(customJavascript.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "custom styles updated") +} + +// SetForbiddenUsernameList will set the list of usernames we do not allow to use. +func (h *Handlers) SetForbiddenUsernameList(w http.ResponseWriter, r *http.Request) { + type forbiddenUsernameListRequest struct { + Value []string `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var request forbiddenUsernameListRequest + if err := decoder.Decode(&request); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update forbidden usernames with provided values") + return + } + + if err := data.SetForbiddenUsernameList(request.Value); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "forbidden username list updated") +} + +// SetSuggestedUsernameList will set the list of suggested usernames that newly registered users are assigned if it isn't inferred otherwise (i.e. through a proxy). +func (h *Handlers) SetSuggestedUsernameList(w http.ResponseWriter, r *http.Request) { + type suggestedUsernameListRequest struct { + Value []string `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var request suggestedUsernameListRequest + + if err := decoder.Decode(&request); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update suggested usernames with provided values") + return + } + + if err := data.SetSuggestedUsernamesList(request.Value); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "suggested username list updated") +} + +// SetChatJoinMessagesEnabled will enable or disable the chat join messages. +func (h *Handlers) SetChatJoinMessagesEnabled(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to update chat join messages enabled") + return + } + + if err := data.SetChatJoinMessagesEnabled(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "chat join message status updated") +} + +// SetHideViewerCount will enable or disable hiding the viewer count. +func (h *Handlers) SetHideViewerCount(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to update hiding viewer count") + return + } + + if err := data.SetHideViewerCount(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "hide viewer count setting updated") +} + +// SetDisableSearchIndexing will set search indexing support. +func (h *Handlers) SetDisableSearchIndexing(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to update search indexing") + return + } + + if err := data.SetDisableSearchIndexing(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "search indexing support updated") +} + +// SetVideoServingEndpoint will save the video serving endpoint. +func (h *Handlers) SetVideoServingEndpoint(w http.ResponseWriter, r *http.Request) { + endpoint, success := requests.GetValueFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") + return + } + + value, ok := endpoint.Value.(string) + if !ok { + responses.WriteSimpleResponse(w, false, "unable to update custom video serving endpoint") + return + } + + if err := data.SetVideoServingEndpoint(value); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "custom video serving endpoint updated") +} + +// SetStreamKeys will set the valid stream keys. +func (h *Handlers) SetStreamKeys(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + type streamKeysRequest struct { + Value []models.StreamKey `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var streamKeys streamKeysRequest + if err := decoder.Decode(&streamKeys); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update stream keys with provided values") + return + } + + if err := data.SetStreamKeys(streamKeys.Value); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "changed") +} diff --git a/controllers/admin/connectedClients.go b/webserver/handlers/adminApiConnectedClients.go similarity index 53% rename from controllers/admin/connectedClients.go rename to webserver/handlers/adminApiConnectedClients.go index 01c2f99fe..f2f47ac83 100644 --- a/controllers/admin/connectedClients.go +++ b/webserver/handlers/adminApiConnectedClients.go @@ -1,25 +1,25 @@ -package admin +package handlers import ( "encoding/json" "net/http" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/webserver/responses" ) // GetConnectedChatClients returns currently connected clients. -func GetConnectedChatClients(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetConnectedChatClients(w http.ResponseWriter, r *http.Request) { clients := chat.GetClients() w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(clients); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) } } // ExternalGetConnectedChatClients returns currently connected clients. -func ExternalGetConnectedChatClients(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { - GetConnectedChatClients(w, r) +func (h *Handlers) ExternalGetConnectedChatClients(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + h.GetConnectedChatClients(w, r) } diff --git a/controllers/admin/yp.go b/webserver/handlers/adminApiDirectory.go similarity index 55% rename from controllers/admin/yp.go rename to webserver/handlers/adminApiDirectory.go index c6595b86e..e38d2dd12 100644 --- a/controllers/admin/yp.go +++ b/webserver/handlers/adminApiDirectory.go @@ -1,20 +1,20 @@ -package admin +package handlers import ( "net/http" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/webserver/responses" log "github.com/sirupsen/logrus" ) // ResetYPRegistration will clear the YP protocol registration key. -func ResetYPRegistration(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) ResetYPRegistration(w http.ResponseWriter, r *http.Request) { log.Traceln("Resetting YP registration key") if err := data.SetDirectoryRegistrationKey(""); err != nil { log.Errorln(err) - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } - controllers.WriteSimpleResponse(w, true, "reset") + responses.WriteSimpleResponse(w, true, "reset") } diff --git a/controllers/admin/emoji.go b/webserver/handlers/adminApiEmojiConfig.go similarity index 54% rename from controllers/admin/emoji.go rename to webserver/handlers/adminApiEmojiConfig.go index 43028e6b7..086a489b0 100644 --- a/controllers/admin/emoji.go +++ b/webserver/handlers/adminApiEmojiConfig.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -8,13 +8,14 @@ import ( "path/filepath" "github.com/owncast/owncast/config" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/utils" + "github.com/owncast/owncast/webserver/requests" + "github.com/owncast/owncast/webserver/responses" ) // UploadCustomEmoji allows POSTing a new custom emoji to the server. -func UploadCustomEmoji(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { +func (h *Handlers) UploadCustomEmoji(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { return } @@ -26,13 +27,13 @@ func UploadCustomEmoji(w http.ResponseWriter, r *http.Request) { emoji := new(postEmoji) if err := json.NewDecoder(r.Body).Decode(emoji); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } bytes, _, err := utils.DecodeBase64Image(emoji.Data) if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -42,26 +43,26 @@ func UploadCustomEmoji(w http.ResponseWriter, r *http.Request) { err = os.MkdirAll(config.CustomEmojiPath, 0o700) if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } if utils.DoesFileExists(targetPath) { - controllers.WriteSimpleResponse(w, false, fmt.Sprintf("An emoji with the name %q already exists", emojiFileName)) + responses.WriteSimpleResponse(w, false, fmt.Sprintf("An emoji with the name %q already exists", emojiFileName)) return } if err = os.WriteFile(targetPath, bytes, 0o600); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } - controllers.WriteSimpleResponse(w, true, fmt.Sprintf("Emoji %q has been uploaded", emojiFileName)) + responses.WriteSimpleResponse(w, true, fmt.Sprintf("Emoji %q has been uploaded", emojiFileName)) } // DeleteCustomEmoji deletes a custom emoji. -func DeleteCustomEmoji(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { +func (h *Handlers) DeleteCustomEmoji(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { return } @@ -72,7 +73,7 @@ func DeleteCustomEmoji(w http.ResponseWriter, r *http.Request) { emoji := new(deleteEmoji) if err := json.NewDecoder(r.Body).Decode(emoji); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -81,12 +82,12 @@ func DeleteCustomEmoji(w http.ResponseWriter, r *http.Request) { if err := os.Remove(targetPath); err != nil { if os.IsNotExist(err) { - controllers.WriteSimpleResponse(w, false, fmt.Sprintf("Emoji %q doesn't exist", emoji.Name)) + responses.WriteSimpleResponse(w, false, fmt.Sprintf("Emoji %q doesn't exist", emoji.Name)) } else { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) } return } - controllers.WriteSimpleResponse(w, true, fmt.Sprintf("Emoji %q has been deleted", emoji.Name)) + responses.WriteSimpleResponse(w, true, fmt.Sprintf("Emoji %q has been deleted", emoji.Name)) } diff --git a/controllers/admin/externalAPIUsers.go b/webserver/handlers/adminApiExternalAPIUsers.go similarity index 64% rename from controllers/admin/externalAPIUsers.go rename to webserver/handlers/adminApiExternalAPIUsers.go index a6bc1a5d7..09e0a26af 100644 --- a/controllers/admin/externalAPIUsers.go +++ b/webserver/handlers/adminApiExternalAPIUsers.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -7,9 +7,9 @@ import ( "time" "github.com/owncast/owncast/config" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/utils" + "github.com/owncast/owncast/webserver/responses" ) type deleteExternalAPIUserRequest struct { @@ -22,35 +22,35 @@ type createExternalAPIUserRequest struct { } // CreateExternalAPIUser will generate a 3rd party access token. -func CreateExternalAPIUser(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) CreateExternalAPIUser(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var request createExternalAPIUserRequest if err := decoder.Decode(&request); err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } // Verify all the scopes provided are valid if !user.HasValidScopes(request.Scopes) { - controllers.BadRequestHandler(w, errors.New("one or more invalid scopes provided")) + responses.BadRequestHandler(w, errors.New("one or more invalid scopes provided")) return } token, err := utils.GenerateAccessToken() if err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } color := utils.GenerateRandomDisplayColor(config.MaxUserColor) if err := user.InsertExternalAPIUser(token, request.Name, color, request.Scopes); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } w.Header().Set("Content-Type", "application/json") - controllers.WriteResponse(w, user.ExternalAPIUser{ + responses.WriteResponse(w, user.ExternalAPIUser{ AccessToken: token, DisplayName: request.Name, DisplayColor: color, @@ -61,42 +61,42 @@ func CreateExternalAPIUser(w http.ResponseWriter, r *http.Request) { } // GetExternalAPIUsers will return all 3rd party access tokens. -func GetExternalAPIUsers(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetExternalAPIUsers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") tokens, err := user.GetExternalAPIUser() if err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } - controllers.WriteResponse(w, tokens) + responses.WriteResponse(w, tokens) } // DeleteExternalAPIUser will return a single 3rd party access token. -func DeleteExternalAPIUser(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) DeleteExternalAPIUser(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - if r.Method != controllers.POST { - controllers.WriteSimpleResponse(w, false, r.Method+" not supported") + if r.Method != http.MethodPost { + responses.WriteSimpleResponse(w, false, r.Method+" not supported") return } decoder := json.NewDecoder(r.Body) var request deleteExternalAPIUserRequest if err := decoder.Decode(&request); err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } if request.Token == "" { - controllers.BadRequestHandler(w, errors.New("must provide a token")) + responses.BadRequestHandler(w, errors.New("must provide a token")) return } if err := user.DeleteExternalAPIUser(request.Token); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } - controllers.WriteSimpleResponse(w, true, "deleted token") + responses.WriteSimpleResponse(w, true, "deleted token") } diff --git a/webserver/handlers/adminApiFederationConfig.go b/webserver/handlers/adminApiFederationConfig.go new file mode 100644 index 000000000..e8368f848 --- /dev/null +++ b/webserver/handlers/adminApiFederationConfig.go @@ -0,0 +1,180 @@ +package handlers + +import ( + "net/http" + + "github.com/owncast/owncast/activitypub" + "github.com/owncast/owncast/activitypub/outbox" + "github.com/owncast/owncast/activitypub/persistence" + "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/webserver/requests" + "github.com/owncast/owncast/webserver/responses" +) + +// SendFederatedMessage will send a manual message to the fediverse. +func (h *Handlers) SendFederatedMessage(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + message, ok := configValue.Value.(string) + if !ok { + responses.WriteSimpleResponse(w, false, "unable to send message") + return + } + + if err := activitypub.SendPublicFederatedMessage(message); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "sent") +} + +// SetFederationEnabled will set if Federation features are enabled. +func (h *Handlers) SetFederationEnabled(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetFederationEnabled(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + responses.WriteSimpleResponse(w, true, "federation features saved") +} + +// SetFederationActivityPrivate will set if Federation features are private to followers. +func (h *Handlers) SetFederationActivityPrivate(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetFederationIsPrivate(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + // Update Fediverse followers about this change. + if err := outbox.UpdateFollowersWithAccountUpdates(); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "federation private saved") +} + +// SetFederationShowEngagement will set if Fedivese engagement shows in chat. +func (h *Handlers) SetFederationShowEngagement(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetFederationShowEngagement(configValue.Value.(bool)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + responses.WriteSimpleResponse(w, true, "federation show engagement saved") +} + +// SetFederationUsername will set the local actor username used for federation activities. +func (h *Handlers) SetFederationUsername(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetFederationUsername(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "username saved") +} + +// SetFederationGoLiveMessage will set the federated message sent when the streamer goes live. +func (h *Handlers) SetFederationGoLiveMessage(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValue, success := requests.GetValueFromRequest(w, r) + if !success { + return + } + + if err := data.SetFederationGoLiveMessage(configValue.Value.(string)); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "message saved") +} + +// SetFederationBlockDomains saves a list of domains to block on the Fediverse. +func (h *Handlers) SetFederationBlockDomains(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + configValues, success := requests.GetValuesFromRequest(w, r) + if !success { + responses.WriteSimpleResponse(w, false, "unable to handle provided domains") + return + } + + domainStrings := make([]string, 0) + for _, domain := range configValues { + domainStrings = append(domainStrings, domain.Value.(string)) + } + + if err := data.SetBlockedFederatedDomains(domainStrings); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + responses.WriteSimpleResponse(w, true, "saved") +} + +// GetFederatedActions will return the saved list of accepted inbound +// federated activities. +func (h *Handlers) GetFederatedActions(page int, pageSize int, w http.ResponseWriter, r *http.Request) { + offset := pageSize * page + + activities, total, err := persistence.GetInboundActivities(pageSize, offset) + if err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) + return + } + + response := responses.PaginatedResponse{ + Total: total, + Results: activities, + } + + responses.WriteResponse(w, response) +} diff --git a/controllers/admin/followers.go b/webserver/handlers/adminApiFollowers.go similarity index 53% rename from controllers/admin/followers.go rename to webserver/handlers/adminApiFollowers.go index dd144791c..2c67fc8bb 100644 --- a/controllers/admin/followers.go +++ b/webserver/handlers/adminApiFollowers.go @@ -1,18 +1,19 @@ -package admin +package handlers import ( "encoding/json" "net/http" "github.com/owncast/owncast/activitypub/persistence" - "github.com/owncast/owncast/activitypub/requests" - "github.com/owncast/owncast/controllers" + aprequests "github.com/owncast/owncast/activitypub/requests" "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/webserver/requests" + "github.com/owncast/owncast/webserver/responses" ) // ApproveFollower will approve a federated follow request. -func ApproveFollower(w http.ResponseWriter, r *http.Request) { - if !requirePOST(w, r) { +func (h *Handlers) ApproveFollower(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { return } @@ -24,14 +25,14 @@ func ApproveFollower(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var approval approveFollowerRequest if err := decoder.Decode(&approval); err != nil { - controllers.WriteSimpleResponse(w, false, "unable to handle follower state with provided values") + responses.WriteSimpleResponse(w, false, "unable to handle follower state with provided values") return } if approval.Approved { // Approve a follower if err := persistence.ApprovePreviousFollowRequest(approval.ActorIRI); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -39,44 +40,44 @@ func ApproveFollower(w http.ResponseWriter, r *http.Request) { followRequest, err := persistence.GetFollower(approval.ActorIRI) if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } // Send the approval to the follow requestor. - if err := requests.SendFollowAccept(followRequest.Inbox, followRequest.RequestObject, localAccountName); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + if err := aprequests.SendFollowAccept(followRequest.Inbox, followRequest.RequestObject, localAccountName); err != nil { + responses.WriteSimpleResponse(w, false, err.Error()) return } } else { // Remove/block a follower if err := persistence.BlockOrRejectFollower(approval.ActorIRI); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } } - controllers.WriteSimpleResponse(w, true, "follower updated") + responses.WriteSimpleResponse(w, true, "follower updated") } // GetPendingFollowRequests will return a list of pending follow requests. -func GetPendingFollowRequests(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetPendingFollowRequests(w http.ResponseWriter, r *http.Request) { requests, err := persistence.GetPendingFollowRequests() if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } - controllers.WriteResponse(w, requests) + responses.WriteResponse(w, requests) } // GetBlockedAndRejectedFollowers will return blocked and rejected followers. -func GetBlockedAndRejectedFollowers(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetBlockedAndRejectedFollowers(w http.ResponseWriter, r *http.Request) { rejections, err := persistence.GetBlockedAndRejectedFollowers() if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } - controllers.WriteResponse(w, rejections) + responses.WriteResponse(w, rejections) } diff --git a/controllers/admin/hardware.go b/webserver/handlers/adminApiHardwareStats.go similarity index 78% rename from controllers/admin/hardware.go rename to webserver/handlers/adminApiHardwareStats.go index fc036bc3f..441220d81 100644 --- a/controllers/admin/hardware.go +++ b/webserver/handlers/adminApiHardwareStats.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -9,7 +9,7 @@ import ( ) // GetHardwareStats will return hardware utilization over time. -func GetHardwareStats(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetHardwareStats(w http.ResponseWriter, r *http.Request) { m := metrics.GetMetrics() w.Header().Set("Content-Type", "application/json") diff --git a/controllers/admin/logs.go b/webserver/handlers/adminApiLogs.go similarity index 88% rename from controllers/admin/logs.go rename to webserver/handlers/adminApiLogs.go index 266d75c7e..abc6889d3 100644 --- a/controllers/admin/logs.go +++ b/webserver/handlers/adminApiLogs.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -11,7 +11,7 @@ import ( ) // GetLogs will return all logs. -func GetLogs(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetLogs(w http.ResponseWriter, r *http.Request) { logs := logging.Logger.AllEntries() response := make([]logsResponse, 0) @@ -26,7 +26,7 @@ func GetLogs(w http.ResponseWriter, r *http.Request) { } // GetWarnings will return only warning and error logs. -func GetWarnings(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetWarnings(w http.ResponseWriter, r *http.Request) { logs := logging.Logger.WarningEntries() response := make([]logsResponse, 0) diff --git a/webserver/handlers/adminApiNotificationsConfig.go b/webserver/handlers/adminApiNotificationsConfig.go new file mode 100644 index 000000000..5dbf6a149 --- /dev/null +++ b/webserver/handlers/adminApiNotificationsConfig.go @@ -0,0 +1,61 @@ +package handlers + +import ( + "encoding/json" + "net/http" + + "github.com/owncast/owncast/core/data" + "github.com/owncast/owncast/models" + "github.com/owncast/owncast/webserver/requests" + "github.com/owncast/owncast/webserver/responses" +) + +// SetDiscordNotificationConfiguration will set the discord notification configuration. +func (h *Handlers) SetDiscordNotificationConfiguration(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + type request struct { + Value models.DiscordConfiguration `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var config request + if err := decoder.Decode(&config); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update discord config with provided values") + return + } + + if err := data.SetDiscordConfig(config.Value); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update discord config with provided values") + return + } + + responses.WriteSimpleResponse(w, true, "updated discord config with provided values") +} + +// SetBrowserNotificationConfiguration will set the browser notification configuration. +func (h *Handlers) SetBrowserNotificationConfiguration(w http.ResponseWriter, r *http.Request) { + if !requests.RequirePOST(w, r) { + return + } + + type request struct { + Value models.BrowserNotificationConfiguration `json:"value"` + } + + decoder := json.NewDecoder(r.Body) + var config request + if err := decoder.Decode(&config); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") + return + } + + if err := data.SetBrowserPushConfig(config.Value); err != nil { + responses.WriteSimpleResponse(w, false, "unable to update browser push config with provided values") + return + } + + responses.WriteSimpleResponse(w, true, "updated browser push config with provided values") +} diff --git a/controllers/admin/serverConfig.go b/webserver/handlers/adminApiServerConfig.go similarity index 69% rename from controllers/admin/serverConfig.go rename to webserver/handlers/adminApiServerConfig.go index 5c95f7e7d..2dbbbe3dd 100644 --- a/controllers/admin/serverConfig.go +++ b/webserver/handlers/adminApiServerConfig.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -14,7 +14,7 @@ import ( ) // GetServerConfig gets the config details of the server. -func GetServerConfig(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetServerConfig(w http.ResponseWriter, r *http.Request) { ffmpeg := utils.ValidatedFfmpegPath(data.GetFfMpegPath()) usernameBlocklist := data.GetForbiddenUsernameList() usernameSuggestions := data.GetSuggestedUsernamesList() @@ -34,7 +34,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { }) } response := serverConfigAdminResponse{ - InstanceDetails: webConfigResponse{ + InstanceDetails: adminWebConfigResponse{ Name: data.GetServerName(), Summary: data.GetServerSummary(), Tags: data.GetServerMetadataTags(), @@ -77,7 +77,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { VideoCodec: data.GetVideoCodec(), ForbiddenUsernames: usernameBlocklist, SuggestedUsernames: usernameSuggestions, - Federation: federationConfigResponse{ + Federation: adminFederationConfigResponse{ Enabled: data.GetFederationEnabled(), IsPrivate: data.GetFederationIsPrivate(), Username: data.GetFederationUsername(), @@ -85,7 +85,7 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { ShowEngagement: data.GetFederationShowEngagement(), BlockedDomains: data.GetBlockedFederatedDomains(), }, - Notifications: notificationsConfigResponse{ + Notifications: adminNotificationsConfigResponse{ Discord: data.GetDiscordConfig(), Browser: data.GetBrowserPushConfig(), }, @@ -100,31 +100,31 @@ func GetServerConfig(w http.ResponseWriter, r *http.Request) { } type serverConfigAdminResponse struct { - InstanceDetails webConfigResponse `json:"instanceDetails"` - Notifications notificationsConfigResponse `json:"notifications"` - YP yp `json:"yp"` - FFmpegPath string `json:"ffmpegPath"` - AdminPassword string `json:"adminPassword"` - SocketHostOverride string `json:"socketHostOverride,omitempty"` - WebServerIP string `json:"webServerIP"` - VideoCodec string `json:"videoCodec"` - VideoServingEndpoint string `json:"videoServingEndpoint"` - S3 models.S3 `json:"s3"` - Federation federationConfigResponse `json:"federation"` - SupportedCodecs []string `json:"supportedCodecs"` - ExternalActions []models.ExternalAction `json:"externalActions"` - ForbiddenUsernames []string `json:"forbiddenUsernames"` - SuggestedUsernames []string `json:"suggestedUsernames"` - StreamKeys []models.StreamKey `json:"streamKeys"` - VideoSettings videoSettings `json:"videoSettings"` - RTMPServerPort int `json:"rtmpServerPort"` - WebServerPort int `json:"webServerPort"` - ChatDisabled bool `json:"chatDisabled"` - ChatJoinMessagesEnabled bool `json:"chatJoinMessagesEnabled"` - ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"` - DisableSearchIndexing bool `json:"disableSearchIndexing"` - StreamKeyOverridden bool `json:"streamKeyOverridden"` - HideViewerCount bool `json:"hideViewerCount"` + InstanceDetails adminWebConfigResponse `json:"instanceDetails"` + Notifications adminNotificationsConfigResponse `json:"notifications"` + YP yp `json:"yp"` + FFmpegPath string `json:"ffmpegPath"` + AdminPassword string `json:"adminPassword"` + SocketHostOverride string `json:"socketHostOverride,omitempty"` + WebServerIP string `json:"webServerIP"` + VideoCodec string `json:"videoCodec"` + VideoServingEndpoint string `json:"videoServingEndpoint"` + S3 models.S3 `json:"s3"` + Federation adminFederationConfigResponse `json:"federation"` + SupportedCodecs []string `json:"supportedCodecs"` + ExternalActions []models.ExternalAction `json:"externalActions"` + ForbiddenUsernames []string `json:"forbiddenUsernames"` + SuggestedUsernames []string `json:"suggestedUsernames"` + StreamKeys []models.StreamKey `json:"streamKeys"` + VideoSettings videoSettings `json:"videoSettings"` + RTMPServerPort int `json:"rtmpServerPort"` + WebServerPort int `json:"webServerPort"` + ChatDisabled bool `json:"chatDisabled"` + ChatJoinMessagesEnabled bool `json:"chatJoinMessagesEnabled"` + ChatEstablishedUserMode bool `json:"chatEstablishedUserMode"` + DisableSearchIndexing bool `json:"disableSearchIndexing"` + StreamKeyOverridden bool `json:"streamKeyOverridden"` + HideViewerCount bool `json:"hideViewerCount"` } type videoSettings struct { @@ -132,7 +132,7 @@ type videoSettings struct { LatencyLevel int `json:"latencyLevel"` } -type webConfigResponse struct { +type adminWebConfigResponse struct { AppearanceVariables map[string]string `json:"appearanceVariables"` Version string `json:"version"` WelcomeMessage string `json:"welcomeMessage"` @@ -155,7 +155,7 @@ type yp struct { Enabled bool `json:"enabled"` } -type federationConfigResponse struct { +type adminFederationConfigResponse struct { Username string `json:"username"` GoLiveMessage string `json:"goLiveMessage"` BlockedDomains []string `json:"blockedDomains"` @@ -164,7 +164,7 @@ type federationConfigResponse struct { ShowEngagement bool `json:"showEngagement"` } -type notificationsConfigResponse struct { +type adminNotificationsConfigResponse struct { Browser models.BrowserNotificationConfiguration `json:"browser"` Discord models.DiscordConfiguration `json:"discord"` } diff --git a/controllers/admin/update.go b/webserver/handlers/adminApiSoftwareUpdate.go similarity index 90% rename from controllers/admin/update.go rename to webserver/handlers/adminApiSoftwareUpdate.go index 8f93fa37b..217887f53 100644 --- a/controllers/admin/update.go +++ b/webserver/handlers/adminApiSoftwareUpdate.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "fmt" @@ -10,7 +10,7 @@ import ( "strings" "github.com/owncast/owncast/config" - "github.com/owncast/owncast/controllers" + "github.com/owncast/owncast/webserver/responses" log "github.com/sirupsen/logrus" ) @@ -33,7 +33,7 @@ specific conditions are met. */ // AutoUpdateOptions will return what auto update options are available. -func AutoUpdateOptions(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) AutoUpdateOptions(w http.ResponseWriter, r *http.Request) { type autoUpdateOptionsResponse struct { SupportsUpdate bool `json:"supportsUpdate"` CanRestart bool `json:"canRestart"` @@ -47,7 +47,7 @@ func AutoUpdateOptions(w http.ResponseWriter, r *http.Request) { // Nothing is supported when running under "dev" or the feature is // explicitly disabled. if config.BuildPlatform == "dev" || !config.EnableAutoUpdate { - controllers.WriteResponse(w, updateOptions) + responses.WriteResponse(w, updateOptions) return } @@ -58,11 +58,11 @@ func AutoUpdateOptions(w http.ResponseWriter, r *http.Request) { updateOptions.CanRestart = isRunningUnderSystemD() - controllers.WriteResponse(w, updateOptions) + responses.WriteResponse(w, updateOptions) } // AutoUpdateStart will begin the auto update process. -func AutoUpdateStart(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) AutoUpdateStart(w http.ResponseWriter, r *http.Request) { // We return the console output directly to the client. w.Header().Set("Content-Type", "text/plain") @@ -70,7 +70,7 @@ func AutoUpdateStart(w http.ResponseWriter, r *http.Request) { updater, err := downloadInstaller() if err != nil { log.Errorln(err) - controllers.WriteSimpleResponse(w, false, "failed to download and run installer") + responses.WriteSimpleResponse(w, false, "failed to download and run installer") return } @@ -95,12 +95,12 @@ func AutoUpdateStart(w http.ResponseWriter, r *http.Request) { } // AutoUpdateForceQuit will force quit the service. -func AutoUpdateForceQuit(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) AutoUpdateForceQuit(w http.ResponseWriter, r *http.Request) { log.Warnln("Owncast is exiting due to request.") go func() { os.Exit(0) }() - controllers.WriteSimpleResponse(w, true, "forcing quit") + responses.WriteSimpleResponse(w, true, "forcing quit") } func downloadInstaller() (string, error) { diff --git a/controllers/admin/status.go b/webserver/handlers/adminApiSystemStatus.go similarity index 95% rename from controllers/admin/status.go rename to webserver/handlers/adminApiSystemStatus.go index 6f30edf96..f363525bc 100644 --- a/controllers/admin/status.go +++ b/webserver/handlers/adminApiSystemStatus.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -13,7 +13,7 @@ import ( ) // Status gets the details of the inbound broadcaster. -func Status(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetAdminStatus(w http.ResponseWriter, r *http.Request) { broadcaster := core.GetBroadcaster() status := core.GetStatus() currentBroadcast := core.GetCurrentBroadcast() diff --git a/controllers/admin/video.go b/webserver/handlers/adminApiVideoConfig.go similarity index 97% rename from controllers/admin/video.go rename to webserver/handlers/adminApiVideoConfig.go index 65f7a69e1..45374cc5e 100644 --- a/controllers/admin/video.go +++ b/webserver/handlers/adminApiVideoConfig.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -11,7 +11,7 @@ import ( ) // GetVideoPlaybackMetrics returns video playback metrics. -func GetVideoPlaybackMetrics(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetVideoPlaybackMetrics(w http.ResponseWriter, r *http.Request) { type response struct { Errors []metrics.TimestampedValue `json:"errors"` QualityVariantChanges []metrics.TimestampedValue `json:"qualityVariantChanges"` diff --git a/controllers/admin/viewers.go b/webserver/handlers/adminApiViewers.go similarity index 70% rename from controllers/admin/viewers.go rename to webserver/handlers/adminApiViewers.go index ed542a31a..3f0ea0459 100644 --- a/controllers/admin/viewers.go +++ b/webserver/handlers/adminApiViewers.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -6,20 +6,20 @@ import ( "strconv" "time" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core" "github.com/owncast/owncast/core/user" "github.com/owncast/owncast/metrics" "github.com/owncast/owncast/models" + "github.com/owncast/owncast/webserver/responses" log "github.com/sirupsen/logrus" ) // GetViewersOverTime will return the number of viewers at points in time. -func GetViewersOverTime(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetViewersOverTime(w http.ResponseWriter, r *http.Request) { windowStartAtStr := r.URL.Query().Get("windowStart") windowStartAtUnix, err := strconv.Atoi(windowStartAtStr) if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -35,7 +35,7 @@ func GetViewersOverTime(w http.ResponseWriter, r *http.Request) { } // GetActiveViewers returns currently connected clients. -func GetActiveViewers(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetActiveViewers(w http.ResponseWriter, r *http.Request) { c := core.GetActiveViewers() viewers := make([]models.Viewer, 0, len(c)) for _, v := range c { @@ -45,11 +45,11 @@ func GetActiveViewers(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(viewers); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) } } // ExternalGetActiveViewers returns currently connected clients. -func ExternalGetActiveViewers(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { - GetConnectedChatClients(w, r) +func (h *Handlers) ExternalGetActiveViewers(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) { + h.GetConnectedChatClients(w, r) } diff --git a/controllers/admin/webhooks.go b/webserver/handlers/adminApiWebhooks.go similarity index 59% rename from controllers/admin/webhooks.go rename to webserver/handlers/adminApiWebhooks.go index 9407b1996..e50644fb1 100644 --- a/controllers/admin/webhooks.go +++ b/webserver/handlers/adminApiWebhooks.go @@ -1,4 +1,4 @@ -package admin +package handlers import ( "encoding/json" @@ -6,9 +6,9 @@ import ( "net/http" "time" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/models" + "github.com/owncast/owncast/webserver/responses" ) type deleteWebhookRequest struct { @@ -21,27 +21,27 @@ type createWebhookRequest struct { } // CreateWebhook will add a single webhook. -func CreateWebhook(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) CreateWebhook(w http.ResponseWriter, r *http.Request) { decoder := json.NewDecoder(r.Body) var request createWebhookRequest if err := decoder.Decode(&request); err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } // Verify all the scopes provided are valid if !models.HasValidEvents(request.Events) { - controllers.BadRequestHandler(w, errors.New("one or more invalid event provided")) + responses.BadRequestHandler(w, errors.New("one or more invalid event provided")) return } newWebhookID, err := data.InsertWebhook(request.URL, request.Events) if err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } - controllers.WriteResponse(w, models.Webhook{ + responses.WriteResponse(w, models.Webhook{ ID: newWebhookID, URL: request.URL, Events: request.Events, @@ -51,34 +51,34 @@ func CreateWebhook(w http.ResponseWriter, r *http.Request) { } // GetWebhooks will return all webhooks. -func GetWebhooks(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) GetWebhooks(w http.ResponseWriter, r *http.Request) { webhooks, err := data.GetWebhooks() if err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } - controllers.WriteResponse(w, webhooks) + responses.WriteResponse(w, webhooks) } // DeleteWebhook will delete a single webhook. -func DeleteWebhook(w http.ResponseWriter, r *http.Request) { - if r.Method != controllers.POST { - controllers.WriteSimpleResponse(w, false, r.Method+" not supported") +func (h *Handlers) DeleteWebhook(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + responses.WriteSimpleResponse(w, false, r.Method+" not supported") return } decoder := json.NewDecoder(r.Body) var request deleteWebhookRequest if err := decoder.Decode(&request); err != nil { - controllers.BadRequestHandler(w, err) + responses.BadRequestHandler(w, err) return } if err := data.DeleteWebhook(request.ID); err != nil { - controllers.InternalErrorHandler(w, err) + responses.InternalErrorHandler(w, err) return } - controllers.WriteSimpleResponse(w, true, "deleted webhook") + responses.WriteSimpleResponse(w, true, "deleted webhook") } diff --git a/controllers/auth/fediverse/fediverse.go b/webserver/handlers/auth/fediverse/fediverse.go similarity index 79% rename from controllers/auth/fediverse/fediverse.go rename to webserver/handlers/auth/fediverse/fediverse.go index 6f7002bff..2e2df52dd 100644 --- a/controllers/auth/fediverse/fediverse.go +++ b/webserver/handlers/auth/fediverse/fediverse.go @@ -8,10 +8,10 @@ import ( "github.com/owncast/owncast/activitypub" "github.com/owncast/owncast/auth" fediverseauth "github.com/owncast/owncast/auth/fediverse" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/data" "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/webserver/responses" log "github.com/sirupsen/logrus" ) @@ -23,29 +23,29 @@ func RegisterFediverseOTPRequest(u user.User, w http.ResponseWriter, r *http.Req var req request decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&req); err != nil { - controllers.WriteSimpleResponse(w, false, "Could not decode request: "+err.Error()) + responses.WriteSimpleResponse(w, false, "Could not decode request: "+err.Error()) return } accessToken := r.URL.Query().Get("accessToken") reg, success, err := fediverseauth.RegisterFediverseOTP(accessToken, u.ID, u.DisplayName, req.FediverseAccount) if err != nil { - controllers.WriteSimpleResponse(w, false, "Could not register auth request: "+err.Error()) + responses.WriteSimpleResponse(w, false, "Could not register auth request: "+err.Error()) return } if !success { - controllers.WriteSimpleResponse(w, false, "Could not register auth request. One may already be pending. Try again later.") + responses.WriteSimpleResponse(w, false, "Could not register auth request. One may already be pending. Try again later.") return } msg := fmt.Sprintf("
This is an automated message from %s. If you did not request this message please ignore or block. Your requested one-time code is:
%s
", data.GetServerName(), reg.Code) if err := activitypub.SendDirectFederatedMessage(msg, reg.Account); err != nil { - controllers.WriteSimpleResponse(w, false, "Could not send code to fediverse: "+err.Error()) + responses.WriteSimpleResponse(w, false, "Could not send code to fediverse: "+err.Error()) return } - controllers.WriteSimpleResponse(w, true, "") + responses.WriteSimpleResponse(w, true, "") } // VerifyFediverseOTPRequest verifies the given OTP code for the given access token. @@ -57,7 +57,7 @@ func VerifyFediverseOTPRequest(w http.ResponseWriter, r *http.Request) { var req request decoder := json.NewDecoder(r.Body) if err := decoder.Decode(&req); err != nil { - controllers.WriteSimpleResponse(w, false, "Could not decode request: "+err.Error()) + responses.WriteSimpleResponse(w, false, "Could not decode request: "+err.Error()) return } accessToken := r.URL.Query().Get("accessToken") @@ -75,7 +75,7 @@ func VerifyFediverseOTPRequest(w http.ResponseWriter, r *http.Request) { // Update the current user's access token to point to the existing user id. userID := u.ID if err := user.SetAccessTokenToOwner(accessToken, userID); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -86,7 +86,7 @@ func VerifyFediverseOTPRequest(w http.ResponseWriter, r *http.Request) { } } - controllers.WriteSimpleResponse(w, true, "") + responses.WriteSimpleResponse(w, true, "") return } @@ -94,7 +94,7 @@ func VerifyFediverseOTPRequest(w http.ResponseWriter, r *http.Request) { // Otherwise, save this as new auth. log.Debug("fediverse account does not already exist, saving it as a new one for the current user") if err := auth.AddAuth(authRegistration.UserID, authRegistration.Account, auth.Fediverse); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -104,5 +104,5 @@ func VerifyFediverseOTPRequest(w http.ResponseWriter, r *http.Request) { log.Errorln(err) } - controllers.WriteSimpleResponse(w, true, "") + responses.WriteSimpleResponse(w, true, "") } diff --git a/controllers/auth/indieauth/client.go b/webserver/handlers/auth/indieauth/client.go similarity index 85% rename from controllers/auth/indieauth/client.go rename to webserver/handlers/auth/indieauth/client.go index 8e7960c76..5f3ac8b4b 100644 --- a/controllers/auth/indieauth/client.go +++ b/webserver/handlers/auth/indieauth/client.go @@ -8,9 +8,9 @@ import ( "github.com/owncast/owncast/auth" ia "github.com/owncast/owncast/auth/indieauth" - "github.com/owncast/owncast/controllers" "github.com/owncast/owncast/core/chat" "github.com/owncast/owncast/core/user" + "github.com/owncast/owncast/webserver/responses" log "github.com/sirupsen/logrus" ) @@ -27,12 +27,12 @@ func StartAuthFlow(u user.User, w http.ResponseWriter, r *http.Request) { var authRequest request p, err := io.ReadAll(r.Body) if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } if err := json.Unmarshal(p, &authRequest); err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } @@ -40,14 +40,14 @@ func StartAuthFlow(u user.User, w http.ResponseWriter, r *http.Request) { redirectURL, err := ia.StartAuthFlow(authRequest.AuthHost, u.ID, accessToken, u.DisplayName) if err != nil { - controllers.WriteSimpleResponse(w, false, err.Error()) + responses.WriteSimpleResponse(w, false, err.Error()) return } redirectResponse := response{ Redirect: redirectURL.String(), } - controllers.WriteResponse(w, redirectResponse) + responses.WriteResponse(w, redirectResponse) } // HandleRedirect will handle the redirect from an IndieAuth server to @@ -59,7 +59,7 @@ func HandleRedirect(w http.ResponseWriter, r *http.Request) { if err != nil { log.Debugln(err) msg := `Unable to complete authentication. Go back.