diff options
| author | 2021-07-05 13:23:03 +0200 | |
|---|---|---|
| committer | 2021-07-05 13:23:03 +0200 | |
| commit | d389e7b150df6ecd215c7b661b294ea153ad0103 (patch) | |
| tree | 8739e3103cb5130875d903cc7fc72fd9db3b8434 /internal/processing/media | |
| parent | Fix 404 contact (#74) (diff) | |
| download | gotosocial-d389e7b150df6ecd215c7b661b294ea153ad0103.tar.xz | |
Domain block (#76)
* start work on admin domain blocking
* move stuff around + further work on domain blocks
* move + restructure processor
* prep work for deleting account
* tidy
* go fmt
* formatting
* domain blocking more work
* check domain blocks way earlier on
* progress on delete account
* delete more stuff when an account is gone
* and more...
* domain blocky block block
* get individual domain block, delete a block
Diffstat (limited to 'internal/processing/media')
| -rw-r--r-- | internal/processing/media/create.go | 79 | ||||
| -rw-r--r-- | internal/processing/media/delete.go | 51 | ||||
| -rw-r--r-- | internal/processing/media/getfile.go | 120 | ||||
| -rw-r--r-- | internal/processing/media/getmedia.go | 51 | ||||
| -rw-r--r-- | internal/processing/media/media.go | 63 | ||||
| -rw-r--r-- | internal/processing/media/update.go | 70 | ||||
| -rw-r--r-- | internal/processing/media/util.go | 63 | 
7 files changed, 497 insertions, 0 deletions
diff --git a/internal/processing/media/create.go b/internal/processing/media/create.go new file mode 100644 index 000000000..f9e383504 --- /dev/null +++ b/internal/processing/media/create.go @@ -0,0 +1,79 @@ +/* +   GoToSocial +   Copyright (C) 2021 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 media + +import ( +	"bytes" +	"errors" +	"fmt" +	"io" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (p *processor) Create(account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error) { +	// open the attachment and extract the bytes from it +	f, err := form.File.Open() +	if err != nil { +		return nil, fmt.Errorf("error opening attachment: %s", err) +	} +	buf := new(bytes.Buffer) +	size, err := io.Copy(buf, f) +	if err != nil { +		return nil, fmt.Errorf("error reading attachment: %s", err) +	} +	if size == 0 { +		return nil, errors.New("could not read provided attachment: size 0 bytes") +	} + +	// allow the mediaHandler to work its magic of processing the attachment bytes, and putting them in whatever storage backend we're using +	attachment, err := p.mediaHandler.ProcessAttachment(buf.Bytes(), account.ID, "") +	if err != nil { +		return nil, fmt.Errorf("error reading attachment: %s", err) +	} + +	// now we need to add extra fields that the attachment processor doesn't know (from the form) +	// TODO: handle this inside mediaHandler.ProcessAttachment (just pass more params to it) + +	// first description +	attachment.Description = form.Description + +	// now parse the focus parameter +	focusx, focusy, err := parseFocus(form.Focus) +	if err != nil { +		return nil, err +	} +	attachment.FileMeta.Focus.X = focusx +	attachment.FileMeta.Focus.Y = focusy + +	// prepare the frontend representation now -- if there are any errors here at least we can bail without +	// having already put something in the database and then having to clean it up again (eugh) +	mastoAttachment, err := p.tc.AttachmentToMasto(attachment) +	if err != nil { +		return nil, fmt.Errorf("error parsing media attachment to frontend type: %s", err) +	} + +	// now we can confidently put the attachment in the database +	if err := p.db.Put(attachment); err != nil { +		return nil, fmt.Errorf("error storing media attachment in db: %s", err) +	} + +	return &mastoAttachment, nil +} diff --git a/internal/processing/media/delete.go b/internal/processing/media/delete.go new file mode 100644 index 000000000..694d78ac3 --- /dev/null +++ b/internal/processing/media/delete.go @@ -0,0 +1,51 @@ +package media + +import ( +	"fmt" +	"strings" + +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (p *processor) Delete(mediaAttachmentID string) gtserror.WithCode { +	a := >smodel.MediaAttachment{} +	if err := p.db.GetByID(mediaAttachmentID, a); err != nil { +		if _, ok := err.(db.ErrNoEntries); ok { +			// attachment already gone +			return nil +		} +		// actual error +		return gtserror.NewErrorInternalError(err) +	} + +	errs := []string{} + +	// delete the thumbnail from storage +	if a.Thumbnail.Path != "" { +		if err := p.storage.RemoveFileAt(a.Thumbnail.Path); err != nil { +			errs = append(errs, fmt.Sprintf("remove thumbnail at path %s: %s", a.Thumbnail.Path, err)) +		} +	} + +	// delete the file from storage +	if a.File.Path != "" { +		if err := p.storage.RemoveFileAt(a.File.Path); err != nil { +			errs = append(errs, fmt.Sprintf("remove file at path %s: %s", a.File.Path, err)) +		} +	} + +	// delete the attachment +	if err := p.db.DeleteByID(mediaAttachmentID, a); err != nil { +		if _, ok := err.(db.ErrNoEntries); !ok { +			errs = append(errs, fmt.Sprintf("remove attachment: %s", err)) +		} +	} + +	if len(errs) != 0 { +		return gtserror.NewErrorInternalError(fmt.Errorf("Delete: one or more errors removing attachment with id %s: %s", mediaAttachmentID, strings.Join(errs, "; "))) +	} + +	return nil +} diff --git a/internal/processing/media/getfile.go b/internal/processing/media/getfile.go new file mode 100644 index 000000000..1664306b8 --- /dev/null +++ b/internal/processing/media/getfile.go @@ -0,0 +1,120 @@ +/* +   GoToSocial +   Copyright (C) 2021 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 media + +import ( +	"fmt" +	"strings" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/media" +) + +func (p *processor) GetFile(account *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, error) { +	// parse the form fields +	mediaSize, err := media.ParseMediaSize(form.MediaSize) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not valid", form.MediaSize)) +	} + +	mediaType, err := media.ParseMediaType(form.MediaType) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("media type %s not valid", form.MediaType)) +	} + +	spl := strings.Split(form.FileName, ".") +	if len(spl) != 2 || spl[0] == "" || spl[1] == "" { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("file name %s not parseable", form.FileName)) +	} +	wantedMediaID := spl[0] + +	// get the account that owns the media and make sure it's not suspended +	acct := >smodel.Account{} +	if err := p.db.GetByID(form.AccountID, acct); err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("account with id %s could not be selected from the db: %s", form.AccountID, err)) +	} +	if !acct.SuspendedAt.IsZero() { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("account with id %s is suspended", form.AccountID)) +	} + +	// make sure the requesting account and the media account don't block each other +	if account != nil { +		blocked, err := p.db.Blocked(account.ID, form.AccountID) +		if err != nil { +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("block status could not be established between accounts %s and %s: %s", form.AccountID, account.ID, err)) +		} +		if blocked { +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("block exists between accounts %s and %s", form.AccountID, account.ID)) +		} +	} + +	// the way we store emojis is a little different from the way we store other attachments, +	// so we need to take different steps depending on the media type being requested +	content := &apimodel.Content{} +	var storagePath string +	switch mediaType { +	case media.Emoji: +		e := >smodel.Emoji{} +		if err := p.db.GetByID(wantedMediaID, e); err != nil { +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji %s could not be taken from the db: %s", wantedMediaID, err)) +		} +		if e.Disabled { +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("emoji %s has been disabled", wantedMediaID)) +		} +		switch mediaSize { +		case media.Original: +			content.ContentType = e.ImageContentType +			storagePath = e.ImagePath +		case media.Static: +			content.ContentType = e.ImageStaticContentType +			storagePath = e.ImageStaticPath +		default: +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for emoji", mediaSize)) +		} +	case media.Attachment, media.Header, media.Avatar: +		a := >smodel.MediaAttachment{} +		if err := p.db.GetByID(wantedMediaID, a); err != nil { +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("attachment %s could not be taken from the db: %s", wantedMediaID, err)) +		} +		if a.AccountID != form.AccountID { +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("attachment %s is not owned by %s", wantedMediaID, form.AccountID)) +		} +		switch mediaSize { +		case media.Original: +			content.ContentType = a.File.ContentType +			storagePath = a.File.Path +		case media.Small: +			content.ContentType = a.Thumbnail.ContentType +			storagePath = a.Thumbnail.Path +		default: +			return nil, gtserror.NewErrorNotFound(fmt.Errorf("media size %s not recognized for attachment", mediaSize)) +		} +	} + +	bytes, err := p.storage.RetrieveFileFrom(storagePath) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error retrieving from storage: %s", err)) +	} + +	content.ContentLength = int64(len(bytes)) +	content.Content = bytes +	return content, nil +} diff --git a/internal/processing/media/getmedia.go b/internal/processing/media/getmedia.go new file mode 100644 index 000000000..c36370225 --- /dev/null +++ b/internal/processing/media/getmedia.go @@ -0,0 +1,51 @@ +/* +   GoToSocial +   Copyright (C) 2021 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 media + +import ( +	"errors" +	"fmt" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (p *processor) GetMedia(account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) { +	attachment := >smodel.MediaAttachment{} +	if err := p.db.GetByID(mediaAttachmentID, attachment); err != nil { +		if _, ok := err.(db.ErrNoEntries); ok { +			// attachment doesn't exist +			return nil, gtserror.NewErrorNotFound(errors.New("attachment doesn't exist in the db")) +		} +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("db error getting attachment: %s", err)) +	} + +	if attachment.AccountID != account.ID { +		return nil, gtserror.NewErrorNotFound(errors.New("attachment not owned by requesting account")) +	} + +	a, err := p.tc.AttachmentToMasto(attachment) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err)) +	} + +	return &a, nil +} diff --git a/internal/processing/media/media.go b/internal/processing/media/media.go new file mode 100644 index 000000000..79c9a7e18 --- /dev/null +++ b/internal/processing/media/media.go @@ -0,0 +1,63 @@ +/* +   GoToSocial +   Copyright (C) 2021 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 media + +import ( +	"github.com/sirupsen/logrus" +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/blob" +	"github.com/superseriousbusiness/gotosocial/internal/config" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +	"github.com/superseriousbusiness/gotosocial/internal/media" +	"github.com/superseriousbusiness/gotosocial/internal/typeutils" +) + +// Processor wraps a bunch of functions for processing media actions. +type Processor interface { +	// Create creates a new media attachment belonging to the given account, using the request form. +	Create(account *gtsmodel.Account, form *apimodel.AttachmentRequest) (*apimodel.Attachment, error) +	// Delete deletes the media attachment with the given ID, including all files pertaining to that attachment. +	Delete(mediaAttachmentID string) gtserror.WithCode +	GetFile(account *gtsmodel.Account, form *apimodel.GetContentRequestForm) (*apimodel.Content, error) +	GetMedia(account *gtsmodel.Account, mediaAttachmentID string) (*apimodel.Attachment, gtserror.WithCode) +	Update(account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) +} + +type processor struct { +	tc           typeutils.TypeConverter +	config       *config.Config +	mediaHandler media.Handler +	storage      blob.Storage +	db           db.DB +	log          *logrus.Logger +} + +// New returns a new media processor. +func New(db db.DB, tc typeutils.TypeConverter, mediaHandler media.Handler, storage blob.Storage, config *config.Config, log *logrus.Logger) Processor { +	return &processor{ +		tc:           tc, +		config:       config, +		mediaHandler: mediaHandler, +		storage:      storage, +		db:           db, +		log:          log, +	} +} diff --git a/internal/processing/media/update.go b/internal/processing/media/update.go new file mode 100644 index 000000000..aa3583054 --- /dev/null +++ b/internal/processing/media/update.go @@ -0,0 +1,70 @@ +/* +   GoToSocial +   Copyright (C) 2021 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 media + +import ( +	"errors" +	"fmt" + +	apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" +	"github.com/superseriousbusiness/gotosocial/internal/db" +	"github.com/superseriousbusiness/gotosocial/internal/gtserror" +	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" +) + +func (p *processor) Update(account *gtsmodel.Account, mediaAttachmentID string, form *apimodel.AttachmentUpdateRequest) (*apimodel.Attachment, gtserror.WithCode) { +	attachment := >smodel.MediaAttachment{} +	if err := p.db.GetByID(mediaAttachmentID, attachment); err != nil { +		if _, ok := err.(db.ErrNoEntries); ok { +			// attachment doesn't exist +			return nil, gtserror.NewErrorNotFound(errors.New("attachment doesn't exist in the db")) +		} +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("db error getting attachment: %s", err)) +	} + +	if attachment.AccountID != account.ID { +		return nil, gtserror.NewErrorNotFound(errors.New("attachment not owned by requesting account")) +	} + +	if form.Description != nil { +		attachment.Description = *form.Description +		if err := p.db.UpdateByID(mediaAttachmentID, attachment); err != nil { +			return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error updating description: %s", err)) +		} +	} + +	if form.Focus != nil { +		focusx, focusy, err := parseFocus(*form.Focus) +		if err != nil { +			return nil, gtserror.NewErrorBadRequest(err) +		} +		attachment.FileMeta.Focus.X = focusx +		attachment.FileMeta.Focus.Y = focusy +		if err := p.db.UpdateByID(mediaAttachmentID, attachment); err != nil { +			return nil, gtserror.NewErrorInternalError(fmt.Errorf("database error updating focus: %s", err)) +		} +	} + +	a, err := p.tc.AttachmentToMasto(attachment) +	if err != nil { +		return nil, gtserror.NewErrorNotFound(fmt.Errorf("error converting attachment: %s", err)) +	} + +	return &a, nil +} diff --git a/internal/processing/media/util.go b/internal/processing/media/util.go new file mode 100644 index 000000000..47ea4fccd --- /dev/null +++ b/internal/processing/media/util.go @@ -0,0 +1,63 @@ +/* +   GoToSocial +   Copyright (C) 2021 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 media + +import ( +	"fmt" +	"strconv" +	"strings" +) + +func parseFocus(focus string) (focusx, focusy float32, err error) { +	if focus == "" { +		return +	} +	spl := strings.Split(focus, ",") +	if len(spl) != 2 { +		err = fmt.Errorf("improperly formatted focus %s", focus) +		return +	} +	xStr := spl[0] +	yStr := spl[1] +	if xStr == "" || yStr == "" { +		err = fmt.Errorf("improperly formatted focus %s", focus) +		return +	} +	fx, err := strconv.ParseFloat(xStr, 32) +	if err != nil { +		err = fmt.Errorf("improperly formatted focus %s: %s", focus, err) +		return +	} +	if fx > 1 || fx < -1 { +		err = fmt.Errorf("improperly formatted focus %s", focus) +		return +	} +	focusx = float32(fx) +	fy, err := strconv.ParseFloat(yStr, 32) +	if err != nil { +		err = fmt.Errorf("improperly formatted focus %s: %s", focus, err) +		return +	} +	if fy > 1 || fy < -1 { +		err = fmt.Errorf("improperly formatted focus %s", focus) +		return +	} +	focusy = float32(fy) +	return +}  | 
