mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 11:12:26 -05:00 
			
		
		
		
	* add default avatars * allow webModule to error * return errWithCode from account get * add AccountGetLocalByUsername * check nil requesting account * add timestampShort function for just month/year * move loading logic to New + add default avatars * add profile page view * update swagger docs * add excludeReblogs to GetAccountStatuses * ignore casing when selecting local account by username * appropriate redirects * css fiddling * add 'about' heading * adjust thread page to work with routing * return AP representation if requested + authorized * simplify auth check * go fmt * golangci-lint ignore math/rand
		
			
				
	
	
		
			168 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			168 lines
		
	
	
	
		
			5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| /*
 | |
|    GoToSocial
 | |
|    Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
 | |
| 
 | |
|    This program is free software: you can redistribute it and/or modify
 | |
|    it under the terms of the GNU Affero General Public License as published by
 | |
|    the Free Software Foundation, either version 3 of the License, or
 | |
|    (at your option) any later version.
 | |
| 
 | |
|    This program is distributed in the hope that it will be useful,
 | |
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|    GNU Affero General Public License for more details.
 | |
| 
 | |
|    You should have received a copy of the GNU Affero General Public License
 | |
|    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
| */
 | |
| 
 | |
| package web
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"io/ioutil"
 | |
| 	"net/http"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| 
 | |
| 	"github.com/gin-gonic/gin"
 | |
| 	"github.com/sirupsen/logrus"
 | |
| 	"github.com/spf13/viper"
 | |
| 	"github.com/superseriousbusiness/gotosocial/internal/api"
 | |
| 	"github.com/superseriousbusiness/gotosocial/internal/config"
 | |
| 	"github.com/superseriousbusiness/gotosocial/internal/processing"
 | |
| 	"github.com/superseriousbusiness/gotosocial/internal/router"
 | |
| 	"github.com/superseriousbusiness/gotosocial/internal/uris"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	confirmEmailPath = "/" + uris.ConfirmEmailPath
 | |
| 	tokenParam       = "token"
 | |
| 	usernameKey      = "username"
 | |
| 	statusIDKey      = "status"
 | |
| 	profilePath      = "/@:" + usernameKey
 | |
| 	statusPath       = profilePath + "/statuses/:" + statusIDKey
 | |
| )
 | |
| 
 | |
| // Module implements the api.ClientModule interface for web pages.
 | |
| type Module struct {
 | |
| 	processor      processing.Processor
 | |
| 	assetsPath     string
 | |
| 	adminPath      string
 | |
| 	defaultAvatars []string
 | |
| }
 | |
| 
 | |
| // New returns a new api.ClientModule for web pages.
 | |
| func New(processor processing.Processor) (api.ClientModule, error) {
 | |
| 	assetsBaseDir := viper.GetString(config.Keys.WebAssetBaseDir)
 | |
| 	if assetsBaseDir == "" {
 | |
| 		return nil, fmt.Errorf("%s cannot be empty and must be a relative or absolute path", config.Keys.WebAssetBaseDir)
 | |
| 	}
 | |
| 
 | |
| 	assetsPath, err := filepath.Abs(assetsBaseDir)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("error getting absolute path of %s: %s", assetsBaseDir, err)
 | |
| 	}
 | |
| 
 | |
| 	defaultAvatarsPath := filepath.Join(assetsPath, "default_avatars")
 | |
| 	defaultAvatarFiles, err := ioutil.ReadDir(defaultAvatarsPath)
 | |
| 	if err != nil {
 | |
| 		return nil, fmt.Errorf("error reading default avatars at %s: %s", defaultAvatarsPath, err)
 | |
| 	}
 | |
| 
 | |
| 	defaultAvatars := []string{}
 | |
| 	for _, f := range defaultAvatarFiles {
 | |
| 		// ignore directories
 | |
| 		if f.IsDir() {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// ignore files bigger than 50kb
 | |
| 		if f.Size() > 50000 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		extension := strings.TrimPrefix(strings.ToLower(filepath.Ext(f.Name())), ".")
 | |
| 
 | |
| 		// take only files with simple extensions
 | |
| 		switch extension {
 | |
| 		case "svg", "jpeg", "jpg", "gif", "png":
 | |
| 			defaultAvatarPath := fmt.Sprintf("/assets/default_avatars/%s", f.Name())
 | |
| 			defaultAvatars = append(defaultAvatars, defaultAvatarPath)
 | |
| 		default:
 | |
| 			continue
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return &Module{
 | |
| 		processor:      processor,
 | |
| 		assetsPath:     assetsPath,
 | |
| 		adminPath:      filepath.Join(assetsPath, "admin"),
 | |
| 		defaultAvatars: defaultAvatars,
 | |
| 	}, nil
 | |
| }
 | |
| 
 | |
| func (m *Module) baseHandler(c *gin.Context) {
 | |
| 	l := logrus.WithField("func", "BaseGETHandler")
 | |
| 	l.Trace("serving index html")
 | |
| 
 | |
| 	host := viper.GetString(config.Keys.Host)
 | |
| 	instance, err := m.processor.InstanceGet(c.Request.Context(), host)
 | |
| 	if err != nil {
 | |
| 		l.Debugf("error getting instance from processor: %s", err)
 | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	c.HTML(http.StatusOK, "index.tmpl", gin.H{
 | |
| 		"instance": instance,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // NotFoundHandler serves a 404 html page instead of a blank 404 error.
 | |
| func (m *Module) NotFoundHandler(c *gin.Context) {
 | |
| 	l := logrus.WithField("func", "404")
 | |
| 	l.Trace("serving 404 html")
 | |
| 
 | |
| 	host := viper.GetString(config.Keys.Host)
 | |
| 	instance, err := m.processor.InstanceGet(c.Request.Context(), host)
 | |
| 	if err != nil {
 | |
| 		l.Debugf("error getting instance from processor: %s", err)
 | |
| 		c.JSON(http.StatusInternalServerError, gin.H{"error": "internal server error"})
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	c.HTML(404, "404.tmpl", gin.H{
 | |
| 		"instance": instance,
 | |
| 	})
 | |
| }
 | |
| 
 | |
| // Route satisfies the RESTAPIModule interface
 | |
| func (m *Module) Route(s router.Router) error {
 | |
| 	// serve static files from assets dir at /assets
 | |
| 	s.AttachStaticFS("/assets", fileSystem{http.Dir(m.assetsPath)})
 | |
| 
 | |
| 	// serve admin panel from within assets dir at /admin/
 | |
| 	// and redirect /admin to /admin/
 | |
| 	s.AttachStaticFS("/admin/", fileSystem{http.Dir(m.adminPath)})
 | |
| 	s.AttachHandler(http.MethodGet, "/admin", func(c *gin.Context) {
 | |
| 		c.Redirect(http.StatusMovedPermanently, "/admin/")
 | |
| 	})
 | |
| 
 | |
| 	// serve front-page
 | |
| 	s.AttachHandler(http.MethodGet, "/", m.baseHandler)
 | |
| 
 | |
| 	// serve profile pages at /@username
 | |
| 	s.AttachHandler(http.MethodGet, profilePath, m.profileTemplateHandler)
 | |
| 
 | |
| 	// serve statuses
 | |
| 	s.AttachHandler(http.MethodGet, statusPath, m.threadTemplateHandler)
 | |
| 
 | |
| 	// serve email confirmation page at /confirm_email?token=whatever
 | |
| 	s.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler)
 | |
| 
 | |
| 	// 404 handler
 | |
| 	s.AttachNoRouteHandler(m.NotFoundHandler)
 | |
| 
 | |
| 	return nil
 | |
| }
 |