GraphQL API and utilities for the rpdata project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

144 lines
3.4 KiB

package services
import (
"context"
"errors"
"git.aiterp.net/rpdata/api/models"
"git.aiterp.net/rpdata/api/repositories"
"github.com/h2non/filetype"
"io"
)
var ErrPrivateNoAuthor = errors.New("cannot search for private files without an author")
var ErrInvalidName = errors.New("invalid name")
var ErrInvalidFileSize = errors.New("file is not of a correct size (min: 320B, max: 16MB)")
var ErrCouldNotReadHead = errors.New("could not read file head")
var ErrInvalidFileType = errors.New("file type could not be recognized or is not allowed")
var ErrCouldNotUploadFile = errors.New("could not upload file")
// FileService is a service for files.
type FileService struct {
files repositories.FileRepository
authService *AuthService
}
func (s *FileService) Find(ctx context.Context, id string) (*models.File, error) {
file, err := s.files.Find(ctx, id)
if err != nil {
return nil, err
}
if !file.Public {
err := s.authService.CheckPermission(ctx, "view", file)
if err != nil {
return nil, repositories.ErrNotFound
}
}
return file, nil
}
func (s *FileService) List(ctx context.Context, filter models.FileFilter) ([]*models.File, error) {
token := s.authService.TokenFromContext(ctx)
if filter.Public != nil {
if *filter.Public == false {
if filter.Author == nil || *filter.Author == "" {
return nil, ErrPrivateNoAuthor
}
if !token.PermittedUser(*filter.Author, "member", "file.list") {
return nil, ErrUnauthorized
}
}
}
return s.files.List(ctx, filter)
}
func (s *FileService) Upload(ctx context.Context, reader io.Reader, name string, size int64) (*models.File, error) {
if name == "" {
return nil, ErrInvalidName
} else if size < 320 || size > 16777216 {
return nil, ErrInvalidFileSize
}
head := make([]byte, 320)
n, err := reader.Read(head)
if err != nil || n < 320 {
return nil, ErrCouldNotReadHead
}
fileType, err := filetype.Match(head)
if err != nil || !allowedMimeTypes[fileType.MIME.Value] {
return nil, ErrInvalidFileType
}
panic("implement rest of me")
}
func (s *FileService) Edit(ctx context.Context, id string, name *string, public *bool) (*models.File, error) {
file, err := s.files.Find(ctx, id)
if err != nil {
return nil, err
}
err = s.authService.CheckPermission(ctx, "edit", file)
if err != nil {
if !file.Public {
return nil, repositories.ErrNotFound
}
return nil, err
}
return s.files.Update(ctx, *file, models.FileUpdate{
Name: name,
Public: public,
})
}
// concatReader is a quick and dirty reader for reading the head and the file.
type concatReader struct {
head []byte
headPos int
body io.Reader
}
func (r *concatReader) Read(p []byte) (n int, err error) {
if len(p) == 0 {
return 0, io.ErrShortBuffer
}
if r.headPos < len(r.head) {
remainder := r.head[r.headPos:]
if len(p) < len(remainder) {
r.headPos += len(p)
copy(p, remainder)
return len(p), nil
}
r.headPos = len(r.head)
copy(p, remainder)
return len(p), nil
}
return r.body.Read(p)
}
var allowedMimeTypes = map[string]bool{
"": false,
"image/jpeg": true,
"image/png": true,
"image/gif": true,
"image/tiff": true,
"image/tga": true,
"text/plain": true,
"application/json": true,
"application/pdf": false,
"binary/octet-stream": false,
"video/mp4": false,
"audio/mp3": false,
}