gtsocial-umbx

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

emojiupdate.go (7065B)


      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 admin
     19 
     20 import (
     21 	"errors"
     22 	"fmt"
     23 	"net/http"
     24 	"strings"
     25 
     26 	"github.com/gin-gonic/gin"
     27 	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
     28 	apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
     29 	"github.com/superseriousbusiness/gotosocial/internal/config"
     30 	"github.com/superseriousbusiness/gotosocial/internal/gtserror"
     31 	"github.com/superseriousbusiness/gotosocial/internal/oauth"
     32 	"github.com/superseriousbusiness/gotosocial/internal/validate"
     33 )
     34 
     35 // EmojiPATCHHandler swagger:operation PATCH /api/v1/admin/custom_emojis/{id} emojiUpdate
     36 //
     37 // Perform admin action on a local or remote emoji known to this instance.
     38 //
     39 // Action performed depends upon the action `type` provided.
     40 //
     41 // `disable`: disable a REMOTE emoji from being used/displayed on this instance. Does not work for local emojis.
     42 //
     43 // `copy`: copy a REMOTE emoji to this instance. When doing this action, a shortcode MUST be provided, and it must
     44 // be unique among emojis already present on this instance. A category MAY be provided, and the copied emoji will then
     45 // be put into the provided category.
     46 //
     47 // `modify`: modify a LOCAL emoji. You can provide a new image for the emoji and/or update the category.
     48 //
     49 // Local emojis cannot be deleted using this endpoint. To delete a local emoji, check DELETE /api/v1/admin/custom_emojis/{id} instead.
     50 //
     51 //	---
     52 //	tags:
     53 //	- admin
     54 //
     55 //	consumes:
     56 //	- multipart/form-data
     57 //
     58 //	produces:
     59 //	- application/json
     60 //
     61 //	parameters:
     62 //	-
     63 //		name: id
     64 //		type: string
     65 //		description: The id of the emoji.
     66 //		in: path
     67 //		required: true
     68 //	-
     69 //		name: type
     70 //		in: formData
     71 //		description: |-
     72 //			Type of action to be taken. One of: (`disable`, `copy`, `modify`).
     73 //			For REMOTE emojis, `copy` or `disable` are supported.
     74 //			For LOCAL emojis, only `modify` is supported.
     75 //		type: string
     76 //		required: true
     77 //	-
     78 //		name: shortcode
     79 //		in: formData
     80 //		description: >-
     81 //			The code to use for the emoji, which will be used by instance denizens to select it.
     82 //			This must be unique on the instance. Works for the `copy` action type only.
     83 //		type: string
     84 //		pattern: \w{2,30}
     85 //	-
     86 //		name: image
     87 //		in: formData
     88 //		description: >-
     89 //			A new png or gif image to use for the emoji. Animated pngs work too!
     90 //			To ensure compatibility with other fedi implementations, emoji size limit is 50kb by default.
     91 //			Works for LOCAL emojis only.
     92 //		type: file
     93 //	-
     94 //		name: category
     95 //		in: formData
     96 //		description: >-
     97 //			Category in which to place the emoji. 64 characters or less.
     98 //			If a category with the given name doesn't exist yet, it will be created.
     99 //		type: string
    100 //
    101 //	security:
    102 //	- OAuth2 Bearer:
    103 //		- admin
    104 //
    105 //	responses:
    106 //		'200':
    107 //			description: The updated emoji.
    108 //			schema:
    109 //				"$ref": "#/definitions/adminEmoji"
    110 //		'400':
    111 //			description: bad request
    112 //		'401':
    113 //			description: unauthorized
    114 //		'403':
    115 //			description: forbidden
    116 //		'404':
    117 //			description: not found
    118 //		'406':
    119 //			description: not acceptable
    120 //		'500':
    121 //			description: internal server error
    122 func (m *Module) EmojiPATCHHandler(c *gin.Context) {
    123 	authed, err := oauth.Authed(c, true, true, true, true)
    124 	if err != nil {
    125 		apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1)
    126 		return
    127 	}
    128 
    129 	if !*authed.User.Admin {
    130 		err := fmt.Errorf("user %s not an admin", authed.User.ID)
    131 		apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
    132 		return
    133 	}
    134 
    135 	if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
    136 		apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
    137 		return
    138 	}
    139 
    140 	emojiID := c.Param(IDKey)
    141 	if emojiID == "" {
    142 		err := errors.New("no emoji id specified")
    143 		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
    144 		return
    145 	}
    146 
    147 	form := &apimodel.EmojiUpdateRequest{}
    148 	if err := c.ShouldBind(form); err != nil {
    149 		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
    150 		return
    151 	}
    152 
    153 	if err := validateUpdateEmoji(form); err != nil {
    154 		apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
    155 		return
    156 	}
    157 
    158 	emoji, errWithCode := m.processor.Admin().EmojiUpdate(c.Request.Context(), emojiID, form)
    159 	if errWithCode != nil {
    160 		apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
    161 		return
    162 	}
    163 
    164 	c.JSON(http.StatusOK, emoji)
    165 }
    166 
    167 // do a first pass on the form here
    168 func validateUpdateEmoji(form *apimodel.EmojiUpdateRequest) error {
    169 	// check + normalize update type so we don't need
    170 	// to do this trimming + lowercasing again later
    171 	switch strings.TrimSpace(strings.ToLower(string(form.Type))) {
    172 	case string(apimodel.EmojiUpdateDisable):
    173 		// no params required for this one, so don't bother checking
    174 		form.Type = apimodel.EmojiUpdateDisable
    175 	case string(apimodel.EmojiUpdateCopy):
    176 		// need at least a valid shortcode when doing a copy
    177 		if form.Shortcode == nil {
    178 			return errors.New("emoji action type was 'copy' but no shortcode was provided")
    179 		}
    180 
    181 		if err := validate.EmojiShortcode(*form.Shortcode); err != nil {
    182 			return err
    183 		}
    184 
    185 		// category optional during copy
    186 		if form.CategoryName != nil {
    187 			if err := validate.EmojiCategory(*form.CategoryName); err != nil {
    188 				return err
    189 			}
    190 		}
    191 
    192 		form.Type = apimodel.EmojiUpdateCopy
    193 	case string(apimodel.EmojiUpdateModify):
    194 		// need either image or category name for modify
    195 		hasImage := form.Image != nil && form.Image.Size != 0
    196 		hasCategoryName := form.CategoryName != nil
    197 		if !hasImage && !hasCategoryName {
    198 			return errors.New("emoji action type was 'modify' but no image or category name was provided")
    199 		}
    200 
    201 		if hasImage {
    202 			maxSize := config.GetMediaEmojiLocalMaxSize()
    203 			if form.Image.Size > int64(maxSize) {
    204 				return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024)
    205 			}
    206 		}
    207 
    208 		if hasCategoryName {
    209 			if err := validate.EmojiCategory(*form.CategoryName); err != nil {
    210 				return err
    211 			}
    212 		}
    213 
    214 		form.Type = apimodel.EmojiUpdateModify
    215 	default:
    216 		return errors.New("emoji action type must be one of 'disable', 'copy', 'modify'")
    217 	}
    218 
    219 	return nil
    220 }