Vabble Technology

By May 9, 2020 May 11th, 2020 Vabble Technology

Vabble Platform

The full development was on LAMP (Linux, Apache, MySQL, PHP) and the symfony PHP framework but other options such as NEO4j, Elastic Search were also included in the development.

Upon initial launch, we will use traditional RDBMS as well as NoSQL databases working together to fulfill the different requirements. For relational databases we use MySQL to store data about users, videos, content settings, moderation logic and user sessions.

In time, we will migrate to our own blockchain for most of our database requirements. Having our platform online as fast as possible given the times we are in is of significant importance. If we were to wait for our blockchain, it would be another 18 months. We don’t have that time but when our blockchain is ready, we will migrate.

The development web server was Apache and FastCGI mode for PHP. Rather than Apache for production server we used NginX to handle content delivery. We also delegated this job to CND-s, being Amazon S3. CND-s improved the systems scalability as the web server could be easily replicated then and put behind load balancer

We used websockets for persistent communication between server and client upon page load. Node JS was used for server-side implementation of websockets.

Angular JS was used for implementing client side MVC and moving some load from web server to client. Server was in charge of producing JSON data that clients transferred into DOM/HTML

We used Doctrine ORM as it has built in schema migrations and improved deployments and database structure changes.

Other languages used for varity of reasons were:

HTML5
Twig
AngularJS
ReactJS
Jasmine
Karma
GulpJS
Bootstrap 3
Sass/Compass

And for video encoding we use Amazon Elastic Transcoder for the encoding of user uploaded videos.

 

VAB Crypto Currency

The Vab cryptocurrency is an ERC20 token on the ethereum blockchain.

We decided to use ethereum and ERC20 because of its proven smart contract technology.

Each account created on Vabble will have a wallet automatically created. This wallet will be funded with a small amount of VAB token as a good will gesture for joining. Users may then use this VAB to start watching video and tipping others if they would like to do so.

Each video on Vabble is auto monetized and VAB earned from users watching other users videos is auto deducted and deposited into a smart view contract. The smart view contract holds the generated VAB until the video receives 1000 views. Upon receiving 1000 views, VAB held in the smart contract is released to the video owner.

The amount deducted from a user for watching another users video is based on duration.

 

  • 0 -25% = 0.1
  • 25 – 50% = 0.3
  • 50 – 75% = 0.6
  • 75% – 100% = 0.9

The denominations above are only an example of how much a user will be deducted and how much a user will be paid. The exact %OfDurationWatched to VabEarned ratio will be released at a later date.

Users will also generate VAB cryptocurrency as reward via our unique ranking system. This image provides a brief explanation of how our ranking system works. Each rank receives an award in VAB cryptocurrency.

 

Vabble Ranking Reward System

There will be a total of 2 Billion VAB tokens generated of which 5% will be allocated to new user GoodWillGesture and RankReward. A further 4% of tokens will be held by the Vabble platform for ongoing developments, marketing and other outgoings such as server costs and employee wages. The remaining 91% and its allocation will be updated here at a later date.

 

Take a look at look at some of the Vabble Platform source code

Inspect if you wish.

PostBundle\Form\Type

 

<?php

namespace Vabble\Bundle\PostBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Sylius\Bundle\SettingsBundle\Manager\SettingsManagerInterface;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraints\NotBlank;
use Vabble\Bundle\PostBundle\Entity\Post;
use Symfony\Component\Validator\Constraints\File;
use Vabble\Component\DataTransformer\CleanedTextTransformer;

/**
 * Class EditPostType
 */
class PostType extends AbstractType
{
    /**
     * Settings manager.
     *
     * @var SettingsManagerInterface
     */
    private $settingsManager;

    /**
     * Token storage.
     *
     * @var TokenStorageInterface
     */
    private $tokenStorage;

    /**
     * Doctrine.
     *
     * @var EntityManager
     */
    private $doctrine;

    /**
     * Translator.
     *
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * Initialize post form type.
     *
     * @param SettingsManagerInterface $settingsManager
     */
    public function __construct(SettingsManagerInterface $settingsManager, TokenStorageInterface $tokenStorage, $doctrine, TranslatorInterface $translator)
    {
        $this->settingsManager = $settingsManager;
        $this->tokenStorage = $tokenStorage;
        $this->doctrine = $doctrine;
        $this->translator = $translator;
    }

    /**
     * {@inheritDoc }
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $genreSettings = $this->settingsManager->loadSettings('genre_settings');
        $globalSettings = $this->settingsManager->loadSettings('global_settings');

        $builder
            ->add('name', 'text', array(
                'label' => 'form.label.name',
                'required' => false,
                'translation_domain' => 'PostBundle',
                'attr' => array(
                    'ng-model' => 'vabble_post_form.name',
                )
            ))
            ->add('private', 'checkbox', array(
                'label' => 'form.label.private',
                'required' => false,
                'translation_domain' => 'PostBundle',
                'attr' => array(
                    'ng-model' => 'vabble_post_form.private',
                )
            ))
            ->add('body', 'textarea', array(
                'label' => 'form.label.body',
                'required' => false,
                'translation_domain' => 'PostBundle',
                'attr' => array(
                    'ng-model' => 'vabble_post_form.body',
                )
            ))
            ->add('owned', 'checkbox', array(
                'label' => 'form.label.owned',
                'required' => false,
                'translation_domain' => 'PostBundle',
                'attr' => array(
                    'ng-model' => 'vabble_post_form.owned',
                )
            ))
            ->add('genres', null, array(
                'label' => 'form.label.add_edit_genres',
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('g')
                        ->orderBy('g.name', 'ASC');
                },
                'choice_label' => 'name',
                'multiple' => true,
                'expanded' => false,
                'attr' => array(
                    'ng-model' => 'vabble_post_form.genres',
                    'min' => $genreSettings->get('genre_min_post_number'),
                    'max' => $genreSettings->get('genre_max_post_number'),
                ),
                'translation_domain' => 'PostBundle',
            ))
            ->add('mid', 'hidden', array(
                'mapped' => false,
                'attr' => array(
                    'ng-model' => 'vabble_post_form.mid',
                    'autocomplete' => 'off'
                ),
            ))
            ->add('provider', 'hidden', array(
                'mapped' => false,
                'attr' => array(
                    'ng-model' => 'vabble_post_form.provider',
                    'autocomplete' => 'off'
                ),
            ))
            ->add('mentions', 'hidden', array(
                'mapped' => false,
            ))
            ->add('thumb', 'file', array(
                'mapped' => false,
                'label' => 'form.label.thumb',
                'translation_domain' => "PostBundle",
                'attr' => array(
                    'class' => 'js-auto-upload'
                ),
                'required' => false,
                'constraints' => array(
                    new File(array(
                        "mimeTypes" => array('image/jpg', 'image/jpeg', 'image/png', 'image/x-png', 'image/gif'),
                        "mimeTypesMessage" => "post.thumb.image_mime",
                        "maxSize" => 20000000,
                        "maxSizeMessage" => "post.thumb.max_size"
                    ))
                ),
                'multiple' => false
            ))
            ->add('tid', 'hidden', array(
                'mapped' => false,
            ))
        ;

        // add data transformers
        $builder->get('name')->addModelTransformer(new CleanedTextTransformer());
        $builder->get('body')->addModelTransformer(new CleanedTextTransformer());

        $user = $this->tokenStorage->getToken()->getUser();
        if (!$user) {
            throw new \LogicException(
                'The Post form type cannot be used without an authenticated user!'
            );
        }

        $builder->addEventListener(FormEvents::POST_SUBMIT, function(FormEvent $event) use ($user, $globalSettings, $genreSettings){
            $form = $event->getForm();

            $provider = $form['provider']->getData();

            if ($provider) {

                if ($form['name']->getData() == null && $form['mid']->getData() == null) {
                    $error = new FormError($this->translator->trans(
                        'form.error.name.empty',
                        array(),
                        'PostBundle')
                    );

                    $form->addError($error);
                }


                if (count($form['genres']->getData()) > $genreSettings->get('genre_max_post_number')) {
                    $error = new FormError($this->translator->trans(
                        'form.limit.genres.max',
                        array('%max_limit%'=>$genreSettings->get('genre_max_post_number')),
                        'PostBundle')
                    );

                    $form->addError($error);
                }
                elseif (count($form['genres']->getData()) < $genreSettings->get('genre_min_post_number')) {
                    $error = new FormError($this->translator->trans(
                        'form.limit.genres.min',
                        array('%min_limit%'=>$genreSettings->get('genre_min_post_number')),
                        'PostBundle')
                    );

                    $form->addError($error);
                }

                if (in_array($provider, array(Post::PROVIDER_YOTUBE, Post::PROVIDER_VIMEO))) {
                    $postsCount = $this->doctrine->getRepository('PostBundle:Post')->getLinkedPostsByUserAndDate($user);

                    if ($postsCount >= $globalSettings->get('limit_share_video')) {
                        $error = new FormError($this->translator->trans(
                            'form.limit.share_video',
                            array('%share_limit%'=>$globalSettings->get('limit_share_video')),
                            'PostBundle')
                        );

                        $form->addError($error);
                    }
                }
            }

            // If title does not exist. validate body
            if($form['name']->getData() == null) {
                if ($form['body']->getData() == null && $form['mid']->getData() == null) {
                    $error = new FormError($this->translator->trans(
                        'form.error.body.empty',
                        array(),
                        'PostBundle')
                    );

                    $form->addError($error);
                }
            }
        });
    }

    /**
     * {@inheritDoc }
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => '\Vabble\Bundle\PostBundle\Entity\Post',
        ));
    }

    /**
     * {@inheritDoc }
     */
    public function getName()
    {
        return "vabble_post_form";
    }

}

SearchBunlde\Form\Search Type

 

<?php

namespace Vabble\Bundle\SearchBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\EntityRepository;

/**
 * Search filters form.
 */
class SearchFiltersFormType extends AbstractType
{
    /**
     * { @inheritdoc }
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('genre', 'entity', array(
                'class' => 'GenreBundle:Genre',
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('g')
                        ->orderBy('g.name', 'ASC');
                },
                'multiple' => true,
                'expanded' => false,
                'label' => 'search.filter.genre',
                'translation_domain' => 'WebBundle',
            ))
            ->add('country', 'country', array(
                'label' => 'search.filter.country',
                'multiple' => true,
                'expanded' => false,
                'placeholder' => 'profile.form.placeholder.country',
                'translation_domain' => 'WebBundle',
            ))
            ->add('subNmb', 'choice', array(
                'choices' => range(0, 1000, 100),
                'label' => 'search.filter.subscribers.number',
                'translation_domain' => 'WebBundle',
            ))
            ->add('follNmb', 'choice', array(
                'choices' => range(0, 1000, 100),
                'label' => 'search.filter.followers.number',
                'translation_domain' => 'WebBundle',
            ))
            ->add('timePeriod', 'choice', array(
                'choices' => array(
                    'day' => 'search.filter.time.period.choice.day',
                    'week' => 'search.filter.time.period.choice.week',
                    'month' => 'search.filter.time.period.choice.month',
                    'year' => 'search.filter.time.period.choice.year',
                ),
                'label' => 'search.filter.time.period.label',
                'translation_domain' => 'WebBundle',
                'choice_translation_domain' => 'WebBundle',
                'required' => false,
            ))
            ->add('rank', 'entity', array(
                'class' => 'UserBundle:Rank',
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('r')
                        ->orderBy('r.limit', 'ASC');
                },
                'multiple' => true,
                'expanded' => false,
                'label' => 'search.filter.rank',
                'translation_domain' => 'WebBundle',
            ))
            ->add('paid', 'choice', array(
                'choices' => array(
                    '' => 'search.filter.paid.choice.all',
                    0  => 'search.filter.paid.choice.free',
                    1  => 'search.filter.paid.choice.paid',
                ),
                'required' => false,
                'multiple' => false,
                'expanded' => false,
                'empty_data' => null,
                'label' => 'search.filter.paid.label',
                'translation_domain' => 'WebBundle',
                'choice_translation_domain' => 'WebBundle',
            ))
        ;
    }

    /**
     * { @inheritdoc }
     */
    public function getName()
    {
        return 'vabble_search_filters';
    }
}

 

Leave a Reply

Be the first to know when we go live!