64 changed files with 1697 additions and 1732 deletions
@ -1,13 +0,0 @@
@@ -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) |
||||
} |
@ -1,35 +0,0 @@
@@ -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") |
||||
} |
@ -1,880 +0,0 @@
@@ -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") |
||||
} |
@ -1,21 +0,0 @@
@@ -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") |
||||
} |
@ -1,179 +0,0 @@
@@ -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) |
||||
} |
@ -1,60 +0,0 @@
@@ -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") |
||||
} |
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
package controllers |
||||
|
||||
// POST is the HTTP POST method.
|
||||
const POST = "POST" |
||||
|
||||
// GET is the HTTP GET method.
|
||||
const GET = "GET" |
@ -1,55 +0,0 @@
@@ -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) |
||||
} |
@ -0,0 +1,36 @@
@@ -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") |
||||
} |
@ -0,0 +1,814 @@
@@ -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") |
||||
} |
@ -1,25 +1,25 @@
@@ -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) |
||||
} |
@ -1,20 +1,20 @@
@@ -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") |
||||
} |
@ -0,0 +1,180 @@
@@ -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) |
||||
} |
@ -0,0 +1,61 @@
@@ -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") |
||||
} |
@ -0,0 +1,21 @@
@@ -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") |
||||
} |
@ -1,22 +1,23 @@
@@ -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) |
||||
} |
@ -1 +1,55 @@
@@ -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) |
||||
} |
||||
|
@ -1,7 +0,0 @@
@@ -1,7 +0,0 @@
|
||||
package handlers |
||||
|
||||
import "net/http" |
||||
|
||||
func (s *Handlers) HandleTesting(w http.ResponseWriter, r *http.Request) { |
||||
_, _ = w.Write([]byte("testing")) |
||||
} |
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
package controllers |
||||
package handlers |
||||
|
||||
import ( |
||||
"net/http" |
@ -0,0 +1,6 @@
@@ -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"` |
||||
} |
@ -0,0 +1,50 @@
@@ -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 |
||||
} |
@ -0,0 +1,17 @@
@@ -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.") |
||||
} |
||||
} |
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
package controllers |
||||
package responses |
||||
|
||||
// PaginatedResponse is a structure for returning a total count with results.
|
||||
type PaginatedResponse struct { |
@ -1,4 +1,4 @@
@@ -1,4 +1,4 @@
|
||||
package controllers |
||||
package responses |
||||
|
||||
import ( |
||||
"encoding/json" |
Loading…
Reference in new issue