gtsocial-umbx

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

profile.go (5268B)


      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 const (
     37 	// MaxStatusIDKey is for specifying the maximum ID of the status to retrieve.
     38 	MaxStatusIDKey = "max_id"
     39 )
     40 
     41 func (m *Module) profileGETHandler(c *gin.Context) {
     42 	ctx := c.Request.Context()
     43 
     44 	authed, err := oauth.Authed(c, false, false, false, false)
     45 	if err != nil {
     46 		apiutil.WebErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
     47 		return
     48 	}
     49 
     50 	username := strings.ToLower(c.Param(usernameKey))
     51 	if username == "" {
     52 		err := errors.New("no account username specified")
     53 		apiutil.WebErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
     54 		return
     55 	}
     56 
     57 	instance, err := m.processor.InstanceGetV1(ctx)
     58 	if err != nil {
     59 		apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1)
     60 		return
     61 	}
     62 
     63 	instanceGet := func(ctx context.Context) (*apimodel.InstanceV1, gtserror.WithCode) {
     64 		return instance, nil
     65 	}
     66 
     67 	account, errWithCode := m.processor.Account().GetLocalByUsername(ctx, authed.Account, username)
     68 	if errWithCode != nil {
     69 		apiutil.WebErrorHandler(c, errWithCode, instanceGet)
     70 		return
     71 	}
     72 
     73 	// if we're getting an AP request on this endpoint we
     74 	// should render the account's AP representation instead
     75 	accept := apiutil.NegotiateFormat(c, string(apiutil.TextHTML), string(apiutil.AppActivityJSON), string(apiutil.AppActivityLDJSON))
     76 	if accept == string(apiutil.AppActivityJSON) || accept == string(apiutil.AppActivityLDJSON) {
     77 		m.returnAPProfile(c, username, accept)
     78 		return
     79 	}
     80 
     81 	var rssFeed string
     82 	if account.EnableRSS {
     83 		rssFeed = "/@" + account.Username + "/feed.rss"
     84 	}
     85 
     86 	// only allow search engines / robots to view this page if account is discoverable
     87 	var robotsMeta string
     88 	if account.Discoverable {
     89 		robotsMeta = robotsMetaAllowSome
     90 	}
     91 
     92 	// We need to change our response slightly if the
     93 	// profile visitor is paging through statuses.
     94 	var (
     95 		paging      bool
     96 		pinnedResp  = &apimodel.PageableResponse{}
     97 		maxStatusID string
     98 	)
     99 
    100 	if maxStatusIDString := c.Query(MaxStatusIDKey); maxStatusIDString != "" {
    101 		maxStatusID = maxStatusIDString
    102 		paging = true
    103 	}
    104 
    105 	statusResp, errWithCode := m.processor.Account().WebStatusesGet(ctx, account.ID, maxStatusID)
    106 	if errWithCode != nil {
    107 		apiutil.WebErrorHandler(c, errWithCode, instanceGet)
    108 		return
    109 	}
    110 
    111 	// If we're not paging, then the profile visitor
    112 	// is currently just opening the bare profile, so
    113 	// load pinned statuses so we can show them at the
    114 	// top of the profile.
    115 	if !paging {
    116 		pinnedResp, errWithCode = m.processor.Account().StatusesGet(ctx, authed.Account, account.ID, 0, false, false, "", "", true, false, false)
    117 		if errWithCode != nil {
    118 			apiutil.WebErrorHandler(c, errWithCode, instanceGet)
    119 			return
    120 		}
    121 	}
    122 
    123 	stylesheets := []string{
    124 		assetsPathPrefix + "/Fork-Awesome/css/fork-awesome.min.css",
    125 		distPathPrefix + "/status.css",
    126 		distPathPrefix + "/profile.css",
    127 	}
    128 	if config.GetAccountsAllowCustomCSS() {
    129 		stylesheets = append(stylesheets, "/@"+account.Username+"/custom.css")
    130 	}
    131 
    132 	c.HTML(http.StatusOK, "profile.tmpl", gin.H{
    133 		"instance":         instance,
    134 		"account":          account,
    135 		"ogMeta":           ogBase(instance).withAccount(account),
    136 		"rssFeed":          rssFeed,
    137 		"robotsMeta":       robotsMeta,
    138 		"statuses":         statusResp.Items,
    139 		"statuses_next":    statusResp.NextLink,
    140 		"pinned_statuses":  pinnedResp.Items,
    141 		"show_back_to_top": paging,
    142 		"stylesheets":      stylesheets,
    143 		"javascript":       []string{distPathPrefix + "/frontend.js"},
    144 	})
    145 }
    146 
    147 func (m *Module) returnAPProfile(c *gin.Context, username string, accept string) {
    148 	user, errWithCode := m.processor.Fedi().UserGet(c.Request.Context(), username, c.Request.URL)
    149 	if errWithCode != nil {
    150 		apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
    151 		return
    152 	}
    153 
    154 	b, mErr := json.Marshal(user)
    155 	if mErr != nil {
    156 		err := fmt.Errorf("could not marshal json: %s", mErr)
    157 		apiutil.WebErrorHandler(c, gtserror.NewErrorInternalError(err), m.processor.InstanceGetV1)
    158 		return
    159 	}
    160 
    161 	c.Data(http.StatusOK, accept, b)
    162 }