feat: first pass migrating all the handlers to the new architecture

This commit is contained in:
Gabe Kangas 2023-06-09 17:29:02 -07:00
parent 551440f3b6
commit db67603550
No known key found for this signature in database
GPG key ID: 4345B2060657F330
64 changed files with 1696 additions and 1733 deletions

View file

@ -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 {

View file

@ -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)
}

View file

@ -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")
}

View file

@ -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")
}

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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")
}

View file

@ -1,7 +0,0 @@
package controllers
// POST is the HTTP POST method.
const POST = "POST"
// GET is the HTTP GET method.
const GET = "GET"

View file

@ -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)
}

View file

@ -1 +0,0 @@
package handlers

View file

@ -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")
}

View file

@ -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")
}

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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")
}

View file

@ -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))
}

View file

@ -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")
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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")

View file

@ -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)

View file

@ -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")
}

View file

@ -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"`
}

View file

@ -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) {

View file

@ -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()

View file

@ -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"`

View file

@ -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)
}

View file

@ -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")
}

View file

@ -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("<p>This is an automated message from %s. If you did not request this message please ignore or block. Your requested one-time code is:</p><p>%s</p>", 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, "")
}

View file

@ -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. <a href="/">Go back.</a><hr/>`
_ = controllers.WriteString(w, msg, http.StatusBadRequest)
_ = responses.WriteString(w, msg, http.StatusBadRequest)
return
}
@ -72,7 +72,7 @@ func HandleRedirect(w http.ResponseWriter, r *http.Request) {
accessToken := request.CurrentAccessToken
userID := u.ID
if err := user.SetAccessTokenToOwner(accessToken, userID); err != nil {
controllers.WriteSimpleResponse(w, false, err.Error())
responses.WriteSimpleResponse(w, false, err.Error())
return
}
@ -91,7 +91,7 @@ func HandleRedirect(w http.ResponseWriter, r *http.Request) {
// Otherwise, save this as new auth.
log.Debug("indieauth token does not already exist, saving it as a new one for the current user")
if err := auth.AddAuth(request.UserID, response.Me, auth.IndieAuth); err != nil {
controllers.WriteSimpleResponse(w, false, err.Error())
responses.WriteSimpleResponse(w, false, err.Error())
return
}

View file

@ -5,8 +5,8 @@ import (
"net/url"
ia "github.com/owncast/owncast/auth/indieauth"
"github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/webserver/middleware"
"github.com/owncast/owncast/webserver/responses"
)
// HandleAuthEndpoint will handle the IndieAuth auth endpoint.
@ -33,7 +33,7 @@ func handleAuthEndpointGet(w http.ResponseWriter, r *http.Request) {
request, err := ia.StartServerAuth(clientID, redirectURI, codeChallenge, state, me)
if err != nil {
_ = controllers.WriteString(w, err.Error(), http.StatusInternalServerError)
_ = responses.WriteString(w, err.Error(), http.StatusInternalServerError)
return
}
@ -42,7 +42,7 @@ func handleAuthEndpointGet(w http.ResponseWriter, r *http.Request) {
// If the URL is invalid then return with specific "invalid_request" error.
u, err := url.Parse(redirectURI)
if err != nil {
controllers.WriteResponse(w, ia.Response{
responses.WriteResponse(w, ia.Response{
Error: "invalid_request",
ErrorDescription: err.Error(),
})
@ -59,7 +59,7 @@ func handleAuthEndpointGet(w http.ResponseWriter, r *http.Request) {
func handleAuthEndpointPost(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
controllers.WriteSimpleResponse(w, false, err.Error())
responses.WriteSimpleResponse(w, false, err.Error())
return
}
@ -72,12 +72,12 @@ func handleAuthEndpointPost(w http.ResponseWriter, r *http.Request) {
// "invalid_client" error.
response, err := ia.CompleteServerAuth(code, redirectURI, clientID, codeVerifier)
if err != nil {
controllers.WriteResponse(w, ia.Response{
responses.WriteResponse(w, ia.Response{
Error: "invalid_client",
ErrorDescription: err.Error(),
})
return
}
controllers.WriteResponse(w, response)
responses.WriteResponse(w, response)
}

View file

@ -1,7 +1,8 @@
package controllers
package handlers
import (
"encoding/json"
"errors"
"net/http"
"github.com/owncast/owncast/config"
@ -9,17 +10,18 @@ import (
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/middleware"
"github.com/owncast/owncast/webserver/responses"
log "github.com/sirupsen/logrus"
)
// ExternalGetChatMessages gets all of the chat messages.
func ExternalGetChatMessages(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
func (h *Handlers) ExternalGetChatMessages(integration user.ExternalAPIUser, w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(w)
getChatMessages(w, r)
}
// GetChatMessages gets all of the chat messages.
func GetChatMessages(u user.User, w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetChatMessages(u user.User, w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(w)
getChatMessages(w, r)
}
@ -36,14 +38,12 @@ func getChatMessages(w http.ResponseWriter, r *http.Request) {
}
default:
w.WriteHeader(http.StatusNotImplemented)
if err := json.NewEncoder(w).Encode(j{"error": "method not implemented (PRs are accepted)"}); err != nil {
InternalErrorHandler(w, err)
}
responses.BadRequestHandler(w, errors.New("method not implemented"))
}
}
// RegisterAnonymousChatUser will register a new user.
func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(w)
if r.Method == "OPTIONS" {
@ -54,8 +54,7 @@ func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
}
if r.Method != http.MethodPost {
// nolint:goconst
WriteSimpleResponse(w, false, r.Method+" not supported")
responses.WriteSimpleResponse(w, false, r.Method+" not supported")
return
}
@ -82,7 +81,7 @@ func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
proposedNewDisplayName := utils.MakeSafeStringOfLength(request.DisplayName, config.MaxChatDisplayNameLength)
newUser, accessToken, err := user.CreateAnonymousUser(proposedNewDisplayName)
if err != nil {
WriteSimpleResponse(w, false, err.Error())
responses.WriteSimpleResponse(w, false, err.Error())
return
}
@ -95,5 +94,5 @@ func RegisterAnonymousChatUser(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
middleware.DisableCache(w)
WriteResponse(w, response)
responses.WriteResponse(w, response)
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"encoding/json"
@ -12,6 +12,7 @@ import (
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/middleware"
"github.com/owncast/owncast/webserver/responses"
log "github.com/sirupsen/logrus"
)
@ -58,7 +59,7 @@ type authenticationConfigResponse struct {
}
// GetWebConfig gets the status of the server.
func GetWebConfig(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetWebConfig(w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(w)
middleware.DisableCache(w)
w.Header().Set("Content-Type", "application/json")
@ -66,7 +67,7 @@ func GetWebConfig(w http.ResponseWriter, r *http.Request) {
configuration := getConfigResponse()
if err := json.NewEncoder(w).Encode(configuration); err != nil {
BadRequestHandler(w, err)
responses.BadRequestHandler(w, err)
}
}
@ -142,12 +143,12 @@ func getConfigResponse() webConfigResponse {
}
// GetAllSocialPlatforms will return a list of all social platform types.
func GetAllSocialPlatforms(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetAllSocialPlatforms(w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(w)
w.Header().Set("Content-Type", "application/json")
platforms := models.GetAllSocialHandles()
if err := json.NewEncoder(w).Encode(platforms); err != nil {
InternalErrorHandler(w, err)
responses.InternalErrorHandler(w, err)
}
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"net/http"
@ -7,7 +7,7 @@ import (
)
// ServeCustomJavascript will serve optional custom Javascript.
func ServeCustomJavascript(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) ServeCustomJavascript(w http.ResponseWriter, r *http.Request) {
js := data.GetCustomJavascript()
_, _ = w.Write([]byte(js))
}

View file

@ -1 +0,0 @@
package handlers

View file

@ -0,0 +1,21 @@
package handlers
import (
"net/http"
"github.com/owncast/owncast/core"
"github.com/owncast/owncast/webserver/responses"
"github.com/owncast/owncast/core/rtmp"
)
// DisconnectInboundConnection will force-disconnect an inbound stream.
func (h *Handlers) DisconnectInboundConnection(w http.ResponseWriter, r *http.Request) {
if !core.GetStatus().Online {
responses.WriteSimpleResponse(w, false, "no inbound stream connected")
return
}
rtmp.Disconnect()
responses.WriteSimpleResponse(w, true, "inbound stream disconnected")
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"encoding/json"
@ -9,20 +9,21 @@ import (
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/router/middleware"
"github.com/owncast/owncast/webserver/responses"
)
// GetCustomEmojiList returns a list of emoji via the API.
func GetCustomEmojiList(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetCustomEmojiList(w http.ResponseWriter, r *http.Request) {
emojiList := data.GetEmojiList()
middleware.SetCachingHeaders(w, r)
if err := json.NewEncoder(w).Encode(emojiList); err != nil {
InternalErrorHandler(w, err)
responses.InternalErrorHandler(w, err)
}
}
// GetCustomEmojiImage returns a single emoji image.
func GetCustomEmojiImage(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetCustomEmojiImage(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/img/emoji/")
r.URL.Path = path

View file

@ -1 +0,0 @@
package handlers

View file

@ -1,22 +1,23 @@
package controllers
package handlers
import (
"net/http"
"github.com/owncast/owncast/activitypub/persistence"
"github.com/owncast/owncast/webserver/responses"
)
// GetFollowers will handle an API request to fetch the list of followers (non-activitypub response).
func GetFollowers(offset int, limit int, w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetFollowers(offset int, limit int, w http.ResponseWriter, r *http.Request) {
followers, total, err := persistence.GetFederationFollowers(limit, offset)
if err != nil {
WriteSimpleResponse(w, false, "unable to fetch followers")
responses.WriteSimpleResponse(w, false, "unable to fetch followers")
return
}
response := PaginatedResponse{
response := responses.PaginatedResponse{
Total: total,
Results: followers,
}
WriteResponse(w, response)
responses.WriteResponse(w, response)
}

View file

@ -1 +1,55 @@
package handlers
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 (h *Handlers) 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)
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"net/http"
@ -6,6 +6,7 @@ import (
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/responses"
)
const (
@ -14,7 +15,7 @@ const (
)
// GetThumbnail will return the thumbnail image as a response.
func GetThumbnail(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetThumbnail(w http.ResponseWriter, r *http.Request) {
imageFilename := "thumbnail.jpg"
imagePath := filepath.Join(config.TempDir, imageFilename)
@ -24,21 +25,21 @@ func GetThumbnail(w http.ResponseWriter, r *http.Request) {
if utils.DoesFileExists(imagePath) {
imageBytes, err = getImage(imagePath)
} else {
GetLogo(w, r)
h.GetLogo(w, r)
return
}
if err != nil {
GetLogo(w, r)
h.GetLogo(w, r)
return
}
cacheTime := utils.GetCacheDurationSecondsForPath(imagePath)
writeBytesAsImage(imageBytes, contentTypeJPEG, w, cacheTime)
responses.WriteBytesAsImage(imageBytes, contentTypeJPEG, w, cacheTime)
}
// GetPreview will return the preview gif as a response.
func GetPreview(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetPreview(w http.ResponseWriter, r *http.Request) {
imageFilename := "preview.gif"
imagePath := filepath.Join(config.TempDir, imageFilename)
@ -48,15 +49,15 @@ func GetPreview(w http.ResponseWriter, r *http.Request) {
if utils.DoesFileExists(imagePath) {
imageBytes, err = getImage(imagePath)
} else {
GetLogo(w, r)
h.GetLogo(w, r)
return
}
if err != nil {
GetLogo(w, r)
h.GetLogo(w, r)
return
}
cacheTime := utils.GetCacheDurationSecondsForPath(imagePath)
writeBytesAsImage(imageBytes, contentTypeGIF, w, cacheTime)
responses.WriteBytesAsImage(imageBytes, contentTypeGIF, w, cacheTime)
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"encoding/json"
@ -19,7 +19,7 @@ import (
)
// IndexHandler handles the default index route.
func IndexHandler(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) IndexHandler(w http.ResponseWriter, r *http.Request) {
middleware.EnableCors(w)
isIndexRequest := r.URL.Path == "/" || filepath.Base(r.URL.Path) == "index.html" || filepath.Base(r.URL.Path) == ""

View file

@ -1 +0,0 @@
package handlers

View file

@ -1,22 +1,22 @@
package controllers
package handlers
import (
"net/http"
"os"
"path/filepath"
"strconv"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/static"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/responses"
log "github.com/sirupsen/logrus"
)
var _hasWarnedSVGLogo = false
// GetLogo will return the logo image as a response.
func GetLogo(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetLogo(w http.ResponseWriter, r *http.Request) {
imageFilename := data.GetLogoPath()
if imageFilename == "" {
returnDefault(w)
@ -39,20 +39,20 @@ func GetLogo(w http.ResponseWriter, r *http.Request) {
}
cacheTime := utils.GetCacheDurationSecondsForPath(imagePath)
writeBytesAsImage(imageBytes, contentType, w, cacheTime)
responses.WriteBytesAsImage(imageBytes, contentType, w, cacheTime)
}
// GetCompatibleLogo will return the logo unless it's a SVG
// and in that case will return a default placeholder.
// Used for sharing to external social networks that generally
// don't support SVG.
func GetCompatibleLogo(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetCompatibleLogo(w http.ResponseWriter, r *http.Request) {
imageFilename := data.GetLogoPath()
// If the logo image is not a SVG then we can return it
// without any problems.
if imageFilename != "" && filepath.Ext(imageFilename) != ".svg" {
GetLogo(w, r)
h.GetLogo(w, r)
return
}
@ -66,7 +66,7 @@ func GetCompatibleLogo(w http.ResponseWriter, r *http.Request) {
}
cacheTime := utils.GetCacheDurationSecondsForPath(imagePath)
writeBytesAsImage(imageBytes, contentType, w, cacheTime)
responses.WriteBytesAsImage(imageBytes, contentType, w, cacheTime)
if !_hasWarnedSVGLogo {
log.Warnf("an external site requested your logo. because many social networks do not support SVGs we returned a placeholder instead. change your current logo to a png or jpeg to be most compatible with external social networking sites.")
@ -77,17 +77,7 @@ func GetCompatibleLogo(w http.ResponseWriter, r *http.Request) {
func returnDefault(w http.ResponseWriter) {
imageBytes := static.GetLogo()
cacheTime := utils.GetCacheDurationSecondsForPath("logo.png")
writeBytesAsImage(imageBytes, "image/png", w, cacheTime)
}
func writeBytesAsImage(data []byte, contentType string, w http.ResponseWriter, cacheSeconds int) {
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheSeconds))
if _, err := w.Write(data); err != nil {
log.Println("unable to write image.")
}
responses.WriteBytesAsImage(imageBytes, "image/png", w, cacheTime)
}
func getImage(path string) ([]byte, error) {

View file

@ -1,4 +1,4 @@
package moderation
package handlers
import (
"encoding/json"
@ -6,15 +6,15 @@ import (
"strings"
"time"
"github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/chat/events"
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/webserver/responses"
log "github.com/sirupsen/logrus"
)
// GetUserDetails returns the details of a chat user for moderators.
func GetUserDetails(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetUserDetails(w http.ResponseWriter, r *http.Request) {
type connectedClient struct {
ConnectedAt time.Time `json:"connectedAt"`
UserAgent string `json:"userAgent"`
@ -68,6 +68,6 @@ func GetUserDetails(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(res); err != nil {
controllers.InternalErrorHandler(w, err)
responses.InternalErrorHandler(w, err)
}
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"encoding/json"
@ -6,6 +6,7 @@ import (
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/notifications"
"github.com/owncast/owncast/webserver/responses"
"github.com/owncast/owncast/utils"
@ -14,9 +15,9 @@ import (
// RegisterForLiveNotifications will register a channel + destination to be
// notified when a stream goes live.
func RegisterForLiveNotifications(u user.User, w http.ResponseWriter, r *http.Request) {
if r.Method != POST {
WriteSimpleResponse(w, false, r.Method+" not supported")
func (h *Handlers) RegisterForLiveNotifications(u user.User, w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
responses.WriteSimpleResponse(w, false, r.Method+" not supported")
return
}
@ -31,7 +32,7 @@ func RegisterForLiveNotifications(u user.User, w http.ResponseWriter, r *http.Re
var req request
if err := decoder.Decode(&req); err != nil {
log.Errorln(err)
WriteSimpleResponse(w, false, "unable to register for notifications")
responses.WriteSimpleResponse(w, false, "unable to register for notifications")
return
}
@ -39,13 +40,13 @@ func RegisterForLiveNotifications(u user.User, w http.ResponseWriter, r *http.Re
validTypes := []string{notifications.BrowserPushNotification}
_, validChannel := utils.FindInSlice(validTypes, req.Channel)
if !validChannel {
WriteSimpleResponse(w, false, "invalid notification channel: "+req.Channel)
responses.WriteSimpleResponse(w, false, "invalid notification channel: "+req.Channel)
return
}
if err := notifications.AddNotification(req.Channel, req.Destination); err != nil {
log.Errorln(err)
WriteSimpleResponse(w, false, "unable to save notification")
responses.WriteSimpleResponse(w, false, "unable to save notification")
return
}
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"net/http"
@ -8,7 +8,7 @@ import (
)
// Ping is fired by a client to show they are still an active viewer.
func Ping(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) Ping(w http.ResponseWriter, r *http.Request) {
viewer := models.GenerateViewerFromRequest(r)
core.SetViewerActive(&viewer)
w.WriteHeader(http.StatusOK)

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"encoding/json"
@ -6,14 +6,15 @@ import (
"github.com/owncast/owncast/metrics"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/responses"
log "github.com/sirupsen/logrus"
)
// ReportPlaybackMetrics will accept playback metrics from a client and save
// them for future video health reporting.
func ReportPlaybackMetrics(w http.ResponseWriter, r *http.Request) {
if r.Method != POST {
WriteSimpleResponse(w, false, r.Method+" not supported")
func (h *Handlers) ReportPlaybackMetrics(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
responses.WriteSimpleResponse(w, false, r.Method+" not supported")
return
}
@ -29,7 +30,7 @@ func ReportPlaybackMetrics(w http.ResponseWriter, r *http.Request) {
var request reportPlaybackMetricsRequest
if err := decoder.Decode(&request); err != nil {
log.Errorln("error decoding playback metrics payload:", err)
WriteSimpleResponse(w, false, err.Error())
responses.WriteSimpleResponse(w, false, err.Error())
return
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"encoding/json"
@ -9,10 +9,11 @@ import (
"github.com/owncast/owncast/activitypub/webfinger"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/webserver/responses"
)
// RemoteFollow handles a request to begin the remote follow redirect flow.
func RemoteFollow(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) RemoteFollow(w http.ResponseWriter, r *http.Request) {
type followRequest struct {
Account string `json:"account"`
}
@ -24,12 +25,12 @@ func RemoteFollow(w http.ResponseWriter, r *http.Request) {
var request followRequest
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&request); err != nil {
WriteSimpleResponse(w, false, "unable to parse request")
responses.WriteSimpleResponse(w, false, "unable to parse request")
return
}
if request.Account == "" {
WriteSimpleResponse(w, false, "Remote Fediverse account is required to follow.")
responses.WriteSimpleResponse(w, false, "Remote Fediverse account is required to follow.")
return
}
@ -38,7 +39,7 @@ func RemoteFollow(w http.ResponseWriter, r *http.Request) {
var template string
links, err := webfinger.GetWebfingerLinks(request.Account)
if err != nil {
WriteSimpleResponse(w, false, err.Error())
responses.WriteSimpleResponse(w, false, err.Error())
return
}
@ -52,7 +53,7 @@ func RemoteFollow(w http.ResponseWriter, r *http.Request) {
}
if localActorPath.String() == "" || template == "" {
WriteSimpleResponse(w, false, "unable to determine remote follow information for "+request.Account)
responses.WriteSimpleResponse(w, false, "unable to determine remote follow information for "+request.Account)
return
}
@ -61,5 +62,5 @@ func RemoteFollow(w http.ResponseWriter, r *http.Request) {
RedirectURL: redirectURL,
}
WriteResponse(w, response)
responses.WriteResponse(w, response)
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"net/http"
@ -8,7 +8,7 @@ import (
)
// GetRobotsDotTxt returns the contents of our robots.txt.
func GetRobotsDotTxt(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetRobotsDotTxt(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
contents := []string{
"User-agent: *",

View file

@ -1 +0,0 @@
package handlers

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"encoding/json"
@ -9,17 +9,18 @@ import (
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/utils"
"github.com/owncast/owncast/webserver/middleware"
"github.com/owncast/owncast/webserver/responses"
)
// GetStatus gets the status of the server.
func GetStatus(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetStatus(w http.ResponseWriter, r *http.Request) {
response := getStatusResponse()
w.Header().Set("Content-Type", "application/json")
middleware.DisableCache(w)
if err := json.NewEncoder(w).Encode(response); err != nil {
InternalErrorHandler(w, err)
responses.InternalErrorHandler(w, err)
}
}

View file

@ -1,7 +0,0 @@
package handlers
import "net/http"
func (s *Handlers) HandleTesting(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("testing"))
}

View file

@ -1,10 +1,11 @@
package controllers
package handlers
import (
"net/http"
"sort"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/webserver/responses"
)
type variantsSort struct {
@ -20,7 +21,7 @@ type variantsResponse struct {
}
// GetVideoStreamOutputVariants will return the video variants available.
func GetVideoStreamOutputVariants(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetVideoStreamOutputVariants(w http.ResponseWriter, r *http.Request) {
outputVariants := data.GetStreamOutputVariants()
streamSortVariants := make([]variantsSort, len(outputVariants))
@ -55,5 +56,5 @@ func GetVideoStreamOutputVariants(w http.ResponseWriter, r *http.Request) {
response[i] = variantResponse
}
WriteResponse(w, response)
responses.WriteResponse(w, response)
}

View file

@ -1,4 +1,4 @@
package controllers
package handlers
import (
"net/http"

View file

@ -1 +0,0 @@
package handlers

View file

@ -1,9 +1,10 @@
package yp
package handlers
import (
"encoding/json"
"net/http"
"github.com/owncast/owncast/core"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/models"
"github.com/owncast/owncast/utils"
@ -27,13 +28,13 @@ type ypDetailsResponse struct {
}
// GetYPResponse gets the status of the server for YP purposes.
func GetYPResponse(w http.ResponseWriter, r *http.Request) {
func (h *Handlers) GetYPResponse(w http.ResponseWriter, r *http.Request) {
if !data.GetDirectoryEnabled() {
w.WriteHeader(http.StatusNotFound)
return
}
status := getStatus()
status := core.GetStatus()
streamTitle := data.GetStreamTitle()

View file

@ -0,0 +1,6 @@
package requests
// ConfigValue is a container object that holds a value, is encoded, and saved to the database.
type ConfigValue struct {
Value interface{} `json:"value"`
}

View file

@ -0,0 +1,50 @@
package requests
import (
"encoding/json"
"net/http"
"reflect"
"github.com/owncast/owncast/webserver/responses"
log "github.com/sirupsen/logrus"
)
func RequirePOST(w http.ResponseWriter, r *http.Request) bool {
if r.Method != http.MethodPost {
responses.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)
responses.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 {
responses.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
}

View file

@ -0,0 +1,17 @@
package responses
import (
"log"
"net/http"
"strconv"
)
func WriteBytesAsImage(data []byte, contentType string, w http.ResponseWriter, cacheSeconds int) {
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Length", strconv.Itoa(len(data)))
w.Header().Set("Cache-Control", "public, max-age="+strconv.Itoa(cacheSeconds))
if _, err := w.Write(data); err != nil {
log.Println("unable to write image.")
}
}

View file

@ -1,4 +1,4 @@
package controllers
package responses
// PaginatedResponse is a structure for returning a total count with results.
type PaginatedResponse struct {

View file

@ -1,4 +1,4 @@
package controllers
package responses
import (
"encoding/json"

View file

@ -5,30 +5,24 @@ import (
"github.com/owncast/owncast/activitypub"
"github.com/owncast/owncast/config"
"github.com/owncast/owncast/controllers"
"github.com/owncast/owncast/controllers/admin"
fediverseauth "github.com/owncast/owncast/controllers/auth/fediverse"
"github.com/owncast/owncast/controllers/auth/indieauth"
"github.com/owncast/owncast/controllers/moderation"
"github.com/owncast/owncast/core/chat"
"github.com/owncast/owncast/core/data"
"github.com/owncast/owncast/core/user"
"github.com/owncast/owncast/utils"
fediverseauth "github.com/owncast/owncast/webserver/handlers/auth/fediverse"
"github.com/owncast/owncast/webserver/handlers/auth/indieauth"
"github.com/owncast/owncast/webserver/middleware"
"github.com/owncast/owncast/yp"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func (s *webServer) setupRoutes() {
s.router.HandleFunc("/test", s.handlers.HandleTesting)
s.setupWebAssetRoutes()
s.setupInternalAPIRoutes()
s.setupAdminAPIRoutes()
s.setupExternalThirdPartyAPIRoutes()
s.setupModerationAPIRoutes()
s.router.HandleFunc("/hls/", controllers.HandleHLSRequest)
s.router.HandleFunc("/hls/", s.handlers.HandleHLSRequest)
// websocket
s.router.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
@ -45,7 +39,7 @@ func (s *webServer) setupRoutes() {
// Redirect /embed/chat
http.Redirect(w, r, "/embed/chat/readonly", http.StatusTemporaryRedirect)
} else {
controllers.IndexHandler(w, r)
s.handlers.IndexHandler(w, r)
// s.ServeHTTP(w, r)
}
})
@ -56,26 +50,26 @@ func (s *webServer) setupRoutes() {
func (s *webServer) setupWebAssetRoutes() {
// The admin web app.
s.router.HandleFunc("/admin/", middleware.RequireAdminAuth(controllers.IndexHandler))
s.router.HandleFunc("/admin/", middleware.RequireAdminAuth(s.handlers.IndexHandler))
// Images
s.router.HandleFunc("/thumbnail.jpg", controllers.GetThumbnail)
s.router.HandleFunc("/preview.gif", controllers.GetPreview)
s.router.HandleFunc("/logo", controllers.GetLogo)
s.router.HandleFunc("/thumbnail.jpg", s.handlers.GetThumbnail)
s.router.HandleFunc("/preview.gif", s.handlers.GetPreview)
s.router.HandleFunc("/logo", s.handlers.GetLogo)
// Custom Javascript
s.router.HandleFunc("/customjavascript", controllers.ServeCustomJavascript)
s.router.HandleFunc("/customjavascript", s.handlers.ServeCustomJavascript)
// Return a single emoji image.
s.router.HandleFunc(config.EmojiDir, controllers.GetCustomEmojiImage)
s.router.HandleFunc(config.EmojiDir, s.handlers.GetCustomEmojiImage)
// return the logo
// return a logo that's compatible with external social networks
s.router.HandleFunc("/logo/external", controllers.GetCompatibleLogo)
s.router.HandleFunc("/logo/external", s.handlers.GetCompatibleLogo)
// robots.txt
s.router.HandleFunc("/robots.txt", controllers.GetRobotsDotTxt)
s.router.HandleFunc("/robots.txt", s.handlers.GetRobotsDotTxt)
// Optional public static files
s.router.Handle("/public/", http.StripPrefix("/public/", http.FileServer(http.Dir(config.PublicFilesPath))))
@ -85,289 +79,289 @@ func (s *webServer) setupInternalAPIRoutes() {
// Internal APIs
// status of the system
s.router.HandleFunc("/api/status", controllers.GetStatus)
s.router.HandleFunc("/api/status", s.handlers.GetStatus)
// custom emoji supported in the chat
s.router.HandleFunc("/api/emoji", controllers.GetCustomEmojiList)
s.router.HandleFunc("/api/emoji", s.handlers.GetCustomEmojiList)
// chat history api
s.router.HandleFunc("/api/chat", middleware.RequireUserAccessToken(controllers.GetChatMessages))
s.router.HandleFunc("/api/chat", middleware.RequireUserAccessToken(s.handlers.GetChatMessages))
// web config api
s.router.HandleFunc("/api/config", controllers.GetWebConfig)
s.router.HandleFunc("/api/config", s.handlers.GetWebConfig)
// return the YP protocol data
s.router.HandleFunc("/api/yp", yp.GetYPResponse)
s.router.HandleFunc("/api/yp", s.handlers.GetYPResponse)
// list of all social platforms
s.router.HandleFunc("/api/socialplatforms", controllers.GetAllSocialPlatforms)
s.router.HandleFunc("/api/socialplatforms", s.handlers.GetAllSocialPlatforms)
// return the list of video variants available
s.router.HandleFunc("/api/video/variants", controllers.GetVideoStreamOutputVariants)
s.router.HandleFunc("/api/video/variants", s.handlers.GetVideoStreamOutputVariants)
// tell the backend you're an active viewer
s.router.HandleFunc("/api/ping", controllers.Ping)
s.router.HandleFunc("/api/ping", s.handlers.Ping)
// register a new chat user
s.router.HandleFunc("/api/chat/register", controllers.RegisterAnonymousChatUser)
s.router.HandleFunc("/api/chat/register", s.handlers.RegisterAnonymousChatUser)
// return remote follow details
s.router.HandleFunc("/api/remotefollow", controllers.RemoteFollow)
s.router.HandleFunc("/api/remotefollow", s.handlers.RemoteFollow)
// return followers
s.router.HandleFunc("/api/followers", middleware.HandlePagination(controllers.GetFollowers))
s.router.HandleFunc("/api/followers", middleware.HandlePagination(s.handlers.GetFollowers))
// save client video playback metrics
s.router.HandleFunc("/api/metrics/playback", controllers.ReportPlaybackMetrics)
s.router.HandleFunc("/api/metrics/playback", s.handlers.ReportPlaybackMetrics)
// Register for notifications
s.router.HandleFunc("/api/notifications/register", middleware.RequireUserAccessToken(controllers.RegisterForLiveNotifications))
s.router.HandleFunc("/api/notifications/register", middleware.RequireUserAccessToken(s.handlers.RegisterForLiveNotifications))
// Start auth flow
http.HandleFunc("/api/auth/indieauth", middleware.RequireUserAccessToken(indieauth.StartAuthFlow))
http.HandleFunc("/api/auth/indieauth/callback", indieauth.HandleRedirect)
http.HandleFunc("/api/auth/provider/indieauth", indieauth.HandleAuthEndpoint)
s.router.HandleFunc("/api/auth/indieauth", middleware.RequireUserAccessToken(indieauth.StartAuthFlow))
s.router.HandleFunc("/api/auth/indieauth/callback", indieauth.HandleRedirect)
s.router.HandleFunc("/api/auth/provider/indieauth", indieauth.HandleAuthEndpoint)
http.HandleFunc("/api/auth/fediverse", middleware.RequireUserAccessToken(fediverseauth.RegisterFediverseOTPRequest))
http.HandleFunc("/api/auth/fediverse/verify", fediverseauth.VerifyFediverseOTPRequest)
s.router.HandleFunc("/api/auth/fediverse", middleware.RequireUserAccessToken(fediverseauth.RegisterFediverseOTPRequest))
s.router.HandleFunc("/api/auth/fediverse/verify", fediverseauth.VerifyFediverseOTPRequest)
}
func (s *webServer) setupAdminAPIRoutes() {
// Current inbound broadcaster
s.router.HandleFunc("/api/admin/status", middleware.RequireAdminAuth(admin.Status))
s.router.HandleFunc("/api/admin/status", middleware.RequireAdminAuth(s.handlers.GetAdminStatus))
// Disconnect inbound stream
s.router.HandleFunc("/api/admin/disconnect", middleware.RequireAdminAuth(admin.DisconnectInboundConnection))
s.router.HandleFunc("/api/admin/disconnect", middleware.RequireAdminAuth(s.handlers.DisconnectInboundConnection))
// Server config
s.router.HandleFunc("/api/admin/serverconfig", middleware.RequireAdminAuth(admin.GetServerConfig))
s.router.HandleFunc("/api/admin/serverconfig", middleware.RequireAdminAuth(s.handlers.GetServerConfig))
// Get viewer count over time
s.router.HandleFunc("/api/admin/viewersOverTime", middleware.RequireAdminAuth(admin.GetViewersOverTime))
s.router.HandleFunc("/api/admin/viewersOverTime", middleware.RequireAdminAuth(s.handlers.GetViewersOverTime))
// Get active viewers
s.router.HandleFunc("/api/admin/viewers", middleware.RequireAdminAuth(admin.GetActiveViewers))
s.router.HandleFunc("/api/admin/viewers", middleware.RequireAdminAuth(s.handlers.GetActiveViewers))
// Get hardware stats
s.router.HandleFunc("/api/admin/hardwarestats", middleware.RequireAdminAuth(admin.GetHardwareStats))
s.router.HandleFunc("/api/admin/hardwarestats", middleware.RequireAdminAuth(s.handlers.GetHardwareStats))
// Get a a detailed list of currently connected chat clients
s.router.HandleFunc("/api/admin/chat/clients", middleware.RequireAdminAuth(admin.GetConnectedChatClients))
s.router.HandleFunc("/api/admin/chat/clients", middleware.RequireAdminAuth(s.handlers.GetConnectedChatClients))
// Get all logs
s.router.HandleFunc("/api/admin/logs", middleware.RequireAdminAuth(admin.GetLogs))
s.router.HandleFunc("/api/admin/logs", middleware.RequireAdminAuth(s.handlers.GetLogs))
// Get warning/error logs
s.router.HandleFunc("/api/admin/logs/warnings", middleware.RequireAdminAuth(admin.GetWarnings))
s.router.HandleFunc("/api/admin/logs/warnings", middleware.RequireAdminAuth(s.handlers.GetWarnings))
// Get all chat messages for the admin, unfiltered.
s.router.HandleFunc("/api/admin/chat/messages", middleware.RequireAdminAuth(admin.GetChatMessages))
s.router.HandleFunc("/api/admin/chat/messages", middleware.RequireAdminAuth(s.handlers.GetAdminChatMessages))
// Update chat message visibility
s.router.HandleFunc("/api/admin/chat/messagevisibility", middleware.RequireAdminAuth(admin.UpdateMessageVisibility))
s.router.HandleFunc("/api/admin/chat/messagevisibility", middleware.RequireAdminAuth(s.handlers.UpdateMessageVisibility))
// Enable/disable a user
s.router.HandleFunc("/api/admin/chat/users/setenabled", middleware.RequireAdminAuth(admin.UpdateUserEnabled))
s.router.HandleFunc("/api/admin/chat/users/setenabled", middleware.RequireAdminAuth(s.handlers.UpdateUserEnabled))
// Ban/unban an IP address
s.router.HandleFunc("/api/admin/chat/users/ipbans/create", middleware.RequireAdminAuth(admin.BanIPAddress))
s.router.HandleFunc("/api/admin/chat/users/ipbans/create", middleware.RequireAdminAuth(s.handlers.BanIPAddress))
// Remove an IP address ban
s.router.HandleFunc("/api/admin/chat/users/ipbans/remove", middleware.RequireAdminAuth(admin.UnBanIPAddress))
s.router.HandleFunc("/api/admin/chat/users/ipbans/remove", middleware.RequireAdminAuth(s.handlers.UnBanIPAddress))
// Return all the banned IP addresses
s.router.HandleFunc("/api/admin/chat/users/ipbans", middleware.RequireAdminAuth(admin.GetIPAddressBans))
s.router.HandleFunc("/api/admin/chat/users/ipbans", middleware.RequireAdminAuth(s.handlers.GetIPAddressBans))
// Get a list of disabled users
s.router.HandleFunc("/api/admin/chat/users/disabled", middleware.RequireAdminAuth(admin.GetDisabledUsers))
s.router.HandleFunc("/api/admin/chat/users/disabled", middleware.RequireAdminAuth(s.handlers.GetDisabledUsers))
// Set moderator status for a user
s.router.HandleFunc("/api/admin/chat/users/setmoderator", middleware.RequireAdminAuth(admin.UpdateUserModerator))
s.router.HandleFunc("/api/admin/chat/users/setmoderator", middleware.RequireAdminAuth(s.handlers.UpdateUserModerator))
// Get a list of moderator users
s.router.HandleFunc("/api/admin/chat/users/moderators", middleware.RequireAdminAuth(admin.GetModerators))
s.router.HandleFunc("/api/admin/chat/users/moderators", middleware.RequireAdminAuth(s.handlers.GetModerators))
// return followers
s.router.HandleFunc("/api/admin/followers", middleware.RequireAdminAuth(middleware.HandlePagination(controllers.GetFollowers)))
s.router.HandleFunc("/api/admin/followers", middleware.RequireAdminAuth(middleware.HandlePagination(s.handlers.GetFollowers)))
// Get a list of pending follow requests
s.router.HandleFunc("/api/admin/followers/pending", middleware.RequireAdminAuth(admin.GetPendingFollowRequests))
s.router.HandleFunc("/api/admin/followers/pending", middleware.RequireAdminAuth(s.handlers.GetPendingFollowRequests))
// Get a list of rejected or blocked follows
s.router.HandleFunc("/api/admin/followers/blocked", middleware.RequireAdminAuth(admin.GetBlockedAndRejectedFollowers))
s.router.HandleFunc("/api/admin/followers/blocked", middleware.RequireAdminAuth(s.handlers.GetBlockedAndRejectedFollowers))
// Set the following state of a follower or follow request.
s.router.HandleFunc("/api/admin/followers/approve", middleware.RequireAdminAuth(admin.ApproveFollower))
s.router.HandleFunc("/api/admin/followers/approve", middleware.RequireAdminAuth(s.handlers.ApproveFollower))
// Upload custom emoji
s.router.HandleFunc("/api/admin/emoji/upload", middleware.RequireAdminAuth(admin.UploadCustomEmoji))
s.router.HandleFunc("/api/admin/emoji/upload", middleware.RequireAdminAuth(s.handlers.UploadCustomEmoji))
// Delete custom emoji
s.router.HandleFunc("/api/admin/emoji/delete", middleware.RequireAdminAuth(admin.DeleteCustomEmoji))
s.router.HandleFunc("/api/admin/emoji/delete", middleware.RequireAdminAuth(s.handlers.DeleteCustomEmoji))
// Update config values
// Change the current streaming key in memory
s.router.HandleFunc("/api/admin/config/adminpass", middleware.RequireAdminAuth(admin.SetAdminPassword))
s.router.HandleFunc("/api/admin/config/adminpass", middleware.RequireAdminAuth(s.handlers.SetAdminPassword))
// Set an array of valid stream keys
s.router.HandleFunc("/api/admin/config/streamkeys", middleware.RequireAdminAuth(admin.SetStreamKeys))
s.router.HandleFunc("/api/admin/config/streamkeys", middleware.RequireAdminAuth(s.handlers.SetStreamKeys))
// Change the extra page content in memory
s.router.HandleFunc("/api/admin/config/pagecontent", middleware.RequireAdminAuth(admin.SetExtraPageContent))
s.router.HandleFunc("/api/admin/config/pagecontent", middleware.RequireAdminAuth(s.handlers.SetExtraPageContent))
// Stream title
s.router.HandleFunc("/api/admin/config/streamtitle", middleware.RequireAdminAuth(admin.SetStreamTitle))
s.router.HandleFunc("/api/admin/config/streamtitle", middleware.RequireAdminAuth(s.handlers.SetStreamTitle))
// Server name
s.router.HandleFunc("/api/admin/config/name", middleware.RequireAdminAuth(admin.SetServerName))
s.router.HandleFunc("/api/admin/config/name", middleware.RequireAdminAuth(s.handlers.SetServerName))
// Server summary
s.router.HandleFunc("/api/admin/config/serversummary", middleware.RequireAdminAuth(admin.SetServerSummary))
s.router.HandleFunc("/api/admin/config/serversummary", middleware.RequireAdminAuth(s.handlers.SetServerSummary))
// Offline message
s.router.HandleFunc("/api/admin/config/offlinemessage", middleware.RequireAdminAuth(admin.SetCustomOfflineMessage))
s.router.HandleFunc("/api/admin/config/offlinemessage", middleware.RequireAdminAuth(s.handlers.SetCustomOfflineMessage))
// Server welcome message
s.router.HandleFunc("/api/admin/config/welcomemessage", middleware.RequireAdminAuth(admin.SetServerWelcomeMessage))
s.router.HandleFunc("/api/admin/config/welcomemessage", middleware.RequireAdminAuth(s.handlers.SetServerWelcomeMessage))
// Disable chat
s.router.HandleFunc("/api/admin/config/chat/disable", middleware.RequireAdminAuth(admin.SetChatDisabled))
s.router.HandleFunc("/api/admin/config/chat/disable", middleware.RequireAdminAuth(s.handlers.SetChatDisabled))
// Disable chat user join messages
s.router.HandleFunc("/api/admin/config/chat/joinmessagesenabled", middleware.RequireAdminAuth(admin.SetChatJoinMessagesEnabled))
s.router.HandleFunc("/api/admin/config/chat/joinmessagesenabled", middleware.RequireAdminAuth(s.handlers.SetChatJoinMessagesEnabled))
// Enable/disable chat established user mode
s.router.HandleFunc("/api/admin/config/chat/establishedusermode", middleware.RequireAdminAuth(admin.SetEnableEstablishedChatUserMode))
s.router.HandleFunc("/api/admin/config/chat/establishedusermode", middleware.RequireAdminAuth(s.handlers.SetEnableEstablishedChatUserMode))
// Set chat usernames that are not allowed
s.router.HandleFunc("/api/admin/config/chat/forbiddenusernames", middleware.RequireAdminAuth(admin.SetForbiddenUsernameList))
s.router.HandleFunc("/api/admin/config/chat/forbiddenusernames", middleware.RequireAdminAuth(s.handlers.SetForbiddenUsernameList))
// Set the suggested chat usernames that will be assigned automatically
s.router.HandleFunc("/api/admin/config/chat/suggestedusernames", middleware.RequireAdminAuth(admin.SetSuggestedUsernameList))
s.router.HandleFunc("/api/admin/config/chat/suggestedusernames", middleware.RequireAdminAuth(s.handlers.SetSuggestedUsernameList))
// Set video codec
s.router.HandleFunc("/api/admin/config/video/codec", middleware.RequireAdminAuth(admin.SetVideoCodec))
s.router.HandleFunc("/api/admin/config/video/codec", middleware.RequireAdminAuth(s.handlers.SetVideoCodec))
// Set style/color/css values
s.router.HandleFunc("/api/admin/config/appearance", middleware.RequireAdminAuth(admin.SetCustomColorVariableValues))
s.router.HandleFunc("/api/admin/config/appearance", middleware.RequireAdminAuth(s.handlers.SetCustomColorVariableValues))
// Return all webhooks
s.router.HandleFunc("/api/admin/webhooks", middleware.RequireAdminAuth(admin.GetWebhooks))
s.router.HandleFunc("/api/admin/webhooks", middleware.RequireAdminAuth(s.handlers.GetWebhooks))
// Delete a single webhook
s.router.HandleFunc("/api/admin/webhooks/delete", middleware.RequireAdminAuth(admin.DeleteWebhook))
s.router.HandleFunc("/api/admin/webhooks/delete", middleware.RequireAdminAuth(s.handlers.DeleteWebhook))
// Create a single webhook
s.router.HandleFunc("/api/admin/webhooks/create", middleware.RequireAdminAuth(admin.CreateWebhook))
s.router.HandleFunc("/api/admin/webhooks/create", middleware.RequireAdminAuth(s.handlers.CreateWebhook))
// Get all access tokens
s.router.HandleFunc("/api/admin/accesstokens", middleware.RequireAdminAuth(admin.GetExternalAPIUsers))
s.router.HandleFunc("/api/admin/accesstokens", middleware.RequireAdminAuth(s.handlers.GetExternalAPIUsers))
// Delete a single access token
s.router.HandleFunc("/api/admin/accesstokens/delete", middleware.RequireAdminAuth(admin.DeleteExternalAPIUser))
s.router.HandleFunc("/api/admin/accesstokens/delete", middleware.RequireAdminAuth(s.handlers.DeleteExternalAPIUser))
// Create a single access token
s.router.HandleFunc("/api/admin/accesstokens/create", middleware.RequireAdminAuth(admin.CreateExternalAPIUser))
s.router.HandleFunc("/api/admin/accesstokens/create", middleware.RequireAdminAuth(s.handlers.CreateExternalAPIUser))
// Return the auto-update features that are supported for this instance.
s.router.HandleFunc("/api/admin/update/options", middleware.RequireAdminAuth(admin.AutoUpdateOptions))
s.router.HandleFunc("/api/admin/update/options", middleware.RequireAdminAuth(s.handlers.AutoUpdateOptions))
// Begin the auto update
s.router.HandleFunc("/api/admin/update/start", middleware.RequireAdminAuth(admin.AutoUpdateStart))
s.router.HandleFunc("/api/admin/update/start", middleware.RequireAdminAuth(s.handlers.AutoUpdateStart))
// Force quit the service to restart it
s.router.HandleFunc("/api/admin/update/forcequit", middleware.RequireAdminAuth(admin.AutoUpdateForceQuit))
s.router.HandleFunc("/api/admin/update/forcequit", middleware.RequireAdminAuth(s.handlers.AutoUpdateForceQuit))
// Logo path
s.router.HandleFunc("/api/admin/config/logo", middleware.RequireAdminAuth(admin.SetLogo))
s.router.HandleFunc("/api/admin/config/logo", middleware.RequireAdminAuth(s.handlers.SetLogo))
// Server tags
s.router.HandleFunc("/api/admin/config/tags", middleware.RequireAdminAuth(admin.SetTags))
s.router.HandleFunc("/api/admin/config/tags", middleware.RequireAdminAuth(s.handlers.SetTags))
// ffmpeg
s.router.HandleFunc("/api/admin/config/ffmpegpath", middleware.RequireAdminAuth(admin.SetFfmpegPath))
s.router.HandleFunc("/api/admin/config/ffmpegpath", middleware.RequireAdminAuth(s.handlers.SetFfmpegPath))
// Server http port
s.router.HandleFunc("/api/admin/config/webserverport", middleware.RequireAdminAuth(admin.SetWebServerPort))
s.router.HandleFunc("/api/admin/config/webserverport", middleware.RequireAdminAuth(s.handlers.SetWebServerPort))
// Server http listen address
s.router.HandleFunc("/api/admin/config/webserverip", middleware.RequireAdminAuth(admin.SetWebServerIP))
s.router.HandleFunc("/api/admin/config/webserverip", middleware.RequireAdminAuth(s.handlers.SetWebServerIP))
// Server rtmp port
s.router.HandleFunc("/api/admin/config/rtmpserverport", middleware.RequireAdminAuth(admin.SetRTMPServerPort))
s.router.HandleFunc("/api/admin/config/rtmpserverport", middleware.RequireAdminAuth(s.handlers.SetRTMPServerPort))
// Websocket host override
s.router.HandleFunc("/api/admin/config/sockethostoverride", middleware.RequireAdminAuth(admin.SetSocketHostOverride))
s.router.HandleFunc("/api/admin/config/sockethostoverride", middleware.RequireAdminAuth(s.handlers.SetSocketHostOverride))
// Custom video serving endpoint
s.router.HandleFunc("/api/admin/config/videoservingendpoint", middleware.RequireAdminAuth(admin.SetVideoServingEndpoint))
s.router.HandleFunc("/api/admin/config/videoservingendpoint", middleware.RequireAdminAuth(s.handlers.SetVideoServingEndpoint))
// Is server marked as NSFW
s.router.HandleFunc("/api/admin/config/nsfw", middleware.RequireAdminAuth(admin.SetNSFW))
s.router.HandleFunc("/api/admin/config/nsfw", middleware.RequireAdminAuth(s.handlers.SetNSFW))
// directory enabled
s.router.HandleFunc("/api/admin/config/directoryenabled", middleware.RequireAdminAuth(admin.SetDirectoryEnabled))
s.router.HandleFunc("/api/admin/config/directoryenabled", middleware.RequireAdminAuth(s.handlers.SetDirectoryEnabled))
// social handles
s.router.HandleFunc("/api/admin/config/socialhandles", middleware.RequireAdminAuth(admin.SetSocialHandles))
s.router.HandleFunc("/api/admin/config/socialhandles", middleware.RequireAdminAuth(s.handlers.SetSocialHandles))
// set the number of video segments and duration per segment in a playlist
s.router.HandleFunc("/api/admin/config/video/streamlatencylevel", middleware.RequireAdminAuth(admin.SetStreamLatencyLevel))
s.router.HandleFunc("/api/admin/config/video/streamlatencylevel", middleware.RequireAdminAuth(s.handlers.SetStreamLatencyLevel))
// set an array of video output configurations
s.router.HandleFunc("/api/admin/config/video/streamoutputvariants", middleware.RequireAdminAuth(admin.SetStreamOutputVariants))
s.router.HandleFunc("/api/admin/config/video/streamoutputvariants", middleware.RequireAdminAuth(s.handlers.SetStreamOutputVariants))
// set s3 configuration
s.router.HandleFunc("/api/admin/config/s3", middleware.RequireAdminAuth(admin.SetS3Configuration))
s.router.HandleFunc("/api/admin/config/s3", middleware.RequireAdminAuth(s.handlers.SetS3Configuration))
// set server url
s.router.HandleFunc("/api/admin/config/serverurl", middleware.RequireAdminAuth(admin.SetServerURL))
s.router.HandleFunc("/api/admin/config/serverurl", middleware.RequireAdminAuth(s.handlers.SetServerURL))
// reset the YP registration
s.router.HandleFunc("/api/admin/yp/reset", middleware.RequireAdminAuth(admin.ResetYPRegistration))
s.router.HandleFunc("/api/admin/yp/reset", middleware.RequireAdminAuth(s.handlers.ResetYPRegistration))
// set external action links
s.router.HandleFunc("/api/admin/config/externalactions", middleware.RequireAdminAuth(admin.SetExternalActions))
s.router.HandleFunc("/api/admin/config/externalactions", middleware.RequireAdminAuth(s.handlers.SetExternalActions))
// set custom style css
s.router.HandleFunc("/api/admin/config/customstyles", middleware.RequireAdminAuth(admin.SetCustomStyles))
s.router.HandleFunc("/api/admin/config/customstyles", middleware.RequireAdminAuth(s.handlers.SetCustomStyles))
// set custom style javascript
s.router.HandleFunc("/api/admin/config/customjavascript", middleware.RequireAdminAuth(admin.SetCustomJavascript))
s.router.HandleFunc("/api/admin/config/customjavascript", middleware.RequireAdminAuth(s.handlers.SetCustomJavascript))
// Video playback metrics
s.router.HandleFunc("/api/admin/metrics/video", middleware.RequireAdminAuth(admin.GetVideoPlaybackMetrics))
s.router.HandleFunc("/api/admin/metrics/video", middleware.RequireAdminAuth(s.handlers.GetVideoPlaybackMetrics))
// Is the viewer count hidden from viewers
s.router.HandleFunc("/api/admin/config/hideviewercount", middleware.RequireAdminAuth(admin.SetHideViewerCount))
s.router.HandleFunc("/api/admin/config/hideviewercount", middleware.RequireAdminAuth(s.handlers.SetHideViewerCount))
// set disabling of search indexing
s.router.HandleFunc("/api/admin/config/disablesearchindexing", middleware.RequireAdminAuth(admin.SetDisableSearchIndexing))
s.router.HandleFunc("/api/admin/config/disablesearchindexing", middleware.RequireAdminAuth(s.handlers.SetDisableSearchIndexing))
// enable/disable federation features
s.router.HandleFunc("/api/admin/config/federation/enable", middleware.RequireAdminAuth(admin.SetFederationEnabled))
s.router.HandleFunc("/api/admin/config/federation/enable", middleware.RequireAdminAuth(s.handlers.SetFederationEnabled))
// set if federation activities are private
s.router.HandleFunc("/api/admin/config/federation/private", middleware.RequireAdminAuth(admin.SetFederationActivityPrivate))
s.router.HandleFunc("/api/admin/config/federation/private", middleware.RequireAdminAuth(s.handlers.SetFederationActivityPrivate))
// set if fediverse engagement appears in chat
s.router.HandleFunc("/api/admin/config/federation/showengagement", middleware.RequireAdminAuth(admin.SetFederationShowEngagement))
s.router.HandleFunc("/api/admin/config/federation/showengagement", middleware.RequireAdminAuth(s.handlers.SetFederationShowEngagement))
// set local federated username
s.router.HandleFunc("/api/admin/config/federation/username", middleware.RequireAdminAuth(admin.SetFederationUsername))
s.router.HandleFunc("/api/admin/config/federation/username", middleware.RequireAdminAuth(s.handlers.SetFederationUsername))
// set federated go live message
s.router.HandleFunc("/api/admin/config/federation/livemessage", middleware.RequireAdminAuth(admin.SetFederationGoLiveMessage))
s.router.HandleFunc("/api/admin/config/federation/livemessage", middleware.RequireAdminAuth(s.handlers.SetFederationGoLiveMessage))
// Federation blocked domains
s.router.HandleFunc("/api/admin/config/federation/blockdomains", middleware.RequireAdminAuth(admin.SetFederationBlockDomains))
s.router.HandleFunc("/api/admin/config/federation/blockdomains", middleware.RequireAdminAuth(s.handlers.SetFederationBlockDomains))
// send a public message to the Fediverse from the server's user
s.router.HandleFunc("/api/admin/federation/send", middleware.RequireAdminAuth(admin.SendFederatedMessage))
s.router.HandleFunc("/api/admin/federation/send", middleware.RequireAdminAuth(s.handlers.SendFederatedMessage))
// Return federated activities
s.router.HandleFunc("/api/admin/federation/actions", middleware.RequireAdminAuth(middleware.HandlePagination(admin.GetFederatedActions)))
s.router.HandleFunc("/api/admin/federation/actions", middleware.RequireAdminAuth(middleware.HandlePagination(s.handlers.GetFederatedActions)))
// Prometheus metrics
s.router.Handle("/api/admin/prometheus", middleware.RequireAdminAuth(func(rw http.ResponseWriter, r *http.Request) {
@ -375,46 +369,46 @@ func (s *webServer) setupAdminAPIRoutes() {
}))
// Configure outbound notification channels.
http.HandleFunc("/api/admin/config/notifications/discord", middleware.RequireAdminAuth(admin.SetDiscordNotificationConfiguration))
http.HandleFunc("/api/admin/config/notifications/browser", middleware.RequireAdminAuth(admin.SetBrowserNotificationConfiguration))
s.router.HandleFunc("/api/admin/config/notifications/discord", middleware.RequireAdminAuth(s.handlers.SetDiscordNotificationConfiguration))
s.router.HandleFunc("/api/admin/config/notifications/browser", middleware.RequireAdminAuth(s.handlers.SetBrowserNotificationConfiguration))
}
func (s *webServer) setupExternalThirdPartyAPIRoutes() {
// Send a system message to chat
s.router.HandleFunc("/api/integrations/chat/system", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, admin.SendSystemMessage))
s.router.HandleFunc("/api/integrations/chat/system", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, s.handlers.SendSystemMessage))
// Send a system message to a single client
s.router.HandleFunc(utils.RestEndpoint("/api/integrations/chat/system/client/{clientId}", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, admin.SendSystemMessageToConnectedClient)))
s.router.HandleFunc(utils.RestEndpoint("/api/integrations/chat/system/client/{clientId}", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, s.handlers.SendSystemMessageToConnectedClient)))
// Send a user message to chat *NO LONGER SUPPORTED
s.router.HandleFunc("/api/integrations/chat/user", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, admin.SendUserMessage))
s.router.HandleFunc("/api/integrations/chat/user", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, s.handlers.SendUserMessage))
// Send a message to chat as a specific 3rd party bot/integration based on its access token
s.router.HandleFunc("/api/integrations/chat/send", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, admin.SendIntegrationChatMessage))
s.router.HandleFunc("/api/integrations/chat/send", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendChatMessages, s.handlers.SendIntegrationChatMessage))
// Send a user action to chat
s.router.HandleFunc("/api/integrations/chat/action", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, admin.SendChatAction))
s.router.HandleFunc("/api/integrations/chat/action", middleware.RequireExternalAPIAccessToken(user.ScopeCanSendSystemMessages, s.handlers.SendChatAction))
// Hide chat message
s.router.HandleFunc("/api/integrations/chat/messagevisibility", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, admin.ExternalUpdateMessageVisibility))
s.router.HandleFunc("/api/integrations/chat/messagevisibility", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalUpdateMessageVisibility))
// Stream title
s.router.HandleFunc("/api/integrations/streamtitle", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, admin.ExternalSetStreamTitle))
s.router.HandleFunc("/api/integrations/streamtitle", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalSetStreamTitle))
// Get chat history
s.router.HandleFunc("/api/integrations/chat", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, controllers.ExternalGetChatMessages))
s.router.HandleFunc("/api/integrations/chat", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalGetChatMessages))
// Connected clients
s.router.HandleFunc("/api/integrations/clients", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, admin.ExternalGetConnectedChatClients))
s.router.HandleFunc("/api/integrations/clients", middleware.RequireExternalAPIAccessToken(user.ScopeHasAdminAccess, s.handlers.ExternalGetConnectedChatClients))
}
func (s *webServer) setupModerationAPIRoutes() {
// Update chat message visibility
s.router.HandleFunc("/api/chat/messagevisibility", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateMessageVisibility))
s.router.HandleFunc("/api/chat/messagevisibility", middleware.RequireUserModerationScopeAccesstoken(s.handlers.UpdateMessageVisibility))
// Enable/disable a user
s.router.HandleFunc("/api/chat/users/setenabled", middleware.RequireUserModerationScopeAccesstoken(admin.UpdateUserEnabled))
s.router.HandleFunc("/api/chat/users/setenabled", middleware.RequireUserModerationScopeAccesstoken(s.handlers.UpdateUserEnabled))
// Get a user's details
s.router.HandleFunc("/api/moderation/chat/user/", middleware.RequireUserModerationScopeAccesstoken(moderation.GetUserDetails))
s.router.HandleFunc("/api/moderation/chat/user/", middleware.RequireUserModerationScopeAccesstoken(s.handlers.GetUserDetails))
}

View file

@ -33,12 +33,3 @@ func TestPrometheusDebugPath(t *testing.T) {
t.Errorf("Expected 404, got %d", w.Result().StatusCode)
}
}
func TestTestingEndpoint(t *testing.T) {
r := httptest.NewRequest(http.MethodGet, "/test", nil)
w := httptest.NewRecorder()
srv.ServeHTTP(w, r)
if w.Result().StatusCode != http.StatusOK {
t.Errorf("Expected 200, got %d", w.Result().StatusCode)
}
}