Skip to content
Snippets Groups Projects
Commit 35a556e8 authored by Pavel Antonov's avatar Pavel Antonov :asterisk:
Browse files

Merge branch 'feature/PRXS-1082-Files' into 'feature/1004-AddPublicEntities'

Перенесён в репозиторий services/files

See merge request perxis/perxis-go!23
parents 197886fd d78a5294
No related branches found
No related tags found
No related merge requests found
Showing
with 1657 additions and 0 deletions
package files
import (
"errors"
"io"
"net/http"
)
type Downloader interface {
Download(dst io.Writer, file *File) error
}
type downloader struct{}
func NewDownloader() Downloader {
return &downloader{}
}
func (d *downloader) Download(dst io.Writer, file *File) error {
r, err := http.Get(file.URL)
if err != nil {
return err
}
defer r.Body.Close()
if r.StatusCode != http.StatusOK {
return errors.New("download request failed: " + r.Status)
}
_, err = io.Copy(dst, r.Body)
return err
}
package files
import (
"context"
"errors"
"fmt"
"net/url"
"reflect"
"git.perx.ru/perxis/perxis-go/pkg/items"
"git.perx.ru/perxis/perxis-go/pkg/schema/field"
signer "git.perx.ru/perxis/perxis-go/pkg/urlsigner"
"github.com/mitchellh/mapstructure"
)
const FileTypeName = "file"
type FileParameters struct {
t *FileType
}
func (p FileParameters) Type() field.Type { return p.t }
func (p *FileParameters) Clone(reset bool) field.Parameters { return p }
type FileType struct {
fs Files
signer signer.URLSigner
fileServerUrl string
uploader Uploader
}
func NewFileType(fs Files, signer signer.URLSigner, fileServerUrl string) *FileType {
return &FileType{fs: fs, signer: signer, fileServerUrl: fileServerUrl, uploader: NewUploader()}
}
func (t *FileType) WithUploader(uploader Uploader) *FileType {
t.uploader = uploader
return t
}
func (t FileType) Name() string { return FileTypeName }
func (t *FileType) NewParameters() field.Parameters {
return &FileParameters{t}
}
func (t FileType) Decode(_ context.Context, fld *field.Field, v interface{}) (interface{}, error) {
if v == nil {
return nil, nil
}
var f File
if err := mapstructure.Decode(v, &f); err != nil {
return nil, err
}
return &f, nil
}
func (t FileType) Encode(ctx context.Context, fld *field.Field, v interface{}) (interface{}, error) {
if v == nil {
return nil, nil
}
f, ok := v.(*File)
if !ok {
return nil, fmt.Errorf("FileField encode error: incorrect type: \"%s\", expected \"file\"", reflect.ValueOf(v).Kind())
}
if f.File != nil { // upload from file system
upload, err := t.fs.Upload(ctx, f)
if err != nil {
return nil, err
}
if err = t.uploader.Upload(f.File, upload); err != nil {
return nil, err
}
f = &upload.File
}
u := fmt.Sprintf("%s/%s", t.fileServerUrl, f.ID)
resURL, err := url.Parse(u)
if err != nil {
return nil, err
}
if t.signer != nil {
f.URL = t.signer.Sign(resURL).String()
} else {
f.URL = resURL.String()
}
res := make(map[string]interface{})
if err := mapstructure.Decode(f, &res); err != nil {
return nil, err
}
return res, nil
}
// PreSave - функция буде вызвана перед сохранением поля в Storage. Реализует интерфейс `perxis.PreSaver`
// Выполняет проверку поля является ли файл только что загруженным и переносит его при необходимости для
// постоянного хранения
func (t FileType) PreSave(ctx context.Context, fld *field.Field, v interface{}, itemCtx *items.Context) (interface{}, bool, error) {
if v == nil {
return nil, false, nil
}
f := v.(*File)
if f.ID == "" {
return nil, false, errors.New("FileType: file id required")
}
if !f.Temporary() {
return f, false, nil
}
f, err := t.fs.MoveUpload(ctx, &MultipartUpload{File: *f})
if err != nil {
return nil, false, err
}
return f, true, nil
}
// Field - создает новое поле Field типа FileType
// FileType должен быть предварительно создан `NewFileType` и зарегистрирован `field.Register`
func Field(o ...interface{}) *field.Field {
t, ok := field.GetType(FileTypeName)
if !ok {
panic("field file type not registered")
}
return field.NewField(t.NewParameters(), o...)
}
func (t *FileType) IsEmpty(v interface{}) bool {
if v == nil {
return true
}
f, ok := v.(*File)
return !ok || f.ID == ""
}
func (p FileParameters) GetField(path string) (fld *field.Field) {
switch path {
case "id", "name", "mimeType", "url", "key":
return field.String()
case "size":
return field.Number(field.NumberFormatInt)
default:
return nil
}
}
func init() {
// По умолчанию без FS
// Если нужны подписанные URL, и загрузка на FS, нужно зарегистрировать корректный типа
// См. cmd/content/command/server.go:195
field.Register(NewFileType(nil, nil, ""))
}
package files
import (
"fmt"
"io/fs"
"strings"
"git.perx.ru/perxis/perxis-go/pkg/id"
)
const (
TemporaryPrefix = "tmp"
)
// File - описание файла в системе хранения perxis
type File struct {
ID string `mapstructure:"id,omitempty" json:"id"` // Уникальный идентификатор файла в хранилище
Name string `mapstructure:"name,omitempty" json:"name" bson:"name,omitempty"` // Имя файла
Size int `mapstructure:"size,omitempty" json:"size" bson:"size,omitempty"` // Размер файла
MimeType string `mapstructure:"mimeType,omitempty" json:"mimeType" bson:"mimeType,omitempty"` // Mime-type файла
URL string `mapstructure:"url,omitempty" json:"url" bson:"url,omitempty"` // Адрес для загрузки файла
Key string `mapstructure:"key,omitempty" json:"key" bson:"key,omitempty"` // Ключ для хранения файла в хранилище
File fs.File `mapstructure:"-" json:"-" bson:"-"` // Файл для загрузки(из файловой системы)
}
func (f File) Clone() *File {
return &f
}
func (f File) Temporary() bool {
return strings.HasPrefix(f.ID, TemporaryPrefix)
}
func (f File) Fetch(i interface{}) interface{} {
p, _ := i.(string)
switch p {
case "id":
return f.ID
case "name":
return f.Name
case "size":
return f.Size
case "mime_type":
return f.MimeType
case "url":
return f.URL
case "key":
return f.Key
default:
panic("unknown parameter")
}
return nil
}
func NewFile(name, mimeType string, size int, temp bool) *File {
i := id.GenerateNewID()
if temp {
i = fmt.Sprintf("%s%s", TemporaryPrefix, i)
}
return &File{
ID: i,
Name: name,
Size: size,
MimeType: mimeType,
}
}
// MultipartUpload - описание загрузки файла
type MultipartUpload struct {
File
UploadID string `json:"upload_id"` // Идентификатор загрузки хранилища
PartSize int `json:"part_size"` // Размер блока для загрузки
PartURLs []string `json:"part_urls"` // Адреса для загрузки полного файла
Parts []*CompletedPart `json:"parts"` // Идентификаторы загруженных блоков (S3 ETAGs)
}
// Upload - описание загрузки файла
type Upload struct {
File
UploadURL string `json:"upload_url"` // Идентификатор загрузки хранилища
}
type CompletedPart struct {
Number int `json:"part_number"`
ID string `json:"id"`
}
func (u MultipartUpload) Clone() *MultipartUpload {
return &u
}
// Code generated by mockery v2.7.4. DO NOT EDIT.
package mocks
import (
io "io"
files "git.perx.ru/perxis/perxis-go/pkg/files"
mock "github.com/stretchr/testify/mock"
)
// Downloader is an autogenerated mock type for the Downloader type
type Downloader struct {
mock.Mock
}
// Download provides a mock function with given fields: dst, file
func (_m *Downloader) Download(dst io.Writer, file *files.File) error {
ret := _m.Called(dst, file)
var r0 error
if rf, ok := ret.Get(0).(func(io.Writer, *files.File) error); ok {
r0 = rf(dst, file)
} else {
r0 = ret.Error(0)
}
return r0
}
// Code generated by mockery v2.7.4. DO NOT EDIT.
package mocks
import (
context "context"
files "git.perx.ru/perxis/perxis-go/pkg/files"
mock "github.com/stretchr/testify/mock"
)
// Files is an autogenerated mock type for the Files type
type Files struct {
mock.Mock
}
// AbortUpload provides a mock function with given fields: ctx, upload
func (_m *Files) AbortUpload(ctx context.Context, upload *files.MultipartUpload) error {
ret := _m.Called(ctx, upload)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) error); ok {
r0 = rf(ctx, upload)
} else {
r0 = ret.Error(0)
}
return r0
}
// CompleteUpload provides a mock function with given fields: ctx, upload
func (_m *Files) CompleteUpload(ctx context.Context, upload *files.MultipartUpload) (*files.MultipartUpload, error) {
ret := _m.Called(ctx, upload)
var r0 *files.MultipartUpload
if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.MultipartUpload); ok {
r0 = rf(ctx, upload)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*files.MultipartUpload)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
r1 = rf(ctx, upload)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// DeleteFile provides a mock function with given fields: ctx, file
func (_m *Files) DeleteFile(ctx context.Context, file *files.File) error {
ret := _m.Called(ctx, file)
var r0 error
if rf, ok := ret.Get(0).(func(context.Context, *files.File) error); ok {
r0 = rf(ctx, file)
} else {
r0 = ret.Error(0)
}
return r0
}
// GetFile provides a mock function with given fields: ctx, file
func (_m *Files) GetFile(ctx context.Context, file *files.File) (*files.File, error) {
ret := _m.Called(ctx, file)
var r0 *files.File
if rf, ok := ret.Get(0).(func(context.Context, *files.File) *files.File); ok {
r0 = rf(ctx, file)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*files.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *files.File) error); ok {
r1 = rf(ctx, file)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// MoveUpload provides a mock function with given fields: ctx, upload
func (_m *Files) MoveUpload(ctx context.Context, upload *files.MultipartUpload) (*files.File, error) {
ret := _m.Called(ctx, upload)
var r0 *files.File
if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.File); ok {
r0 = rf(ctx, upload)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*files.File)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
r1 = rf(ctx, upload)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// StartUpload provides a mock function with given fields: ctx, upload
func (_m *Files) StartUpload(ctx context.Context, upload *files.MultipartUpload) (*files.MultipartUpload, error) {
ret := _m.Called(ctx, upload)
var r0 *files.MultipartUpload
if rf, ok := ret.Get(0).(func(context.Context, *files.MultipartUpload) *files.MultipartUpload); ok {
r0 = rf(ctx, upload)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*files.MultipartUpload)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *files.MultipartUpload) error); ok {
r1 = rf(ctx, upload)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Upload provides a mock function with given fields: ctx, file
func (_m *Files) Upload(ctx context.Context, file *files.File) (*files.Upload, error) {
ret := _m.Called(ctx, file)
var r0 *files.Upload
if rf, ok := ret.Get(0).(func(context.Context, *files.File) *files.Upload); ok {
r0 = rf(ctx, file)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*files.Upload)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(context.Context, *files.File) error); ok {
r1 = rf(ctx, file)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Code generated by mockery v2.7.4. DO NOT EDIT.
package mocks
import (
io "io"
files "git.perx.ru/perxis/perxis-go/pkg/files"
mock "github.com/stretchr/testify/mock"
)
// Uploader is an autogenerated mock type for the Uploader type
type Uploader struct {
mock.Mock
}
// Upload provides a mock function with given fields: src, upload
func (_m *Uploader) Upload(src io.Reader, upload *files.Upload) error {
ret := _m.Called(src, upload)
var r0 error
if rf, ok := ret.Get(0).(func(io.Reader, *files.Upload) error); ok {
r0 = rf(src, upload)
} else {
r0 = ret.Error(0)
}
return r0
}
package files
import (
"context"
)
// Files - описывает интерфейс файлового сервиса
// @microgen grpc, recovering, middleware
// @protobuf git.perx.ru/perxis/perxis-go/proto/files
// @grpc-addr files.Files
type Files interface {
// StartUpload - инициирует процедуру загрузки файла в файловое хранилище.
// Используется клиентским приложением для начала загрузки файла
StartUpload(ctx context.Context, upload *MultipartUpload) (u *MultipartUpload, err error)
// CompleteUpload - завершает процедуру загрузку файла
CompleteUpload(ctx context.Context, upload *MultipartUpload) (u *MultipartUpload, err error)
// AbortUpload - прерывает процедуру загрузки файла, все загруженные части файла удаляются их хранилища
AbortUpload(ctx context.Context, upload *MultipartUpload) (err error)
// MoveUpload - перемещает загруженный файл из временного расположения в постоянное месторасположения.
// После перемещение загрузки хранилище выдает новый идентификатор постоянного файла
MoveUpload(ctx context.Context, upload *MultipartUpload) (file *File, err error)
// Upload - инициация загрузки файла в хранилище. Возвращает объект, содержащий подписанный URL.
// Завершение загрузки файла осуществляется выполнением POST-запроса
Upload(ctx context.Context, file *File) (u *Upload, err error)
// GetFile - получить объект файла
GetFile(ctx context.Context, file *File) (f *File, err error)
// DeleteFile - удаляет файл. Если происходит удаление оригинала,
// удаляются и все связанные структуры
DeleteFile(ctx context.Context, file *File) (err error)
}
// Code generated by microgen 0.9.1. DO NOT EDIT.
package transport
import (
"context"
"errors"
files "git.perx.ru/perxis/perxis-go/pkg/files"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
func (set EndpointsSet) StartUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 *files.MultipartUpload, res1 error) {
request := StartUploadRequest{Upload: arg1}
response, res1 := set.StartUploadEndpoint(arg0, &request)
if res1 != nil {
if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
res1 = errors.New(e.Message())
}
return
}
return response.(*StartUploadResponse).U, res1
}
func (set EndpointsSet) CompleteUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 *files.MultipartUpload, res1 error) {
request := CompleteUploadRequest{Upload: arg1}
response, res1 := set.CompleteUploadEndpoint(arg0, &request)
if res1 != nil {
if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
res1 = errors.New(e.Message())
}
return
}
return response.(*CompleteUploadResponse).U, res1
}
func (set EndpointsSet) AbortUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 error) {
request := AbortUploadRequest{Upload: arg1}
_, res0 = set.AbortUploadEndpoint(arg0, &request)
if res0 != nil {
if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
res0 = errors.New(e.Message())
}
return
}
return res0
}
func (set EndpointsSet) MoveUpload(arg0 context.Context, arg1 *files.MultipartUpload) (res0 *files.File, res1 error) {
request := MoveUploadRequest{Upload: arg1}
response, res1 := set.MoveUploadEndpoint(arg0, &request)
if res1 != nil {
if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
res1 = errors.New(e.Message())
}
return
}
return response.(*MoveUploadResponse).File, res1
}
func (set EndpointsSet) Upload(arg0 context.Context, arg1 *files.File) (res0 *files.Upload, res1 error) {
request := UploadRequest{File: arg1}
response, res1 := set.UploadEndpoint(arg0, &request)
if res1 != nil {
if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
res1 = errors.New(e.Message())
}
return
}
return response.(*UploadResponse).U, res1
}
func (set EndpointsSet) GetFile(arg0 context.Context, arg1 *files.File) (res0 *files.File, res1 error) {
request := GetFileRequest{File: arg1}
response, res1 := set.GetFileEndpoint(arg0, &request)
if res1 != nil {
if e, ok := status.FromError(res1); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
res1 = errors.New(e.Message())
}
return
}
return response.(*GetFileResponse).F, res1
}
func (set EndpointsSet) DeleteFile(arg0 context.Context, arg1 *files.File) (res0 error) {
request := DeleteFileRequest{File: arg1}
_, res0 = set.DeleteFileEndpoint(arg0, &request)
if res0 != nil {
if e, ok := status.FromError(res0); ok || e.Code() == codes.Internal || e.Code() == codes.Unknown {
res0 = errors.New(e.Message())
}
return
}
return res0
}
// Code generated by microgen 0.9.1. DO NOT EDIT.
package transport
import endpoint "github.com/go-kit/kit/endpoint"
// EndpointsSet implements Files API and used for transport purposes.
type EndpointsSet struct {
StartUploadEndpoint endpoint.Endpoint
CompleteUploadEndpoint endpoint.Endpoint
AbortUploadEndpoint endpoint.Endpoint
MoveUploadEndpoint endpoint.Endpoint
UploadEndpoint endpoint.Endpoint
GetFileEndpoint endpoint.Endpoint
DeleteFileEndpoint endpoint.Endpoint
}
// Code generated by microgen 0.9.1. DO NOT EDIT.
package transport
import files "git.perx.ru/perxis/perxis-go/pkg/files"
type (
StartUploadRequest struct {
Upload *files.MultipartUpload `json:"upload"`
}
StartUploadResponse struct {
U *files.MultipartUpload `json:"u"`
}
CompleteUploadRequest struct {
Upload *files.MultipartUpload `json:"upload"`
}
CompleteUploadResponse struct {
U *files.MultipartUpload `json:"u"`
}
AbortUploadRequest struct {
Upload *files.MultipartUpload `json:"upload"`
}
// Formal exchange type, please do not delete.
AbortUploadResponse struct{}
MoveUploadRequest struct {
Upload *files.MultipartUpload `json:"upload"`
}
MoveUploadResponse struct {
File *files.File `json:"file"`
}
UploadRequest struct {
File *files.File `json:"file"`
}
UploadResponse struct {
U *files.Upload `json:"u"`
}
GetFileRequest struct {
File *files.File `json:"file"`
}
GetFileResponse struct {
F *files.File `json:"f"`
}
DeleteFileRequest struct {
File *files.File `json:"file"`
}
// Formal exchange type, please do not delete.
DeleteFileResponse struct{}
)
// Code generated by microgen 0.9.1. DO NOT EDIT.
package transportgrpc
import (
transport "git.perx.ru/perxis/perxis-go/pkg/files/transport"
pb "git.perx.ru/perxis/perxis-go/proto/files"
grpckit "github.com/go-kit/kit/transport/grpc"
empty "github.com/golang/protobuf/ptypes/empty"
grpc "google.golang.org/grpc"
)
func NewGRPCClient(conn *grpc.ClientConn, addr string, opts ...grpckit.ClientOption) transport.EndpointsSet {
if addr == "" {
addr = "files.Files"
}
return transport.EndpointsSet{
AbortUploadEndpoint: grpckit.NewClient(
conn, addr, "AbortUpload",
_Encode_AbortUpload_Request,
_Decode_AbortUpload_Response,
empty.Empty{},
opts...,
).Endpoint(),
CompleteUploadEndpoint: grpckit.NewClient(
conn, addr, "CompleteUpload",
_Encode_CompleteUpload_Request,
_Decode_CompleteUpload_Response,
pb.CompleteUploadResponse{},
opts...,
).Endpoint(),
DeleteFileEndpoint: grpckit.NewClient(
conn, addr, "DeleteFile",
_Encode_DeleteFile_Request,
_Decode_DeleteFile_Response,
empty.Empty{},
opts...,
).Endpoint(),
GetFileEndpoint: grpckit.NewClient(
conn, addr, "GetFile",
_Encode_GetFile_Request,
_Decode_GetFile_Response,
pb.GetFileResponse{},
opts...,
).Endpoint(),
MoveUploadEndpoint: grpckit.NewClient(
conn, addr, "MoveUpload",
_Encode_MoveUpload_Request,
_Decode_MoveUpload_Response,
pb.MoveUploadResponse{},
opts...,
).Endpoint(),
StartUploadEndpoint: grpckit.NewClient(
conn, addr, "StartUpload",
_Encode_StartUpload_Request,
_Decode_StartUpload_Response,
pb.StartUploadResponse{},
opts...,
).Endpoint(),
UploadEndpoint: grpckit.NewClient(
conn, addr, "Upload",
_Encode_Upload_Request,
_Decode_Upload_Response,
pb.UploadResponse{},
opts...,
).Endpoint(),
}
}
// Code generated by microgen 0.9.1. DO NOT EDIT.
// Please, do not change functions names!
package transportgrpc
import (
"context"
"errors"
"git.perx.ru/perxis/perxis-go/pkg/files"
"git.perx.ru/perxis/perxis-go/pkg/files/transport"
pb "git.perx.ru/perxis/perxis-go/proto/files"
empty "github.com/golang/protobuf/ptypes/empty"
)
func _Encode_StartUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil StartUploadRequest")
}
req := request.(*transport.StartUploadRequest)
reqUpload, err := PtrMultipartUploadToProto(req.Upload)
if err != nil {
return nil, err
}
return &pb.StartUploadRequest{Upload: reqUpload}, nil
}
func _Encode_CompleteUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil CompleteUploadRequest")
}
req := request.(*transport.CompleteUploadRequest)
reqUpload, err := PtrMultipartUploadToProto(req.Upload)
if err != nil {
return nil, err
}
return &pb.CompleteUploadRequest{Upload: reqUpload}, nil
}
func _Encode_AbortUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil AbortUploadRequest")
}
req := request.(*transport.AbortUploadRequest)
reqUpload, err := PtrMultipartUploadToProto(req.Upload)
if err != nil {
return nil, err
}
return &pb.AbortUploadRequest{Upload: reqUpload}, nil
}
func _Encode_MoveUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil MoveUploadRequest")
}
req := request.(*transport.MoveUploadRequest)
reqUpload, err := PtrMultipartUploadToProto(req.Upload)
if err != nil {
return nil, err
}
return &pb.MoveUploadRequest{Upload: reqUpload}, nil
}
func _Encode_DeleteFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil DeleteFileRequest")
}
req := request.(*transport.DeleteFileRequest)
reqFile, err := PtrFileToProto(req.File)
if err != nil {
return nil, err
}
return &pb.DeleteFileRequest{File: reqFile}, nil
}
func _Encode_StartUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil StartUploadResponse")
}
resp := response.(*transport.StartUploadResponse)
respU, err := PtrMultipartUploadToProto(resp.U)
if err != nil {
return nil, err
}
return &pb.StartUploadResponse{Upload: respU}, nil
}
func _Encode_CompleteUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil CompleteUploadResponse")
}
resp := response.(*transport.CompleteUploadResponse)
respU, err := PtrMultipartUploadToProto(resp.U)
if err != nil {
return nil, err
}
return &pb.CompleteUploadResponse{Upload: respU}, nil
}
func _Encode_AbortUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
return &empty.Empty{}, nil
}
func _Encode_MoveUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil MoveUploadResponse")
}
resp := response.(*transport.MoveUploadResponse)
respFile, err := PtrFileToProto(resp.File)
if err != nil {
return nil, err
}
return &pb.MoveUploadResponse{File: respFile}, nil
}
func _Encode_DeleteFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
return &empty.Empty{}, nil
}
func _Decode_StartUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil StartUploadRequest")
}
req := request.(*pb.StartUploadRequest)
reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
if err != nil {
return nil, err
}
return &transport.StartUploadRequest{Upload: reqUpload}, nil
}
func _Decode_CompleteUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil CompleteUploadRequest")
}
req := request.(*pb.CompleteUploadRequest)
reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
if err != nil {
return nil, err
}
return &transport.CompleteUploadRequest{Upload: reqUpload}, nil
}
func _Decode_AbortUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil AbortUploadRequest")
}
req := request.(*pb.AbortUploadRequest)
reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
if err != nil {
return nil, err
}
return &transport.AbortUploadRequest{Upload: reqUpload}, nil
}
func _Decode_MoveUpload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil MoveUploadRequest")
}
req := request.(*pb.MoveUploadRequest)
reqUpload, err := ProtoToPtrMultipartUpload(req.Upload)
if err != nil {
return nil, err
}
return &transport.MoveUploadRequest{Upload: reqUpload}, nil
}
func _Decode_DeleteFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil DeleteFileRequest")
}
req := request.(*pb.DeleteFileRequest)
reqFile, err := ProtoToPtrFile(req.File)
if err != nil {
return nil, err
}
return &transport.DeleteFileRequest{File: reqFile}, nil
}
func _Decode_StartUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil StartUploadResponse")
}
resp := response.(*pb.StartUploadResponse)
respU, err := ProtoToPtrMultipartUpload(resp.Upload)
if err != nil {
return nil, err
}
return &transport.StartUploadResponse{U: respU}, nil
}
func _Decode_CompleteUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil CompleteUploadResponse")
}
resp := response.(*pb.CompleteUploadResponse)
respU, err := ProtoToPtrMultipartUpload(resp.Upload)
if err != nil {
return nil, err
}
return &transport.CompleteUploadResponse{U: respU}, nil
}
func _Decode_AbortUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
return &empty.Empty{}, nil
}
func _Decode_MoveUpload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil MoveUploadResponse")
}
resp := response.(*pb.MoveUploadResponse)
respFile, err := ProtoToPtrFile(resp.File)
if err != nil {
return nil, err
}
return &transport.MoveUploadResponse{File: respFile}, nil
}
func _Decode_DeleteFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
return &empty.Empty{}, nil
}
func _Encode_GetFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil GetFileRequest")
}
req := request.(*transport.GetFileRequest)
return &pb.GetFileRequest{Id: req.File.ID}, nil
}
func _Encode_GetFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil GetFileResponse")
}
resp := response.(*transport.GetFileResponse)
respF, err := PtrFileToProto(resp.F)
if err != nil {
return nil, err
}
return &pb.GetFileResponse{File: respF}, nil
}
func _Decode_GetFile_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil GetFileRequest")
}
req := request.(*pb.GetFileRequest)
return &transport.GetFileRequest{File: &files.File{ID: req.Id}}, nil
}
func _Decode_GetFile_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil GetFileResponse")
}
resp := response.(*pb.GetFileResponse)
respF, err := ProtoToPtrFile(resp.File)
if err != nil {
return nil, err
}
return &transport.GetFileResponse{F: respF}, nil
}
func _Encode_Upload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil UploadRequest")
}
req := request.(*transport.UploadRequest)
reqFile, err := PtrFileToProto(req.File)
if err != nil {
return nil, err
}
return &pb.UploadRequest{File: reqFile}, nil
}
func _Encode_Upload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil UploadResponse")
}
resp := response.(*transport.UploadResponse)
respFile, err := PtrUploadToProto(resp.U)
if err != nil {
return nil, err
}
return &pb.UploadResponse{Upload: respFile}, nil
}
func _Decode_Upload_Request(ctx context.Context, request interface{}) (interface{}, error) {
if request == nil {
return nil, errors.New("nil UploadRequest")
}
req := request.(*pb.UploadRequest)
reqFile, err := ProtoToPtrFile(req.File)
if err != nil {
return nil, err
}
return &transport.UploadRequest{File: reqFile}, nil
}
func _Decode_Upload_Response(ctx context.Context, response interface{}) (interface{}, error) {
if response == nil {
return nil, errors.New("nil UploadResponse")
}
resp := response.(*pb.UploadResponse)
respUpload, err := ProtoToPtrUpload(resp.Upload)
if err != nil {
return nil, err
}
return &transport.UploadResponse{U: respUpload}, nil
}
// Code generated by microgen 0.9.1. DO NOT EDIT.
// It is better for you if you do not change functions names!
// This files will never be overwritten.
package transportgrpc
import (
"git.perx.ru/perxis/perxis-go/pkg/files"
pb "git.perx.ru/perxis/perxis-go/proto/files"
)
func PtrMultipartUploadToProto(upload *files.MultipartUpload) (*pb.MultipartUpload, error) {
if upload == nil {
return nil, nil
}
pbUpload := &pb.MultipartUpload{
UploadId: upload.UploadID,
PartSize: int32(upload.PartSize),
PartUrls: upload.PartURLs,
}
pbUpload.Parts = make([]*pb.CompletedPart, 0, len(upload.Parts))
for _, part := range upload.Parts {
pbUpload.Parts = append(pbUpload.Parts, &pb.CompletedPart{
Number: int32(part.Number),
Id: part.ID,
})
}
pbFile, err := PtrFileToProto(&upload.File)
if err != nil {
return nil, err
}
pbUpload.File = pbFile
return pbUpload, nil
}
func ProtoToPtrMultipartUpload(protoUpload *pb.MultipartUpload) (*files.MultipartUpload, error) {
if protoUpload == nil {
return nil, nil
}
upload := &files.MultipartUpload{
UploadID: protoUpload.UploadId,
PartSize: int(protoUpload.PartSize),
PartURLs: protoUpload.PartUrls,
}
upload.Parts = make([]*files.CompletedPart, 0, len(protoUpload.Parts))
for _, part := range protoUpload.Parts {
upload.Parts = append(upload.Parts, &files.CompletedPart{
Number: int(part.Number),
ID: part.Id,
})
}
file, err := ProtoToPtrFile(protoUpload.File)
if err != nil {
return nil, err
}
if file != nil {
upload.File = *file
}
return upload, nil
}
func PtrUploadToProto(upload *files.Upload) (*pb.Upload, error) {
if upload == nil {
return nil, nil
}
pbUpload := &pb.Upload{
UploadUrl: upload.UploadURL,
}
pbFile, err := PtrFileToProto(&upload.File)
if err != nil {
return nil, err
}
pbUpload.File = pbFile
return pbUpload, nil
}
func ProtoToPtrUpload(protoUpload *pb.Upload) (*files.Upload, error) {
if protoUpload == nil {
return nil, nil
}
upload := &files.Upload{
UploadURL: protoUpload.UploadUrl,
}
file, err := ProtoToPtrFile(protoUpload.File)
if err != nil {
return nil, err
}
upload.File = *file
return upload, nil
}
func PtrFileToProto(file *files.File) (*pb.File, error) {
if file == nil {
return nil, nil
}
pbFile := &pb.File{
Id: file.ID,
Name: file.Name,
Size: int32(file.Size),
MimeType: file.MimeType,
Url: file.URL,
}
return pbFile, nil
}
func ProtoToPtrFile(protoFile *pb.File) (*files.File, error) {
if protoFile == nil {
return nil, nil
}
file := &files.File{
ID: protoFile.Id,
Name: protoFile.Name,
Size: int(protoFile.Size),
MimeType: protoFile.MimeType,
URL: protoFile.Url,
}
return file, nil
}
// Code generated by microgen 0.9.1. DO NOT EDIT.
// DO NOT EDIT.
package transportgrpc
import (
transport "git.perx.ru/perxis/perxis-go/pkg/files/transport"
pb "git.perx.ru/perxis/perxis-go/proto/files"
grpc "github.com/go-kit/kit/transport/grpc"
empty "github.com/golang/protobuf/ptypes/empty"
context "golang.org/x/net/context"
)
type filesServer struct {
startUpload grpc.Handler
completeUpload grpc.Handler
abortUpload grpc.Handler
moveUpload grpc.Handler
upload grpc.Handler
getFile grpc.Handler
deleteFile grpc.Handler
pb.UnimplementedFilesServer
}
func NewGRPCServer(endpoints *transport.EndpointsSet, opts ...grpc.ServerOption) pb.FilesServer {
return &filesServer{
abortUpload: grpc.NewServer(
endpoints.AbortUploadEndpoint,
_Decode_AbortUpload_Request,
_Encode_AbortUpload_Response,
opts...,
),
completeUpload: grpc.NewServer(
endpoints.CompleteUploadEndpoint,
_Decode_CompleteUpload_Request,
_Encode_CompleteUpload_Response,
opts...,
),
deleteFile: grpc.NewServer(
endpoints.DeleteFileEndpoint,
_Decode_DeleteFile_Request,
_Encode_DeleteFile_Response,
opts...,
),
getFile: grpc.NewServer(
endpoints.GetFileEndpoint,
_Decode_GetFile_Request,
_Encode_GetFile_Response,
opts...,
),
moveUpload: grpc.NewServer(
endpoints.MoveUploadEndpoint,
_Decode_MoveUpload_Request,
_Encode_MoveUpload_Response,
opts...,
),
startUpload: grpc.NewServer(
endpoints.StartUploadEndpoint,
_Decode_StartUpload_Request,
_Encode_StartUpload_Response,
opts...,
),
upload: grpc.NewServer(
endpoints.UploadEndpoint,
_Decode_Upload_Request,
_Encode_Upload_Response,
opts...,
),
}
}
func (S *filesServer) StartUpload(ctx context.Context, req *pb.StartUploadRequest) (*pb.StartUploadResponse, error) {
_, resp, err := S.startUpload.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return resp.(*pb.StartUploadResponse), nil
}
func (S *filesServer) CompleteUpload(ctx context.Context, req *pb.CompleteUploadRequest) (*pb.CompleteUploadResponse, error) {
_, resp, err := S.completeUpload.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return resp.(*pb.CompleteUploadResponse), nil
}
func (S *filesServer) AbortUpload(ctx context.Context, req *pb.AbortUploadRequest) (*empty.Empty, error) {
_, resp, err := S.abortUpload.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return resp.(*empty.Empty), nil
}
func (S *filesServer) MoveUpload(ctx context.Context, req *pb.MoveUploadRequest) (*pb.MoveUploadResponse, error) {
_, resp, err := S.moveUpload.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return resp.(*pb.MoveUploadResponse), nil
}
func (S *filesServer) Upload(ctx context.Context, req *pb.UploadRequest) (*pb.UploadResponse, error) {
_, resp, err := S.upload.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return resp.(*pb.UploadResponse), nil
}
func (S *filesServer) GetFile(ctx context.Context, req *pb.GetFileRequest) (*pb.GetFileResponse, error) {
_, resp, err := S.getFile.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return resp.(*pb.GetFileResponse), nil
}
func (S *filesServer) DeleteFile(ctx context.Context, req *pb.DeleteFileRequest) (*empty.Empty, error) {
_, resp, err := S.deleteFile.ServeGRPC(ctx, req)
if err != nil {
return nil, err
}
return resp.(*empty.Empty), nil
}
// Code generated by microgen 0.9.1. DO NOT EDIT.
package transport
import (
"context"
files "git.perx.ru/perxis/perxis-go/pkg/files"
endpoint "github.com/go-kit/kit/endpoint"
)
func Endpoints(svc files.Files) EndpointsSet {
return EndpointsSet{
AbortUploadEndpoint: AbortUploadEndpoint(svc),
CompleteUploadEndpoint: CompleteUploadEndpoint(svc),
DeleteFileEndpoint: DeleteFileEndpoint(svc),
GetFileEndpoint: GetFileEndpoint(svc),
MoveUploadEndpoint: MoveUploadEndpoint(svc),
StartUploadEndpoint: StartUploadEndpoint(svc),
UploadEndpoint: UploadEndpoint(svc),
}
}
func StartUploadEndpoint(svc files.Files) endpoint.Endpoint {
return func(arg0 context.Context, request interface{}) (interface{}, error) {
req := request.(*StartUploadRequest)
res0, res1 := svc.StartUpload(arg0, req.Upload)
return &StartUploadResponse{U: res0}, res1
}
}
func CompleteUploadEndpoint(svc files.Files) endpoint.Endpoint {
return func(arg0 context.Context, request interface{}) (interface{}, error) {
req := request.(*CompleteUploadRequest)
res0, res1 := svc.CompleteUpload(arg0, req.Upload)
return &CompleteUploadResponse{U: res0}, res1
}
}
func AbortUploadEndpoint(svc files.Files) endpoint.Endpoint {
return func(arg0 context.Context, request interface{}) (interface{}, error) {
req := request.(*AbortUploadRequest)
res0 := svc.AbortUpload(arg0, req.Upload)
return &AbortUploadResponse{}, res0
}
}
func MoveUploadEndpoint(svc files.Files) endpoint.Endpoint {
return func(arg0 context.Context, request interface{}) (interface{}, error) {
req := request.(*MoveUploadRequest)
res0, res1 := svc.MoveUpload(arg0, req.Upload)
return &MoveUploadResponse{File: res0}, res1
}
}
func UploadEndpoint(svc files.Files) endpoint.Endpoint {
return func(arg0 context.Context, request interface{}) (interface{}, error) {
req := request.(*UploadRequest)
res0, res1 := svc.Upload(arg0, req.File)
return &UploadResponse{U: res0}, res1
}
}
func GetFileEndpoint(svc files.Files) endpoint.Endpoint {
return func(arg0 context.Context, request interface{}) (interface{}, error) {
req := request.(*GetFileRequest)
res0, res1 := svc.GetFile(arg0, req.File)
return &GetFileResponse{F: res0}, res1
}
}
func DeleteFileEndpoint(svc files.Files) endpoint.Endpoint {
return func(arg0 context.Context, request interface{}) (interface{}, error) {
req := request.(*DeleteFileRequest)
res0 := svc.DeleteFile(arg0, req.File)
return &DeleteFileResponse{}, res0
}
}
package files
import (
"errors"
"fmt"
"io"
"net/http"
"net/url"
)
type Uploader interface {
Upload(src io.Reader, upload *Upload) error
}
type uploader struct{}
func NewUploader() Uploader {
return &uploader{}
}
func (u *uploader) Upload(src io.Reader, upload *Upload) error {
req, err := http.NewRequest(http.MethodPut, upload.UploadURL, src)
if err != nil {
return err
}
req.ContentLength = int64(upload.Size)
req.Header.Set("Content-Disposition", fmt.Sprintf("attachment; filename*=UTF-8''%s;", url.PathEscape(upload.Name)))
req.Header.Set("Content-Type", upload.MimeType)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New("upload request failed: " + resp.Status)
}
return nil
}
package urlsigner
import (
"bytes"
"crypto/sha256"
"encoding/base64"
"net/url"
"strconv"
"strings"
"time"
"git.perx.ru/perxis/perxis-go/pkg/data"
)
type URLSigner interface {
Sign(url *url.URL) *url.URL
Check(url *url.URL) bool
}
const (
defaultSignatureExpire = 15 * time.Minute
defaultQueryKey = "sign"
separator = "|"
saltSize = 16
)
type urlSigner struct {
secret string
expirationTime time.Duration
queryKey string
params []string
}
func NewURLSigner(secret string, expirationTime time.Duration, queryKey string, params ...string) URLSigner {
if len(params) == 0 {
params = make([]string, 0)
}
if expirationTime == 0 {
expirationTime = defaultSignatureExpire
}
if queryKey == "" {
queryKey = defaultQueryKey
}
return &urlSigner{
secret: secret,
expirationTime: expirationTime,
queryKey: queryKey,
params: params,
}
}
func (s *urlSigner) Sign(u *url.URL) *url.URL {
q := u.Query()
h := sha256.New()
salt := data.GenerateRandomString(saltSize)
for _, p := range s.params {
if vv := q[p]; len(vv) > 0 {
for _, v := range vv {
h.Write([]byte(v))
}
}
}
h.Write([]byte(u.Path))
h.Write([]byte(s.expirationTime.String()))
h.Write([]byte(s.secret))
h.Write([]byte(salt))
expTime := time.Now().Add(s.expirationTime).Unix()
res := strings.Join([]string{strconv.FormatInt(expTime, 16), salt, string(h.Sum(nil))}, separator)
q.Set(s.queryKey, base64.URLEncoding.EncodeToString([]byte(res)))
u.RawQuery = q.Encode()
return u
}
func (s *urlSigner) Check(u *url.URL) bool {
q := u.Query()
sign := q.Get(s.queryKey)
if sign == "" {
return false
}
b, err := base64.URLEncoding.DecodeString(sign)
if err != nil {
return false
}
m := bytes.Split(b, []byte(separator))
if len(m) < 3 {
return false
}
expTime, err := strconv.ParseInt(string(m[0]), 16, 64)
if err != nil || time.Now().Unix() > expTime {
return false
}
salt := m[1]
var hash []byte
for i := 2; i < len(m); i++ {
hash = append(hash, m[i]...)
if len(m) > i+1 {
hash = append(hash, []byte(separator)...)
}
}
h := sha256.New()
for _, p := range s.params {
if vv := q[p]; len(vv) > 0 {
for _, v := range vv {
h.Write([]byte(v))
}
}
}
h.Write([]byte(u.Path))
h.Write([]byte(s.expirationTime.String()))
h.Write([]byte(s.secret))
h.Write(salt)
return bytes.Equal(hash, h.Sum(nil))
}
package urlsigner
import (
"net/url"
"testing"
"time"
"github.com/stretchr/testify/require"
)
const secret = "secret_key"
func TestSigner(t *testing.T) {
t.Run("Not Signed", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path")
signer := NewURLSigner(secret, time.Minute, "")
require.False(t, signer.Check(u))
})
t.Run("Simple", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path")
signer := NewURLSigner(secret, time.Minute, "")
u = signer.Sign(u)
require.True(t, signer.Check(u))
})
t.Run("Custom Query Key", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path")
signer := NewURLSigner(secret, time.Minute, "custom")
u = signer.Sign(u)
require.True(t, signer.Check(u))
})
t.Run("URL Expired", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path")
signer := NewURLSigner(secret, time.Millisecond, "")
u = signer.Sign(u)
time.Sleep(time.Second)
require.False(t, signer.Check(u))
})
t.Run("Required Params", func(t *testing.T) {
t.Run("Correct", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path?param1=1&param2=2")
signer := NewURLSigner(secret, time.Minute, "", "param1", "param2")
u = signer.Sign(u)
require.True(t, signer.Check(u))
})
t.Run("One Param Empty", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path?param1=1")
signer := NewURLSigner(secret, time.Minute, "", "param1", "param2")
u = signer.Sign(u)
require.True(t, signer.Check(u))
})
t.Run("Exchanged Values", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path?param1=1&param2=2")
signer := NewURLSigner(secret, time.Minute, "", "param1", "param2")
u = signer.Sign(u)
q := u.Query()
q.Set("param1", "2")
q.Set("param2", "1")
u.RawQuery = q.Encode()
require.False(t, signer.Check(u))
})
})
t.Run("Extra params", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path?param1=1")
signer := NewURLSigner(secret, time.Minute, "", "param1")
u = signer.Sign(u)
q := u.Query()
q.Set("extra", "100")
u.RawQuery = q.Encode()
require.True(t, signer.Check(u))
})
t.Run("Array Param Value", func(t *testing.T) {
u, _ := url.Parse("http://example.com/path?param1=1&param1=2&param1=3")
signer := NewURLSigner(secret, time.Minute, "", "param1")
u = signer.Sign(u)
q := u.Query()
q.Set("param1", "1")
u.RawQuery = q.Encode()
require.False(t, signer.Check(u))
})
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment