package server import ( "context" "fmt" "time" "git.t-juice.club/torjus/ezshare/pb" "git.t-juice.club/torjus/ezshare/server/interceptors" "git.t-juice.club/torjus/ezshare/store" "github.com/dustin/go-humanize" "github.com/google/uuid" "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) type GRPCFileServiceServer struct { Logger *zap.SugaredLogger Hostname string store store.FileStore pb.UnimplementedFileServiceServer } func NewGRPCFileServiceServer(store store.FileStore) *GRPCFileServiceServer { return &GRPCFileServiceServer{Hostname: "localhost:8051", store: store, Logger: zap.NewNop().Sugar()} } func (s *GRPCFileServiceServer) UploadFile(ctx context.Context, req *pb.UploadFileRequest) (*pb.UploadFileResponse, error) { // Check if authorized if !interceptors.RoleAtLeast(ctx, pb.User_USER) { return nil, status.Error(codes.PermissionDenied, "permission denied") } var f pb.File f.Data = req.GetData() f.Metadata = &pb.File_Metadata{} f.Metadata.OriginalFilename = req.OriginalFilename if req.WithPasscode { f.Metadata.Passcode = uuid.Must(uuid.NewRandom()).String() } f.Metadata.ExpiresOn = req.ExpiresOn f.Metadata.MaxViews = req.MaxViews id, err := s.store.StoreFile(&f) if err != nil { s.Logger.Warnw("Error storing file.", "error", err) return nil, err } fileUrl := fmt.Sprintf("%s/files/%s", s.Hostname, id) if req.WithPasscode { fileUrl = fmt.Sprintf("%s?passcode=%s", fileUrl, f.Metadata.Passcode) } s.Logger.Infow("Received file.", "id", id, "size", humanize.Bytes(uint64(len(f.Data)))) return &pb.UploadFileResponse{Id: id, FileUrl: fileUrl}, nil } func (s *GRPCFileServiceServer) GetFile(ctx context.Context, req *pb.GetFileRequest) (*pb.GetFileResponse, error) { f, err := s.store.GetFile(req.Id) if err != nil { return nil, err } // Check if expired, viewed too many times, or needs passcode if f.Metadata.ExpiresOn != nil { if f.Metadata.ExpiresOn.AsTime().Before(time.Now()) { if err := s.store.DeleteFile(f.FileId); err != nil { s.Logger.Warnw("Error deleting expired file.", "error", err) } return nil, status.Error(codes.NotFound, "no such file") } } // Check if requires passcode if f.Metadata.Passcode != "" { if f.Metadata.Passcode != req.Passcode { return nil, status.Error(codes.PermissionDenied, "needs passcode") } } // Too many views if f.Metadata.MaxViews != 0 { if f.Metadata.CurrentViews >= f.Metadata.MaxViews { if err := s.store.DeleteFile(f.FileId); err != nil { s.Logger.Warnw("Error deleting expired file.", "error", err) } return nil, status.Error(codes.NotFound, "no such file") } } f.Metadata.CurrentViews++ if _, err := s.store.StoreFile(f); err != nil { s.Logger.Warnw("Error storing updated file.", "error", err) } s.Logger.Infow("Sending file to client.", "id", f.FileId, "size", humanize.Bytes(uint64(len(f.Data)))) return &pb.GetFileResponse{File: f}, nil } func (s *GRPCFileServiceServer) DeleteFile(ctx context.Context, req *pb.DeleteFileRequest) (*pb.DeleteFileResponse, error) { // Check if authorized if !interceptors.RoleAtLeast(ctx, pb.User_USER) { return nil, status.Error(codes.PermissionDenied, "permission denied") } // Ensure owner of file or admin f, err := s.store.GetFile(req.Id) if err != nil { if err == store.ErrNoSuchItem { return nil, status.Error(codes.NotFound, "no such file") } s.Logger.Warnw("Error getting file.", "error", err) return nil, status.Errorf(codes.Internal, "error getting file: %s", err) } if !(f.Metadata.Owner == interceptors.UserIDFromContext(ctx) || interceptors.RoleFromContext(ctx) == pb.User_ADMIN) { return nil, status.Error(codes.PermissionDenied, "permission denied") } if err := s.store.DeleteFile(req.Id); err != nil { s.Logger.Warnw("Error deleting file.", "error", err) return nil, err } s.Logger.Infow("Deleted file.", "id", req.Id) return &pb.DeleteFileResponse{}, nil } func (s *GRPCFileServiceServer) ListFiles(ctx context.Context, req *pb.ListFilesRequest) (*pb.ListFilesResponse, error) { var infos []*pb.ListFilesResponse_ListFileInfo allInfos, err := s.store.ListFiles() if err != nil { return nil, err } if interceptors.RoleFromContext(ctx) == pb.User_ADMIN { return &pb.ListFilesResponse{Files: allInfos}, nil } ownerID := interceptors.UserIDFromContext(ctx) for _, info := range allInfos { if info.Metadata.Owner == ownerID { infos = append(infos, info) } } return &pb.ListFilesResponse{ Files: infos, }, nil }