gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

thread.go (4767B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package web
     19 
     20 import (
     21 	"context"
     22 	"encoding/json"
     23 	"errors"
     24 	"fmt"
     25 	"net/http"
     26 	"strings"
     27 
     28 	"github.com/gin-gonic/gin"
     29 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
     30 	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
     31 	"github.com/superseriousbusiness/gotosocial/internal/config"
     32 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
     33 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
     34 )
     35 
     36 func (m *Module) threadGETHandler(c *gin.Context) {
     37 	ctx := c.Request.Context()
     38 
     39 	authed, err := oauth.Authed(c, false, false, false, false)
     40 	if err != nil {
     41 		apiutil.WebErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
     42 		return
     43 	}
     44 
     45 	// usernames on our instance will always be lowercase
     46 	username := strings.ToLower(c.Param(usernameKey))
     47 	if username == "" {
     48 		err := errors.New("no account username specified")
     49 		apiutil.WebErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
     50 		return
     51 	}
     52 
     53 	// status ids will always be uppercase
     54 	statusID := strings.ToUpper(c.Param(statusIDKey))
     55 	if statusID == "" {
     56 		err := errors.New("no status id specified")
     57 		apiutil.WebErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
     58 		return
     59 	}
     60 
     61 	instance, err := m.processor.InstanceGetV1(ctx)
     62 	if err != nil {
     63 		apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1)
     64 		return
     65 	}
     66 
     67 	instanceGet := func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) {
     68 		return instance, nil
     69 	}
     70 
     71 	// do this check to make sure the status is actually from a local account,
     72 	// we shouldn't render threads from statuses that don't belong to us!
     73 	if _, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, username); errWithCode != nil {
     74 		apiutil.WebErrorHandler(c, errWithCode, instanceGet)
     75 		return
     76 	}
     77 
     78 	status, errWithCode := m.processor.Status().Get(ctx, authed.Account, statusID)
     79 	if errWithCode != nil {
     80 		apiutil.WebErrorHandler(c, errWithCode, instanceGet)
     81 		return
     82 	}
     83 
     84 	if !strings.EqualFold(username, status.Account.Username) {
     85 		err := gtserror.NewErrorNotFound(errors.New("path username not equal to status author username"))
     86 		apiutil.WebErrorHandler(c, gtserror.NewErrorNotFound(err), instanceGet)
     87 		return
     88 	}
     89 
     90 	// if we're getting an AP request on this endpoint we
     91 	// should render the status's AP representation instead
     92 	accept := apiutil.NegotiateFormat(c, string(apiutil.TextHTML), string(apiutil.AppActivityJSON), string(apiutil.AppActivityLDJSON))
     93 	if accept == string(apiutil.AppActivityJSON) || accept == string(apiutil.AppActivityLDJSON) {
     94 		m.returnAPStatus(c, username, statusID, accept)
     95 		return
     96 	}
     97 
     98 	context, errWithCode := m.processor.Status().ContextGet(ctx, authed.Account, statusID)
     99 	if errWithCode != nil {
    100 		apiutil.WebErrorHandler(c, errWithCode, instanceGet)
    101 		return
    102 	}
    103 
    104 	stylesheets := []string{
    105 		assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css",
    106 		distPathPrefix + "/status.css",
    107 	}
    108 	if config.GetAccountsAllowCustomCSS() {
    109 		stylesheets = append(stylesheets, "/@"+username+"/custom.css")
    110 	}
    111 
    112 	c.HTML(http.StatusOK, "thread.tmpl", gin.H{
    113 		"instance":    instance,
    114 		"status":      status,
    115 		"context":     context,
    116 		"ogMeta":      ogBase(instance).withStatus(status),
    117 		"stylesheets": stylesheets,
    118 		"javascript":  []string{distPathPrefix + "/frontend.js"},
    119 	})
    120 }
    121 
    122 func (m *Module) returnAPStatus(c *gin.Context, username string, statusID string, accept string) {
    123 	status, errWithCode := m.processor.Fedi().StatusGet(c.Request.Context(), username, statusID)
    124 	if errWithCode != nil {
    125 		apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
    126 		return
    127 	}
    128 
    129 	b, mErr := json.Marshal(status)
    130 	if mErr != nil {
    131 		err := fmt.Errorf("could not marshal json: %s", mErr)
    132 		apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1)
    133 		return
    134 	}
    135 
    136 	c.Data(http.StatusOK, accept, b)
    137 }