package services import ( "context" "errors" "git.aiterp.net/rpdata/api/internal/auth" "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 } 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 := auth.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 := auth.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, auth.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 = auth.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, }