From b9a3e09ad5a7673f64556d1dba122ed4c4fac980 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Mon, 7 Mar 2016 11:33:59 +0100 Subject: Prepare folders structure for angular app --- Gruntfile.js | 4 +- client/app/images/favicon.png | Bin 0 -> 2335 bytes client/app/images/loading.gif | Bin 0 -> 673 bytes client/app/javascripts/index.js | 245 +++++++ client/app/stylesheets/application.scss | 6 + client/app/stylesheets/base.scss | 20 + client/app/stylesheets/bootstrap-variables.scss | 875 ++++++++++++++++++++++++ client/app/stylesheets/index.scss | 88 +++ controllers/api/v1/index.js | 17 - controllers/api/v1/pods.js | 93 --- controllers/api/v1/remoteVideos.js | 53 -- controllers/api/v1/videos.js | 144 ---- controllers/index.js | 11 - controllers/views.js | 27 - helpers/customValidators.js | 32 - helpers/logger.js | 40 -- helpers/peertubeCrypto.js | 147 ---- helpers/requests.js | 109 --- helpers/utils.js | 16 - initializers/checker.js | 46 -- initializers/constants.js | 42 -- initializers/database.js | 29 - lib/friends.js | 228 ------ lib/poolRequests.js | 221 ------ lib/videos.js | 50 -- lib/webtorrent.js | 157 ----- lib/webtorrentProcess.js | 92 --- middlewares/cache.js | 23 - middlewares/index.js | 15 - middlewares/reqValidators/index.js | 15 - middlewares/reqValidators/pods.js | 39 -- middlewares/reqValidators/remote.js | 43 -- middlewares/reqValidators/utils.js | 25 - middlewares/reqValidators/videos.js | 74 -- middlewares/secure.js | 49 -- models/pods.js | 88 --- models/poolRequests.js | 55 -- models/videos.js | 234 ------- public/images/favicon.png | Bin 2335 -> 0 bytes public/images/loading.gif | Bin 673 -> 0 bytes public/javascripts/index.js | 245 ------- public/stylesheets/application.scss | 6 - public/stylesheets/base.scss | 20 - public/stylesheets/bootstrap-variables.scss | 875 ------------------------ public/stylesheets/index.scss | 88 --- server.js | 28 +- server/controllers/api/v1/index.js | 17 + server/controllers/api/v1/pods.js | 93 +++ server/controllers/api/v1/remoteVideos.js | 53 ++ server/controllers/api/v1/videos.js | 144 ++++ server/controllers/index.js | 11 + server/controllers/views.js | 27 + server/helpers/customValidators.js | 32 + server/helpers/logger.js | 40 ++ server/helpers/peertubeCrypto.js | 147 ++++ server/helpers/requests.js | 109 +++ server/helpers/utils.js | 16 + server/initializers/checker.js | 46 ++ server/initializers/constants.js | 42 ++ server/initializers/database.js | 29 + server/lib/friends.js | 228 ++++++ server/lib/poolRequests.js | 221 ++++++ server/lib/videos.js | 50 ++ server/lib/webtorrent.js | 157 +++++ server/lib/webtorrentProcess.js | 92 +++ server/middlewares/cache.js | 23 + server/middlewares/index.js | 15 + server/middlewares/reqValidators/index.js | 15 + server/middlewares/reqValidators/pods.js | 39 ++ server/middlewares/reqValidators/remote.js | 43 ++ server/middlewares/reqValidators/utils.js | 25 + server/middlewares/reqValidators/videos.js | 74 ++ server/middlewares/secure.js | 49 ++ server/models/pods.js | 88 +++ server/models/poolRequests.js | 55 ++ server/models/videos.js | 234 +++++++ server/tests/api/checkParams.js | 300 ++++++++ server/tests/api/fixtures/video_short.mp4 | Bin 0 -> 38783 bytes server/tests/api/fixtures/video_short.ogv | Bin 0 -> 140849 bytes server/tests/api/fixtures/video_short.webm | Bin 0 -> 218910 bytes server/tests/api/fixtures/video_short1.webm | Bin 0 -> 572456 bytes server/tests/api/fixtures/video_short2.webm | Bin 0 -> 942961 bytes server/tests/api/fixtures/video_short3.webm | Bin 0 -> 292677 bytes server/tests/api/fixtures/video_short_fake.webm | 1 + server/tests/api/friendsAdvanced.js | 250 +++++++ server/tests/api/friendsBasic.js | 185 +++++ server/tests/api/index.js | 8 + server/tests/api/multiplePods.js | 328 +++++++++ server/tests/api/singlePod.js | 146 ++++ server/tests/api/utils.js | 185 +++++ server/tests/index.js | 6 + tests/api/checkParams.js | 300 -------- tests/api/fixtures/video_short.mp4 | Bin 38783 -> 0 bytes tests/api/fixtures/video_short.ogv | Bin 140849 -> 0 bytes tests/api/fixtures/video_short.webm | Bin 218910 -> 0 bytes tests/api/fixtures/video_short1.webm | Bin 572456 -> 0 bytes tests/api/fixtures/video_short2.webm | Bin 942961 -> 0 bytes tests/api/fixtures/video_short3.webm | Bin 292677 -> 0 bytes tests/api/fixtures/video_short_fake.webm | 1 - tests/api/friendsAdvanced.js | 250 ------- tests/api/friendsBasic.js | 185 ----- tests/api/index.js | 8 - tests/api/multiplePods.js | 328 --------- tests/api/singlePod.js | 146 ---- tests/api/utils.js | 185 ----- tests/index.js | 6 - views/error.jade | 8 - views/footer.jade | 3 - views/header.jade | 9 - views/index.jade | 15 - views/layout.jade | 15 - views/panel.jade | 17 - 112 files changed, 4870 insertions(+), 4943 deletions(-) create mode 100644 client/app/images/favicon.png create mode 100644 client/app/images/loading.gif create mode 100644 client/app/javascripts/index.js create mode 100644 client/app/stylesheets/application.scss create mode 100644 client/app/stylesheets/base.scss create mode 100644 client/app/stylesheets/bootstrap-variables.scss create mode 100644 client/app/stylesheets/index.scss delete mode 100644 controllers/api/v1/index.js delete mode 100644 controllers/api/v1/pods.js delete mode 100644 controllers/api/v1/remoteVideos.js delete mode 100644 controllers/api/v1/videos.js delete mode 100644 controllers/index.js delete mode 100644 controllers/views.js delete mode 100644 helpers/customValidators.js delete mode 100644 helpers/logger.js delete mode 100644 helpers/peertubeCrypto.js delete mode 100644 helpers/requests.js delete mode 100644 helpers/utils.js delete mode 100644 initializers/checker.js delete mode 100644 initializers/constants.js delete mode 100644 initializers/database.js delete mode 100644 lib/friends.js delete mode 100644 lib/poolRequests.js delete mode 100644 lib/videos.js delete mode 100644 lib/webtorrent.js delete mode 100644 lib/webtorrentProcess.js delete mode 100644 middlewares/cache.js delete mode 100644 middlewares/index.js delete mode 100644 middlewares/reqValidators/index.js delete mode 100644 middlewares/reqValidators/pods.js delete mode 100644 middlewares/reqValidators/remote.js delete mode 100644 middlewares/reqValidators/utils.js delete mode 100644 middlewares/reqValidators/videos.js delete mode 100644 middlewares/secure.js delete mode 100644 models/pods.js delete mode 100644 models/poolRequests.js delete mode 100644 models/videos.js delete mode 100644 public/images/favicon.png delete mode 100644 public/images/loading.gif delete mode 100644 public/javascripts/index.js delete mode 100644 public/stylesheets/application.scss delete mode 100644 public/stylesheets/base.scss delete mode 100644 public/stylesheets/bootstrap-variables.scss delete mode 100644 public/stylesheets/index.scss create mode 100644 server/controllers/api/v1/index.js create mode 100644 server/controllers/api/v1/pods.js create mode 100644 server/controllers/api/v1/remoteVideos.js create mode 100644 server/controllers/api/v1/videos.js create mode 100644 server/controllers/index.js create mode 100644 server/controllers/views.js create mode 100644 server/helpers/customValidators.js create mode 100644 server/helpers/logger.js create mode 100644 server/helpers/peertubeCrypto.js create mode 100644 server/helpers/requests.js create mode 100644 server/helpers/utils.js create mode 100644 server/initializers/checker.js create mode 100644 server/initializers/constants.js create mode 100644 server/initializers/database.js create mode 100644 server/lib/friends.js create mode 100644 server/lib/poolRequests.js create mode 100644 server/lib/videos.js create mode 100644 server/lib/webtorrent.js create mode 100644 server/lib/webtorrentProcess.js create mode 100644 server/middlewares/cache.js create mode 100644 server/middlewares/index.js create mode 100644 server/middlewares/reqValidators/index.js create mode 100644 server/middlewares/reqValidators/pods.js create mode 100644 server/middlewares/reqValidators/remote.js create mode 100644 server/middlewares/reqValidators/utils.js create mode 100644 server/middlewares/reqValidators/videos.js create mode 100644 server/middlewares/secure.js create mode 100644 server/models/pods.js create mode 100644 server/models/poolRequests.js create mode 100644 server/models/videos.js create mode 100644 server/tests/api/checkParams.js create mode 100644 server/tests/api/fixtures/video_short.mp4 create mode 100644 server/tests/api/fixtures/video_short.ogv create mode 100644 server/tests/api/fixtures/video_short.webm create mode 100644 server/tests/api/fixtures/video_short1.webm create mode 100644 server/tests/api/fixtures/video_short2.webm create mode 100644 server/tests/api/fixtures/video_short3.webm create mode 100644 server/tests/api/fixtures/video_short_fake.webm create mode 100644 server/tests/api/friendsAdvanced.js create mode 100644 server/tests/api/friendsBasic.js create mode 100644 server/tests/api/index.js create mode 100644 server/tests/api/multiplePods.js create mode 100644 server/tests/api/singlePod.js create mode 100644 server/tests/api/utils.js create mode 100644 server/tests/index.js delete mode 100644 tests/api/checkParams.js delete mode 100644 tests/api/fixtures/video_short.mp4 delete mode 100644 tests/api/fixtures/video_short.ogv delete mode 100644 tests/api/fixtures/video_short.webm delete mode 100644 tests/api/fixtures/video_short1.webm delete mode 100644 tests/api/fixtures/video_short2.webm delete mode 100644 tests/api/fixtures/video_short3.webm delete mode 100644 tests/api/fixtures/video_short_fake.webm delete mode 100644 tests/api/friendsAdvanced.js delete mode 100644 tests/api/friendsBasic.js delete mode 100644 tests/api/index.js delete mode 100644 tests/api/multiplePods.js delete mode 100644 tests/api/singlePod.js delete mode 100644 tests/api/utils.js delete mode 100644 tests/index.js delete mode 100644 views/error.jade delete mode 100644 views/footer.jade delete mode 100644 views/header.jade delete mode 100644 views/index.jade delete mode 100644 views/layout.jade delete mode 100644 views/panel.jade diff --git a/Gruntfile.js b/Gruntfile.js index 6df0c023a..5aaa13dff 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -2,13 +2,11 @@ module.exports = function (grunt) { var paths = { - dist: 'dist', - jade: 'views/**/**/*.jade', css: 'public/stylesheets/*.css', scss: 'public/stylesheets/application.scss', vendor: 'public/stylesheets/vendor', js: 'public/javascripts/*.js', - routes: 'controllers/**/*.js', + routes: './server/controllers/**/*.js', main: './server.js', browserified: 'public/javascripts/bundle.js', img: 'public/images/*.{png,jpg,jpeg,gif,webp,svg}', diff --git a/client/app/images/favicon.png b/client/app/images/favicon.png new file mode 100644 index 000000000..bb57ee6b0 Binary files /dev/null and b/client/app/images/favicon.png differ diff --git a/client/app/images/loading.gif b/client/app/images/loading.gif new file mode 100644 index 000000000..f2a1bc0c6 Binary files /dev/null and b/client/app/images/loading.gif differ diff --git a/client/app/javascripts/index.js b/client/app/javascripts/index.js new file mode 100644 index 000000000..4910e4540 --- /dev/null +++ b/client/app/javascripts/index.js @@ -0,0 +1,245 @@ +;(function () { + 'use strict' + + var $ = require('jquery') + require('blueimp-file-upload') + + var WebTorrent = require('webtorrent') + var client = new WebTorrent({ dht: false }) + + var $content = $('#ajax_load') + + // Webtorrent events + client.on('error', function (err) { + console.error(err) + }) + + client.on('warning', function (err) { + console.warning(err) + }) + + // Events of the panel + $('#panel_get_videos').on('click', function () { + getVideos() + }) + + $('#panel_upload_video').on('click', function () { + uploadVideo() + }) + + $('#panel_make_friends').on('click', function () { + makeFriends() + }) + + $('#panel_quit_friends').on('click', function () { + quitFriends() + }) + + $('#search-video').on('keyup', function (e) { + var search = $(this).val() + + if (search === '') return + + if (e.keyCode === 13) { + $.ajax({ + url: '/api/v1/videos/search/' + search, + type: 'GET', + dataType: 'json', + success: function (videos) { + printVideos(videos) + } + }) + } + }) + + // Join a new network + function makeFriends () { + $.ajax({ + url: '/api/v1/pods/makefriends', + type: 'GET', + dataType: 'json', + statusCode: { + 409: function () { + alert('Already made friends.') + } + }, + success: function () { + alert('Made friends!') + } + }) + } + + function quitFriends () { + $.ajax({ + url: '/api/v1/pods/quitfriends', + type: 'GET', + dataType: 'json', + success: function () { + alert('Quit friends!') + } + }) + } + + function printVideos (videos) { + $content.empty() + + if (videos.length === 0) { + $content.text('There is no videos.') + } + + videos.forEach(function (video) { + var $video = $('
').addClass('video') + + var $video_name = $('').addClass('video_name').text(video.name) + var $video_pod = $('').addClass('video_pod_url').text(video.podUrl) + var $header = $('
').append([ $video_name, $video_pod ]) + + if (video.namePath !== null) { + var $remove = $('').addClass('span_action glyphicon glyphicon-remove') + + // Remove the video + $remove.on('click', function () { + if (!confirm('Are you sure ?')) return + + removeVideo(video) + }) + + $header.append($remove) + } + + var $video_description = $('
').addClass('video_description').text(video.description) + + // Get the video + $video_name.on('click', function () { + getVideo(video) + }) + + if (!video.magnetUri) { + $remove.css('display', 'none') + } + + $video.append([ $header, $video_description ]) + $content.append($video) + }) + } + + // Upload the video, the server will seed it + function uploadVideo () { + // Creating all the elements + var $video_label = $('').attr('for', 'name').text('Video name') + var $video_name = $('').addClass('form-control').attr({ + name: 'name', + id: 'name' + }) + var $video_block = $('
').addClass('form-group').append([ $video_label, $video_name ]) + + var $title = $('

').text('Upload a video') + + var $button_text = $('').text('Select the video...') + var $input_video = $('').attr({ + type: 'file', + name: 'input_video', + id: 'input_video' + }) + var $button = $('
').addClass('btn btn-default btn-file').append([ $button_text, $input_video ]) + + var $description_label = $('').attr('for', 'description').text('Description') + var $description_text = $('').addClass('form-control').attr({ + name: 'description', + id: 'description', + placeholder: 'Description...' + }) + var $description = $('
').addClass('form-group').append([ $description_label, $description_text ]) + + var $bar = $('
').attr('id', 'progress').append($bar) + + var $input_submit = $('').addClass('btn btn-default').attr({ + type: 'button', + value: 'Upload' + }) + + // JQuery plugin + var $form_video = $('
').append([ $video_block, $button, $progress_bar, $description, $input_submit ]) + $form_video.fileupload({ + singleFileUploads: true, + multipart: true, + url: '/api/v1/videos', + autoupload: false, + add: function (e, data) { + var $text = $('').addClass('name_file').text(data['files'][0]['name']) + $text.insertAfter($button) + $input_submit.off('click').on('click', function () { + $bar.css('display', 'block') + data.formData = $form_video.serializeArray() + data.submit() + }) + }, + progressall: function (e, data) { + $bar.attr({ + value: data.loaded, + max: data.total + }) + }, + done: function (e, data) { + // Print all the videos once it's finished + getVideos() + } + }) + + $content.empty() + $content.append([ $title, $form_video ]) + } + + // Print the list of all the videos + function getVideos () { + $.ajax({ + url: '/api/v1/videos/', + dataType: 'json', + type: 'GET', + success: function (videos) { + printVideos(videos) + } + }) + } + + function removeVideo (video) { + $.ajax({ + url: '/api/v1/videos/' + video._id, + type: 'DELETE', + success: function (response, status) { + getVideos() + } + }) + } + + // Get the video: add the torrent file and stream it into a video tag + function getVideo (video) { + var $waiting = $('').addClass('center-block loading').attr('src', '/images/loading.gif') + $content.empty() + $content.append($waiting) + + console.log('Getting ' + video) + client.add(video.magnetUri, function (torrent) { + var $embed = $('
').addClass('embed-responsive embed-responsive-16by9') + + $content.empty() + $content.append($embed) + + // Got torrent metadata! + console.log('Torrent info hash:', torrent.infoHash) + + // Let's say the first file is a webm (vp8) or mp4 (h264) video... + var file = torrent.files[0] + + file.appendTo($embed.get(0), function (err) { + if (err) { + alert('Cannot append the file.') + console.error(err) + } + }) + }) + } + + getVideos() +})() diff --git a/client/app/stylesheets/application.scss b/client/app/stylesheets/application.scss new file mode 100644 index 000000000..bf9ec90ab --- /dev/null +++ b/client/app/stylesheets/application.scss @@ -0,0 +1,6 @@ +$icon-font-path: "/stylesheets/vendor/fonts/bootstrap/"; + +@import "bootstrap-variables"; +@import "_bootstrap"; +@import "base"; +@import "index"; \ No newline at end of file diff --git a/client/app/stylesheets/base.scss b/client/app/stylesheets/base.scss new file mode 100644 index 000000000..37bdade1e --- /dev/null +++ b/client/app/stylesheets/base.scss @@ -0,0 +1,20 @@ +body { + padding: 20px; +} + +footer { + border-top: 1px solid rgba(0, 0, 0, 0.2); + padding-top: 10px; + text-align: center; + font-size: small; +} + +.search-group { + .search-btn { + position: relative; + left: -40px; + top: 0; + + &:hover { text-decoration: none; } + } +} \ No newline at end of file diff --git a/client/app/stylesheets/bootstrap-variables.scss b/client/app/stylesheets/bootstrap-variables.scss new file mode 100644 index 000000000..5a49649f9 --- /dev/null +++ b/client/app/stylesheets/bootstrap-variables.scss @@ -0,0 +1,875 @@ +// Override Bootstrap variables here (defaults from bootstrap-sass v3.3.6): + +// +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +// $gray-base: #000 +// $gray-darker: lighten($gray-base, 13.5%) // #222 +// $gray-dark: lighten($gray-base, 20%) // #333 +// $gray: lighten($gray-base, 33.5%) // #555 +// $gray-light: lighten($gray-base, 46.7%) // #777 +// $gray-lighter: lighten($gray-base, 93.5%) // #eee + +// $brand-primary: darken(#428bca, 6.5%) // #337ab7 +// $brand-success: #5cb85c +// $brand-info: #5bc0de +// $brand-warning: #f0ad4e +// $brand-danger: #d9534f + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for ``. +// $body-bg: #fff +//** Global text color on ``. +// $text-color: $gray-dark + +//** Global textual link color. +// $link-color: $brand-primary +//** Link hover color set via `darken()` function. +// $link-hover-color: darken($link-color, 15%) +//** Link hover decoration. +// $link-hover-decoration: underline + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +// $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif +// $font-family-serif: Georgia, "Times New Roman", Times, serif +//** Default monospace fonts for ``, ``, and `
`.
+// $font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace
+// $font-family-base:        $font-family-sans-serif
+
+// $font-size-base:          14px
+// $font-size-large:         ceil(($font-size-base * 1.25)) // ~18px
+// $font-size-small:         ceil(($font-size-base * 0.85)) // ~12px
+
+// $font-size-h1:            floor(($font-size-base * 2.6)) // ~36px
+// $font-size-h2:            floor(($font-size-base * 2.15)) // ~30px
+// $font-size-h3:            ceil(($font-size-base * 1.7)) // ~24px
+// $font-size-h4:            ceil(($font-size-base * 1.25)) // ~18px
+// $font-size-h5:            $font-size-base
+// $font-size-h6:            ceil(($font-size-base * 0.85)) // ~12px
+
+//** Unit-less `line-height` for use in components like buttons.
+// $line-height-base:        1.428571429 // 20/14
+//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
+// $line-height-computed:    floor(($font-size-base * $line-height-base)) // ~20px
+
+//** By default, this inherits from the ``.
+// $headings-font-family:    inherit
+// $headings-font-weight:    500
+// $headings-line-height:    1.1
+// $headings-color:          inherit
+
+
+//== Iconography
+//
+//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
+
+//** Load fonts from this directory.
+
+// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
+// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
+// $icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/")
+
+//** File name for all font files.
+// $icon-font-name:          "glyphicons-halflings-regular"
+//** Element ID within SVG icon file.
+// $icon-font-svg-id:        "glyphicons_halflingsregular"
+
+
+//== Components
+//
+//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
+
+// $padding-base-vertical:     6px
+// $padding-base-horizontal:   12px
+
+// $padding-large-vertical:    10px
+// $padding-large-horizontal:  16px
+
+// $padding-small-vertical:    5px
+// $padding-small-horizontal:  10px
+
+// $padding-xs-vertical:       1px
+// $padding-xs-horizontal:     5px
+
+// $line-height-large:         1.3333333 // extra decimals for Win 8.1 Chrome
+// $line-height-small:         1.5
+
+$border-radius-base:        0;
+$border-radius-large:       0;
+$border-radius-small:       0;
+
+//** Global color for active items (e.g., navs or dropdowns).
+// $component-active-color:    #fff
+//** Global background color for active items (e.g., navs or dropdowns).
+// $component-active-bg:       $brand-primary
+
+//** Width of the `border` for generating carets that indicator dropdowns.
+// $caret-width-base:          4px
+//** Carets increase slightly in size for larger components.
+// $caret-width-large:         5px
+
+
+//== Tables
+//
+//## Customizes the `.table` component with basic values, each used across all table variations.
+
+//** Padding for ``s and ``s.
+// $table-cell-padding:            8px
+//** Padding for cells in `.table-condensed`.
+// $table-condensed-cell-padding:  5px
+
+//** Default background color used for all tables.
+// $table-bg:                      transparent
+//** Background color used for `.table-striped`.
+// $table-bg-accent:               #f9f9f9
+//** Background color used for `.table-hover`.
+// $table-bg-hover:                #f5f5f5
+// $table-bg-active:               $table-bg-hover
+
+//** Border color for table and cell borders.
+// $table-border-color:            #ddd
+
+
+//== Buttons
+//
+//## For each of Bootstrap's buttons, define text, background and border color.
+
+// $btn-font-weight:                normal
+
+// $btn-default-color:              #333
+// $btn-default-bg:                 #fff
+// $btn-default-border:             #ccc
+
+// $btn-primary-color:              #fff
+// $btn-primary-bg:                 $brand-primary
+// $btn-primary-border:             darken($btn-primary-bg, 5%)
+
+// $btn-success-color:              #fff
+// $btn-success-bg:                 $brand-success
+// $btn-success-border:             darken($btn-success-bg, 5%)
+
+// $btn-info-color:                 #fff
+// $btn-info-bg:                    $brand-info
+// $btn-info-border:                darken($btn-info-bg, 5%)
+
+// $btn-warning-color:              #fff
+// $btn-warning-bg:                 $brand-warning
+// $btn-warning-border:             darken($btn-warning-bg, 5%)
+
+// $btn-danger-color:               #fff
+// $btn-danger-bg:                  $brand-danger
+// $btn-danger-border:              darken($btn-danger-bg, 5%)
+
+// $btn-link-disabled-color:        $gray-light
+
+// Allows for customizing button radius independently from global border radius
+// $btn-border-radius-base:         $border-radius-base
+// $btn-border-radius-large:        $border-radius-large
+// $btn-border-radius-small:        $border-radius-small
+
+
+//== Forms
+//
+//##
+
+//** `` background color
+// $input-bg:                       #fff
+//** `` background color
+// $input-bg-disabled:              $gray-lighter
+
+//** Text color for ``s
+// $input-color:                    $gray
+//** `` border color
+// $input-border:                   #ccc
+
+// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
+//** Default `.form-control` border radius
+// This has no effect on ``s in CSS.
+// $input-border-radius:            $border-radius-base
+//** Large `.form-control` border radius
+// $input-border-radius-large:      $border-radius-large
+//** Small `.form-control` border radius
+// $input-border-radius-small:      $border-radius-small
+
+//** Border color for inputs on focus
+// $input-border-focus:             #66afe9
+
+//** Placeholder text color
+// $input-color-placeholder:        #999
+
+//** Default `.form-control` height
+// $input-height-base:              ($line-height-computed + ($padding-base-vertical * 2) + 2)
+//** Large `.form-control` height
+// $input-height-large:             (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2)
+//** Small `.form-control` height
+// $input-height-small:             (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2)
+
+//** `.form-group` margin
+// $form-group-margin-bottom:       15px
+
+// $legend-color:                   $gray-dark
+// $legend-border-color:            #e5e5e5
+
+//** Background color for textual input addons
+// $input-group-addon-bg:           $gray-lighter
+//** Border color for textual input addons
+// $input-group-addon-border-color: $input-border
+
+//** Disabled cursor for form controls and buttons.
+// $cursor-disabled:                not-allowed
+
+
+//== Dropdowns
+//
+//## Dropdown menu container and contents.
+
+//** Background for the dropdown menu.
+// $dropdown-bg:                    #fff
+//** Dropdown menu `border-color`.
+// $dropdown-border:                rgba(0,0,0,.15)
+//** Dropdown menu `border-color` **for IE8**.
+// $dropdown-fallback-border:       #ccc
+//** Divider color for between dropdown items.
+// $dropdown-divider-bg:            #e5e5e5
+
+//** Dropdown link text color.
+// $dropdown-link-color:            $gray-dark
+//** Hover color for dropdown links.
+// $dropdown-link-hover-color:      darken($gray-dark, 5%)
+//** Hover background for dropdown links.
+// $dropdown-link-hover-bg:         #f5f5f5
+
+//** Active dropdown menu item text color.
+// $dropdown-link-active-color:     $component-active-color
+//** Active dropdown menu item background color.
+// $dropdown-link-active-bg:        $component-active-bg
+
+//** Disabled dropdown menu item background color.
+// $dropdown-link-disabled-color:   $gray-light
+
+//** Text color for headers within dropdown menus.
+// $dropdown-header-color:          $gray-light
+
+//** Deprecated `$dropdown-caret-color` as of v3.1.0
+// $dropdown-caret-color:           #000
+
+
+//-- Z-index master list
+//
+// Warning: Avoid customizing these values. They're used for a bird's eye view
+// of components dependent on the z-axis and are designed to all work together.
+//
+// Note: These variables are not generated into the Customizer.
+
+// $zindex-navbar:            1000
+// $zindex-dropdown:          1000
+// $zindex-popover:           1060
+// $zindex-tooltip:           1070
+// $zindex-navbar-fixed:      1030
+// $zindex-modal-background:  1040
+// $zindex-modal:             1050
+
+
+//== Media queries breakpoints
+//
+//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
+
+// Extra small screen / phone
+//** Deprecated `$screen-xs` as of v3.0.1
+// $screen-xs:                  480px
+//** Deprecated `$screen-xs-min` as of v3.2.0
+// $screen-xs-min:              $screen-xs
+//** Deprecated `$screen-phone` as of v3.0.1
+// $screen-phone:               $screen-xs-min
+
+// Small screen / tablet
+//** Deprecated `$screen-sm` as of v3.0.1
+// $screen-sm:                  768px
+// $screen-sm-min:              $screen-sm
+//** Deprecated `$screen-tablet` as of v3.0.1
+// $screen-tablet:              $screen-sm-min
+
+// Medium screen / desktop
+//** Deprecated `$screen-md` as of v3.0.1
+// $screen-md:                  992px
+// $screen-md-min:              $screen-md
+//** Deprecated `$screen-desktop` as of v3.0.1
+// $screen-desktop:             $screen-md-min
+
+// Large screen / wide desktop
+//** Deprecated `$screen-lg` as of v3.0.1
+// $screen-lg:                  1200px
+// $screen-lg-min:              $screen-lg
+//** Deprecated `$screen-lg-desktop` as of v3.0.1
+// $screen-lg-desktop:          $screen-lg-min
+
+// So media queries don't overlap when required, provide a maximum
+// $screen-xs-max:              ($screen-sm-min - 1)
+// $screen-sm-max:              ($screen-md-min - 1)
+// $screen-md-max:              ($screen-lg-min - 1)
+
+
+//== Grid system
+//
+//## Define your custom responsive grid.
+
+//** Number of columns in the grid.
+// $grid-columns:              12
+//** Padding between columns. Gets divided in half for the left and right.
+// $grid-gutter-width:         30px
+// Navbar collapse
+//** Point at which the navbar becomes uncollapsed.
+// $grid-float-breakpoint:     $screen-sm-min
+//** Point at which the navbar begins collapsing.
+// $grid-float-breakpoint-max: ($grid-float-breakpoint - 1)
+
+
+//== Container sizes
+//
+//## Define the maximum width of `.container` for different screen sizes.
+
+// Small screen / tablet
+// $container-tablet:             (720px + $grid-gutter-width)
+//** For `$screen-sm-min` and up.
+// $container-sm:                 $container-tablet
+
+// Medium screen / desktop
+// $container-desktop:            (940px + $grid-gutter-width)
+//** For `$screen-md-min` and up.
+// $container-md:                 $container-desktop
+
+// Large screen / wide desktop
+// $container-large-desktop:      (1140px + $grid-gutter-width)
+//** For `$screen-lg-min` and up.
+// $container-lg:                 $container-large-desktop
+
+
+//== Navbar
+//
+//##
+
+// Basics of a navbar
+// $navbar-height:                    50px
+// $navbar-margin-bottom:             $line-height-computed
+// $navbar-border-radius:             $border-radius-base
+// $navbar-padding-horizontal:        floor(($grid-gutter-width / 2))
+// $navbar-padding-vertical:          (($navbar-height - $line-height-computed) / 2)
+// $navbar-collapse-max-height:       340px
+
+// $navbar-default-color:             #777
+// $navbar-default-bg:                #f8f8f8
+// $navbar-default-border:            darken($navbar-default-bg, 6.5%)
+
+// Navbar links
+// $navbar-default-link-color:                #777
+// $navbar-default-link-hover-color:          #333
+// $navbar-default-link-hover-bg:             transparent
+// $navbar-default-link-active-color:         #555
+// $navbar-default-link-active-bg:            darken($navbar-default-bg, 6.5%)
+// $navbar-default-link-disabled-color:       #ccc
+// $navbar-default-link-disabled-bg:          transparent
+
+// Navbar brand label
+// $navbar-default-brand-color:               $navbar-default-link-color
+// $navbar-default-brand-hover-color:         darken($navbar-default-brand-color, 10%)
+// $navbar-default-brand-hover-bg:            transparent
+
+// Navbar toggle
+// $navbar-default-toggle-hover-bg:           #ddd
+// $navbar-default-toggle-icon-bar-bg:        #888
+// $navbar-default-toggle-border-color:       #ddd
+
+
+//=== Inverted navbar
+// Reset inverted navbar basics
+// $navbar-inverse-color:                      lighten($gray-light, 15%)
+// $navbar-inverse-bg:                         #222
+// $navbar-inverse-border:                     darken($navbar-inverse-bg, 10%)
+
+// Inverted navbar links
+// $navbar-inverse-link-color:                 lighten($gray-light, 15%)
+// $navbar-inverse-link-hover-color:           #fff
+// $navbar-inverse-link-hover-bg:              transparent
+// $navbar-inverse-link-active-color:          $navbar-inverse-link-hover-color
+// $navbar-inverse-link-active-bg:             darken($navbar-inverse-bg, 10%)
+// $navbar-inverse-link-disabled-color:        #444
+// $navbar-inverse-link-disabled-bg:           transparent
+
+// Inverted navbar brand label
+// $navbar-inverse-brand-color:                $navbar-inverse-link-color
+// $navbar-inverse-brand-hover-color:          #fff
+// $navbar-inverse-brand-hover-bg:             transparent
+
+// Inverted navbar toggle
+// $navbar-inverse-toggle-hover-bg:            #333
+// $navbar-inverse-toggle-icon-bar-bg:         #fff
+// $navbar-inverse-toggle-border-color:        #333
+
+
+//== Navs
+//
+//##
+
+//=== Shared nav styles
+// $nav-link-padding:                          10px 15px
+// $nav-link-hover-bg:                         $gray-lighter
+
+// $nav-disabled-link-color:                   $gray-light
+// $nav-disabled-link-hover-color:             $gray-light
+
+//== Tabs
+// $nav-tabs-border-color:                     #ddd
+
+// $nav-tabs-link-hover-border-color:          $gray-lighter
+
+// $nav-tabs-active-link-hover-bg:             $body-bg
+// $nav-tabs-active-link-hover-color:          $gray
+// $nav-tabs-active-link-hover-border-color:   #ddd
+
+// $nav-tabs-justified-link-border-color:            #ddd
+// $nav-tabs-justified-active-link-border-color:     $body-bg
+
+//== Pills
+// $nav-pills-border-radius:                   $border-radius-base
+// $nav-pills-active-link-hover-bg:            $component-active-bg
+// $nav-pills-active-link-hover-color:         $component-active-color
+
+
+//== Pagination
+//
+//##
+
+// $pagination-color:                     $link-color
+// $pagination-bg:                        #fff
+// $pagination-border:                    #ddd
+
+// $pagination-hover-color:               $link-hover-color
+// $pagination-hover-bg:                  $gray-lighter
+// $pagination-hover-border:              #ddd
+
+// $pagination-active-color:              #fff
+// $pagination-active-bg:                 $brand-primary
+// $pagination-active-border:             $brand-primary
+
+// $pagination-disabled-color:            $gray-light
+// $pagination-disabled-bg:               #fff
+// $pagination-disabled-border:           #ddd
+
+
+//== Pager
+//
+//##
+
+// $pager-bg:                             $pagination-bg
+// $pager-border:                         $pagination-border
+// $pager-border-radius:                  15px
+
+// $pager-hover-bg:                       $pagination-hover-bg
+
+// $pager-active-bg:                      $pagination-active-bg
+// $pager-active-color:                   $pagination-active-color
+
+// $pager-disabled-color:                 $pagination-disabled-color
+
+
+//== Jumbotron
+//
+//##
+
+// $jumbotron-padding:              30px
+// $jumbotron-color:                inherit
+// $jumbotron-bg:                   $gray-lighter
+// $jumbotron-heading-color:        inherit
+// $jumbotron-font-size:            ceil(($font-size-base * 1.5))
+// $jumbotron-heading-font-size:    ceil(($font-size-base * 4.5))
+
+
+//== Form states and alerts
+//
+//## Define colors for form feedback states and, by default, alerts.
+
+// $state-success-text:             #3c763d
+// $state-success-bg:               #dff0d8
+// $state-success-border:           darken(adjust-hue($state-success-bg, -10), 5%)
+
+// $state-info-text:                #31708f
+// $state-info-bg:                  #d9edf7
+// $state-info-border:              darken(adjust-hue($state-info-bg, -10), 7%)
+
+// $state-warning-text:             #8a6d3b
+// $state-warning-bg:               #fcf8e3
+// $state-warning-border:           darken(adjust-hue($state-warning-bg, -10), 5%)
+
+// $state-danger-text:              #a94442
+// $state-danger-bg:                #f2dede
+// $state-danger-border:            darken(adjust-hue($state-danger-bg, -10), 5%)
+
+
+//== Tooltips
+//
+//##
+
+//** Tooltip max width
+// $tooltip-max-width:           200px
+//** Tooltip text color
+// $tooltip-color:               #fff
+//** Tooltip background color
+// $tooltip-bg:                  #000
+// $tooltip-opacity:             .9
+
+//** Tooltip arrow width
+// $tooltip-arrow-width:         5px
+//** Tooltip arrow color
+// $tooltip-arrow-color:         $tooltip-bg
+
+
+//== Popovers
+//
+//##
+
+//** Popover body background color
+// $popover-bg:                          #fff
+//** Popover maximum width
+// $popover-max-width:                   276px
+//** Popover border color
+// $popover-border-color:                rgba(0,0,0,.2)
+//** Popover fallback border color
+// $popover-fallback-border-color:       #ccc
+
+//** Popover title background color
+// $popover-title-bg:                    darken($popover-bg, 3%)
+
+//** Popover arrow width
+// $popover-arrow-width:                 10px
+//** Popover arrow color
+// $popover-arrow-color:                 $popover-bg
+
+//** Popover outer arrow width
+// $popover-arrow-outer-width:           ($popover-arrow-width + 1)
+//** Popover outer arrow color
+// $popover-arrow-outer-color:           fade_in($popover-border-color, 0.05)
+//** Popover outer arrow fallback color
+// $popover-arrow-outer-fallback-color:  darken($popover-fallback-border-color, 20%)
+
+
+//== Labels
+//
+//##
+
+//** Default label background color
+// $label-default-bg:            $gray-light
+//** Primary label background color
+// $label-primary-bg:            $brand-primary
+//** Success label background color
+// $label-success-bg:            $brand-success
+//** Info label background color
+// $label-info-bg:               $brand-info
+//** Warning label background color
+// $label-warning-bg:            $brand-warning
+//** Danger label background color
+// $label-danger-bg:             $brand-danger
+
+//** Default label text color
+// $label-color:                 #fff
+//** Default text color of a linked label
+// $label-link-hover-color:      #fff
+
+
+//== Modals
+//
+//##
+
+//** Padding applied to the modal body
+// $modal-inner-padding:         15px
+
+//** Padding applied to the modal title
+// $modal-title-padding:         15px
+//** Modal title line-height
+// $modal-title-line-height:     $line-height-base
+
+//** Background color of modal content area
+// $modal-content-bg:                             #fff
+//** Modal content border color
+// $modal-content-border-color:                   rgba(0,0,0,.2)
+//** Modal content border color **for IE8**
+// $modal-content-fallback-border-color:          #999
+
+//** Modal backdrop background color
+// $modal-backdrop-bg:           #000
+//** Modal backdrop opacity
+// $modal-backdrop-opacity:      .5
+//** Modal header border color
+// $modal-header-border-color:   #e5e5e5
+//** Modal footer border color
+// $modal-footer-border-color:   $modal-header-border-color
+
+// $modal-lg:                    900px
+// $modal-md:                    600px
+// $modal-sm:                    300px
+
+
+//== Alerts
+//
+//## Define alert colors, border radius, and padding.
+
+// $alert-padding:               15px
+// $alert-border-radius:         $border-radius-base
+// $alert-link-font-weight:      bold
+
+// $alert-success-bg:            $state-success-bg
+// $alert-success-text:          $state-success-text
+// $alert-success-border:        $state-success-border
+
+// $alert-info-bg:               $state-info-bg
+// $alert-info-text:             $state-info-text
+// $alert-info-border:           $state-info-border
+
+// $alert-warning-bg:            $state-warning-bg
+// $alert-warning-text:          $state-warning-text
+// $alert-warning-border:        $state-warning-border
+
+// $alert-danger-bg:             $state-danger-bg
+// $alert-danger-text:           $state-danger-text
+// $alert-danger-border:         $state-danger-border
+
+
+//== Progress bars
+//
+//##
+
+//** Background color of the whole progress component
+// $progress-bg:                 #f5f5f5
+//** Progress bar text color
+// $progress-bar-color:          #fff
+//** Variable for setting rounded corners on progress bar.
+// $progress-border-radius:      $border-radius-base
+
+//** Default progress bar color
+// $progress-bar-bg:             $brand-primary
+//** Success progress bar color
+// $progress-bar-success-bg:     $brand-success
+//** Warning progress bar color
+// $progress-bar-warning-bg:     $brand-warning
+//** Danger progress bar color
+// $progress-bar-danger-bg:      $brand-danger
+//** Info progress bar color
+// $progress-bar-info-bg:        $brand-info
+
+
+//== List group
+//
+//##
+
+//** Background color on `.list-group-item`
+// $list-group-bg:                 #fff
+//** `.list-group-item` border color
+// $list-group-border:             #ddd
+//** List group border radius
+// $list-group-border-radius:      $border-radius-base
+
+//** Background color of single list items on hover
+// $list-group-hover-bg:           #f5f5f5
+//** Text color of active list items
+// $list-group-active-color:       $component-active-color
+//** Background color of active list items
+// $list-group-active-bg:          $component-active-bg
+//** Border color of active list elements
+// $list-group-active-border:      $list-group-active-bg
+//** Text color for content within active list items
+// $list-group-active-text-color:  lighten($list-group-active-bg, 40%)
+
+//** Text color of disabled list items
+// $list-group-disabled-color:      $gray-light
+//** Background color of disabled list items
+// $list-group-disabled-bg:         $gray-lighter
+//** Text color for content within disabled list items
+// $list-group-disabled-text-color: $list-group-disabled-color
+
+// $list-group-link-color:         #555
+// $list-group-link-hover-color:   $list-group-link-color
+// $list-group-link-heading-color: #333
+
+
+//== Panels
+//
+//##
+
+// $panel-bg:                    #fff
+// $panel-body-padding:          15px
+// $panel-heading-padding:       10px 15px
+// $panel-footer-padding:        $panel-heading-padding
+// $panel-border-radius:         $border-radius-base
+
+//** Border color for elements within panels
+// $panel-inner-border:          #ddd
+// $panel-footer-bg:             #f5f5f5
+
+// $panel-default-text:          $gray-dark
+// $panel-default-border:        #ddd
+// $panel-default-heading-bg:    #f5f5f5
+
+// $panel-primary-text:          #fff
+// $panel-primary-border:        $brand-primary
+// $panel-primary-heading-bg:    $brand-primary
+
+// $panel-success-text:          $state-success-text
+// $panel-success-border:        $state-success-border
+// $panel-success-heading-bg:    $state-success-bg
+
+// $panel-info-text:             $state-info-text
+// $panel-info-border:           $state-info-border
+// $panel-info-heading-bg:       $state-info-bg
+
+// $panel-warning-text:          $state-warning-text
+// $panel-warning-border:        $state-warning-border
+// $panel-warning-heading-bg:    $state-warning-bg
+
+// $panel-danger-text:           $state-danger-text
+// $panel-danger-border:         $state-danger-border
+// $panel-danger-heading-bg:     $state-danger-bg
+
+
+//== Thumbnails
+//
+//##
+
+//** Padding around the thumbnail image
+// $thumbnail-padding:           4px
+//** Thumbnail background color
+// $thumbnail-bg:                $body-bg
+//** Thumbnail border color
+// $thumbnail-border:            #ddd
+//** Thumbnail border radius
+// $thumbnail-border-radius:     $border-radius-base
+
+//** Custom text color for thumbnail captions
+// $thumbnail-caption-color:     $text-color
+//** Padding around the thumbnail caption
+// $thumbnail-caption-padding:   9px
+
+
+//== Wells
+//
+//##
+
+// $well-bg:                     #f5f5f5
+// $well-border:                 darken($well-bg, 7%)
+
+
+//== Badges
+//
+//##
+
+// $badge-color:                 #fff
+//** Linked badge text color on hover
+// $badge-link-hover-color:      #fff
+// $badge-bg:                    $gray-light
+
+//** Badge text color in active nav link
+// $badge-active-color:          $link-color
+//** Badge background color in active nav link
+// $badge-active-bg:             #fff
+
+// $badge-font-weight:           bold
+// $badge-line-height:           1
+// $badge-border-radius:         10px
+
+
+//== Breadcrumbs
+//
+//##
+
+// $breadcrumb-padding-vertical:   8px
+// $breadcrumb-padding-horizontal: 15px
+//** Breadcrumb background color
+// $breadcrumb-bg:                 #f5f5f5
+//** Breadcrumb text color
+// $breadcrumb-color:              #ccc
+//** Text color of current page in the breadcrumb
+// $breadcrumb-active-color:       $gray-light
+//** Textual separator for between breadcrumb elements
+// $breadcrumb-separator:          "/"
+
+
+//== Carousel
+//
+//##
+
+// $carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6)
+
+// $carousel-control-color:                      #fff
+// $carousel-control-width:                      15%
+// $carousel-control-opacity:                    .5
+// $carousel-control-font-size:                  20px
+
+// $carousel-indicator-active-bg:                #fff
+// $carousel-indicator-border-color:             #fff
+
+// $carousel-caption-color:                      #fff
+
+
+//== Close
+//
+//##
+
+// $close-font-weight:           bold
+// $close-color:                 #000
+// $close-text-shadow:           0 1px 0 #fff
+
+
+//== Code
+//
+//##
+
+// $code-color:                  #c7254e
+// $code-bg:                     #f9f2f4
+
+// $kbd-color:                   #fff
+// $kbd-bg:                      #333
+
+// $pre-bg:                      #f5f5f5
+// $pre-color:                   $gray-dark
+// $pre-border-color:            #ccc
+// $pre-scrollable-max-height:   340px
+
+
+//== Type
+//
+//##
+
+//** Horizontal offset for forms and lists.
+// $component-offset-horizontal: 180px
+//** Text muted color
+// $text-muted:                  $gray-light
+//** Abbreviations and acronyms border color
+// $abbr-border-color:           $gray-light
+//** Headings small color
+// $headings-small-color:        $gray-light
+//** Blockquote small color
+// $blockquote-small-color:      $gray-light
+//** Blockquote font size
+// $blockquote-font-size:        ($font-size-base * 1.25)
+//** Blockquote border color
+// $blockquote-border-color:     $gray-lighter
+//** Page header border color
+// $page-header-border-color:    $gray-lighter
+//** Width of horizontal description list titles
+// $dl-horizontal-offset:        $component-offset-horizontal
+//** Point at which .dl-horizontal becomes horizontal
+// $dl-horizontal-breakpoint:    $grid-float-breakpoint
+//** Horizontal line color.
+// $hr-border:                   $gray-lighter
diff --git a/client/app/stylesheets/index.scss b/client/app/stylesheets/index.scss
new file mode 100644
index 000000000..d13dcfa90
--- /dev/null
+++ b/client/app/stylesheets/index.scss
@@ -0,0 +1,88 @@
+.span_action {
+  margin: 5px;
+  cursor: pointer;
+}
+
+header div {
+  height: 50px;
+  line-height: 25px;
+  margin-bottom: 50px;
+}
+
+menu {
+  margin-right: 20px;
+  border-right: 1px solid rgba(0, 0, 0, 0.2);
+}
+
+menu .panel_button {
+  margin: 8px;
+  cursor: pointer;
+  transition: margin 0.2s;
+}
+
+menu .panel_button:hover {
+  margin-left: 15px;
+}
+
+menu .glyphicon {
+  margin: 5px;
+}
+
+#ajax_load {
+  min-height: 500px;
+}
+
+.loading {
+  display: inline-block;
+  margin-top: 100px;
+}
+
+.video {
+  margin-bottom: 10px;
+  transition: margin 0.5s ease;
+}
+
+.video:hover {
+  margin-left: 5px;
+}
+
+.video_name {
+  cursor: pointer;
+  margin-right: 5px;
+}
+
+.video_pod_url {
+  font-size: small;
+  color: rgba(0, 0, 0, 0.5);
+}
+
+.video_description {
+  font-size: small;
+  font-style: italic;
+  margin-left: 7px;
+}
+
+.btn-file {
+  position: relative;
+  overflow: hidden;
+}
+.btn-file input[type=file] {
+  position: absolute;
+  top: 0;
+  right: 0;
+  min-width: 100%;
+  min-height: 100%;
+  font-size: 100px;
+  text-align: right;
+  filter: alpha(opacity=0);
+  opacity: 0;
+  outline: none;
+  background: white;
+  cursor: inherit;
+  display: block;
+}
+
+.name_file {
+  display: inline-block;
+  margin-left: 10px;
+}
\ No newline at end of file
diff --git a/controllers/api/v1/index.js b/controllers/api/v1/index.js
deleted file mode 100644
index 07a68ed9d..000000000
--- a/controllers/api/v1/index.js
+++ /dev/null
@@ -1,17 +0,0 @@
-'use strict'
-
-var express = require('express')
-
-var router = express.Router()
-
-var podsController = require('./pods')
-var remoteVideosController = require('./remoteVideos')
-var videosController = require('./videos')
-
-router.use('/pods', podsController)
-router.use('/remotevideos', remoteVideosController)
-router.use('/videos', videosController)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
diff --git a/controllers/api/v1/pods.js b/controllers/api/v1/pods.js
deleted file mode 100644
index c93a86ee8..000000000
--- a/controllers/api/v1/pods.js
+++ /dev/null
@@ -1,93 +0,0 @@
-'use strict'
-
-var express = require('express')
-var fs = require('fs')
-
-var logger = require('../../../helpers/logger')
-var friends = require('../../../lib/friends')
-var middleware = require('../../../middlewares')
-var cacheMiddleware = middleware.cache
-var peertubeCrypto = require('../../../helpers/peertubeCrypto')
-var Pods = require('../../../models/pods')
-var reqValidator = middleware.reqValidators.pods
-var secureMiddleware = middleware.secure
-var secureRequest = middleware.reqValidators.remote.secureRequest
-var Videos = require('../../../models/videos')
-
-var router = express.Router()
-
-router.get('/', cacheMiddleware.cache(false), listPods)
-router.post('/', reqValidator.podsAdd, cacheMiddleware.cache(false), addPods)
-router.get('/makefriends', reqValidator.makeFriends, cacheMiddleware.cache(false), makeFriends)
-router.get('/quitfriends', cacheMiddleware.cache(false), quitFriends)
-// Post because this is a secured request
-router.post('/remove', secureRequest, secureMiddleware.decryptBody, removePods)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function addPods (req, res, next) {
-  var informations = req.body.data
-  Pods.add(informations, function (err) {
-    if (err) return next(err)
-
-    Videos.addRemotes(informations.videos)
-
-    fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
-      if (err) {
-        logger.error('Cannot read cert file.')
-        return next(err)
-      }
-
-      Videos.listOwned(function (err, videos_list) {
-        if (err) {
-          logger.error('Cannot get the list of owned videos.')
-          return next(err)
-        }
-
-        res.json({ cert: cert, videos: videos_list })
-      })
-    })
-  })
-}
-
-function listPods (req, res, next) {
-  Pods.list(function (err, pods_list) {
-    if (err) return next(err)
-
-    res.json(pods_list)
-  })
-}
-
-function makeFriends (req, res, next) {
-  friends.makeFriends(function (err) {
-    if (err) return next(err)
-
-    res.sendStatus(204)
-  })
-}
-
-function removePods (req, res, next) {
-  var url = req.body.signature.url
-  Pods.remove(url, function (err) {
-    if (err) return next(err)
-
-    Videos.removeAllRemotesOf(url, function (err) {
-      if (err) logger.error('Cannot remove all remote videos of %s.', url)
-      else logger.info('%s pod removed.', url)
-
-      res.sendStatus(204)
-    })
-  })
-}
-
-function quitFriends (req, res, next) {
-  friends.quitFriends(function (err) {
-    if (err) return next(err)
-
-    res.sendStatus(204)
-  })
-}
diff --git a/controllers/api/v1/remoteVideos.js b/controllers/api/v1/remoteVideos.js
deleted file mode 100644
index 475a874cf..000000000
--- a/controllers/api/v1/remoteVideos.js
+++ /dev/null
@@ -1,53 +0,0 @@
-'use strict'
-
-var express = require('express')
-var pluck = require('lodash-node/compat/collection/pluck')
-
-var middleware = require('../../../middlewares')
-var secureMiddleware = middleware.secure
-var cacheMiddleware = middleware.cache
-var reqValidator = middleware.reqValidators.remote
-var videos = require('../../../models/videos')
-
-var router = express.Router()
-
-router.post('/add',
-  reqValidator.secureRequest,
-  secureMiddleware.decryptBody,
-  reqValidator.remoteVideosAdd,
-  cacheMiddleware.cache(false),
-  addRemoteVideos
-)
-
-router.post('/remove',
-  reqValidator.secureRequest,
-  secureMiddleware.decryptBody,
-  reqValidator.remoteVideosRemove,
-  cacheMiddleware.cache(false),
-  removeRemoteVideo
-)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function addRemoteVideos (req, res, next) {
-  videos.addRemotes(req.body.data, function (err, videos) {
-    if (err) return next(err)
-
-    res.json(videos)
-  })
-}
-
-function removeRemoteVideo (req, res, next) {
-  var url = req.body.signature.url
-  var magnetUris = pluck(req.body.data, 'magnetUri')
-
-  videos.removeRemotesOfByMagnetUris(url, magnetUris, function (err) {
-    if (err) return next(err)
-
-    res.sendStatus(204)
-  })
-}
diff --git a/controllers/api/v1/videos.js b/controllers/api/v1/videos.js
deleted file mode 100644
index 620711925..000000000
--- a/controllers/api/v1/videos.js
+++ /dev/null
@@ -1,144 +0,0 @@
-'use strict'
-
-var config = require('config')
-var crypto = require('crypto')
-var express = require('express')
-var multer = require('multer')
-
-var logger = require('../../../helpers/logger')
-var friends = require('../../../lib/friends')
-var middleware = require('../../../middlewares')
-var cacheMiddleware = middleware.cache
-var reqValidator = middleware.reqValidators.videos
-var Videos = require('../../../models/videos') // model
-var videos = require('../../../lib/videos')
-var webtorrent = require('../../../lib/webtorrent')
-
-var router = express.Router()
-var uploads = config.get('storage.uploads')
-
-// multer configuration
-var storage = multer.diskStorage({
-  destination: function (req, file, cb) {
-    cb(null, uploads)
-  },
-
-  filename: function (req, file, cb) {
-    var extension = ''
-    if (file.mimetype === 'video/webm') extension = 'webm'
-    else if (file.mimetype === 'video/mp4') extension = 'mp4'
-    else if (file.mimetype === 'video/ogg') extension = 'ogv'
-    crypto.pseudoRandomBytes(16, function (err, raw) {
-      var fieldname = err ? undefined : raw.toString('hex')
-      cb(null, fieldname + '.' + extension)
-    })
-  }
-})
-
-var reqFiles = multer({ storage: storage }).fields([{ name: 'input_video', maxCount: 1 }])
-
-router.get('/', cacheMiddleware.cache(false), listVideos)
-router.post('/', reqFiles, reqValidator.videosAdd, cacheMiddleware.cache(false), addVideo)
-router.get('/:id', reqValidator.videosGet, cacheMiddleware.cache(false), getVideos)
-router.delete('/:id', reqValidator.videosRemove, cacheMiddleware.cache(false), removeVideo)
-router.get('/search/:name', reqValidator.videosSearch, cacheMiddleware.cache(false), searchVideos)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function addVideo (req, res, next) {
-  var video_file = req.files.input_video[0]
-  var video_infos = req.body
-
-  videos.seed(video_file.path, function (err, torrent) {
-    if (err) {
-      logger.error('Cannot seed this video.')
-      return next(err)
-    }
-
-    var video_data = {
-      name: video_infos.name,
-      namePath: video_file.filename,
-      description: video_infos.description,
-      magnetUri: torrent.magnetURI
-    }
-
-    Videos.add(video_data, function (err) {
-      if (err) {
-        // TODO unseed the video
-        logger.error('Cannot insert this video in the database.')
-        return next(err)
-      }
-
-      // Now we'll add the video's meta data to our friends
-      friends.addVideoToFriends(video_data)
-
-      // TODO : include Location of the new video
-      res.sendStatus(201)
-    })
-  })
-}
-
-function getVideos (req, res, next) {
-  Videos.get(req.params.id, function (err, video) {
-    if (err) return next(err)
-
-    if (video === null) {
-      return res.sendStatus(404)
-    }
-
-    res.json(video)
-  })
-}
-
-function listVideos (req, res, next) {
-  Videos.list(function (err, videos_list) {
-    if (err) return next(err)
-
-    res.json(videos_list)
-  })
-}
-
-function removeVideo (req, res, next) {
-  var video_id = req.params.id
-  Videos.get(video_id, function (err, video) {
-    if (err) return next(err)
-
-    removeTorrent(video.magnetUri, function () {
-      Videos.removeOwned(req.params.id, function (err) {
-        if (err) return next(err)
-
-        var params = {
-          name: video.name,
-          magnetUri: video.magnetUri
-        }
-
-        friends.removeVideoToFriends(params)
-        res.sendStatus(204)
-      })
-    })
-  })
-}
-
-function searchVideos (req, res, next) {
-  Videos.search(req.params.name, function (err, videos_list) {
-    if (err) return next(err)
-
-    res.json(videos_list)
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
-function removeTorrent (magnetUri, callback) {
-  try {
-    webtorrent.remove(magnetUri, callback)
-  } catch (err) {
-    logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
-    return callback(null)
-  }
-}
diff --git a/controllers/index.js b/controllers/index.js
deleted file mode 100644
index 858f493da..000000000
--- a/controllers/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-'use strict'
-
-var constants = require('../initializers/constants')
-
-var apiController = require('./api/' + constants.API_VERSION)
-var viewsController = require('./views')
-
-module.exports = {
-  api: apiController,
-  views: viewsController
-}
diff --git a/controllers/views.js b/controllers/views.js
deleted file mode 100644
index aa9718079..000000000
--- a/controllers/views.js
+++ /dev/null
@@ -1,27 +0,0 @@
-'use strict'
-
-var express = require('express')
-
-var cacheMiddleware = require('../middlewares').cache
-
-var router = express.Router()
-
-router.get(/^\/(index)?$/, cacheMiddleware.cache(), getIndex)
-router.get('/partials/:directory/:name', cacheMiddleware.cache(), getPartial)
-
-// ---------------------------------------------------------------------------
-
-module.exports = router
-
-// ---------------------------------------------------------------------------
-
-function getIndex (req, res) {
-  res.render('index')
-}
-
-function getPartial (req, res) {
-  var directory = req.params.directory
-  var name = req.params.name
-
-  res.render('partials/' + directory + '/' + name)
-}
diff --git a/helpers/customValidators.js b/helpers/customValidators.js
deleted file mode 100644
index 20c41f5da..000000000
--- a/helpers/customValidators.js
+++ /dev/null
@@ -1,32 +0,0 @@
-'use strict'
-
-var validator = require('validator')
-
-var customValidators = {
-  eachIsRemoteVideosAddValid: eachIsRemoteVideosAddValid,
-  eachIsRemoteVideosRemoveValid: eachIsRemoteVideosRemoveValid,
-  isArray: isArray
-}
-
-function eachIsRemoteVideosAddValid (values) {
-  return values.every(function (val) {
-    return validator.isLength(val.name, 1, 50) &&
-      validator.isLength(val.description, 1, 50) &&
-      validator.isLength(val.magnetUri, 10) &&
-      validator.isURL(val.podUrl)
-  })
-}
-
-function eachIsRemoteVideosRemoveValid (values) {
-  return values.every(function (val) {
-    return validator.isLength(val.magnetUri, 10)
-  })
-}
-
-function isArray (value) {
-  return Array.isArray(value)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = customValidators
diff --git a/helpers/logger.js b/helpers/logger.js
deleted file mode 100644
index 67f69a875..000000000
--- a/helpers/logger.js
+++ /dev/null
@@ -1,40 +0,0 @@
-// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
-'use strict'
-
-var config = require('config')
-var path = require('path')
-var winston = require('winston')
-winston.emitErrs = true
-
-var logDir = path.join(__dirname, '..', config.get('storage.logs'))
-var logger = new winston.Logger({
-  transports: [
-    new winston.transports.File({
-      level: 'debug',
-      filename: path.join(logDir, 'all-logs.log'),
-      handleExceptions: true,
-      json: true,
-      maxsize: 5242880,
-      maxFiles: 5,
-      colorize: false
-    }),
-    new winston.transports.Console({
-      level: 'debug',
-      handleExceptions: true,
-      humanReadableUnhandledException: true,
-      json: false,
-      colorize: true
-    })
-  ],
-  exitOnError: true
-})
-
-logger.stream = {
-  write: function (message, encoding) {
-    logger.info(message)
-  }
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = logger
diff --git a/helpers/peertubeCrypto.js b/helpers/peertubeCrypto.js
deleted file mode 100644
index 29b9d79c9..000000000
--- a/helpers/peertubeCrypto.js
+++ /dev/null
@@ -1,147 +0,0 @@
-'use strict'
-
-var config = require('config')
-var crypto = require('crypto')
-var fs = require('fs')
-var openssl = require('openssl-wrapper')
-var path = require('path')
-var ursa = require('ursa')
-
-var logger = require('./logger')
-
-var certDir = path.join(__dirname, '..', config.get('storage.certs'))
-var algorithm = 'aes-256-ctr'
-
-var peertubeCrypto = {
-  checkSignature: checkSignature,
-  createCertsIfNotExist: createCertsIfNotExist,
-  decrypt: decrypt,
-  encrypt: encrypt,
-  getCertDir: getCertDir,
-  sign: sign
-}
-
-function checkSignature (public_key, raw_data, hex_signature) {
-  var crt = ursa.createPublicKey(public_key)
-  var is_valid = crt.hashAndVerify('sha256', new Buffer(raw_data).toString('hex'), hex_signature, 'hex')
-  return is_valid
-}
-
-function createCertsIfNotExist (callback) {
-  certsExist(function (exist) {
-    if (exist === true) {
-      return callback(null)
-    }
-
-    createCerts(function (err) {
-      return callback(err)
-    })
-  })
-}
-
-function decrypt (key, data, callback) {
-  fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) {
-    if (err) return callback(err)
-
-    var my_private_key = ursa.createPrivateKey(file)
-    var decrypted_key = my_private_key.decrypt(key, 'hex', 'utf8')
-    var decrypted_data = symetricDecrypt(data, decrypted_key)
-
-    return callback(null, decrypted_data)
-  })
-}
-
-function encrypt (public_key, data, callback) {
-  var crt = ursa.createPublicKey(public_key)
-
-  symetricEncrypt(data, function (err, dataEncrypted) {
-    if (err) return callback(err)
-
-    var key = crt.encrypt(dataEncrypted.password, 'utf8', 'hex')
-    var encrypted = {
-      data: dataEncrypted.crypted,
-      key: key
-    }
-
-    callback(null, encrypted)
-  })
-}
-
-function getCertDir () {
-  return certDir
-}
-
-function sign (data) {
-  var myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem'))
-  var signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex')
-
-  return signature
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = peertubeCrypto
-
-// ---------------------------------------------------------------------------
-
-function certsExist (callback) {
-  fs.exists(certDir + 'peertube.key.pem', function (exists) {
-    return callback(exists)
-  })
-}
-
-function createCerts (callback) {
-  certsExist(function (exist) {
-    if (exist === true) {
-      var string = 'Certs already exist.'
-      logger.warning(string)
-      return callback(new Error(string))
-    }
-
-    logger.info('Generating a RSA key...')
-    openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) {
-      if (err) {
-        logger.error('Cannot create private key on this pod.')
-        return callback(err)
-      }
-      logger.info('RSA key generated.')
-
-      logger.info('Manage public key...')
-      openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) {
-        if (err) {
-          logger.error('Cannot create public key on this pod.')
-          return callback(err)
-        }
-
-        logger.info('Public key managed.')
-        return callback(null)
-      })
-    })
-  })
-}
-
-function generatePassword (callback) {
-  crypto.randomBytes(32, function (err, buf) {
-    if (err) return callback(err)
-
-    callback(null, buf.toString('utf8'))
-  })
-}
-
-function symetricDecrypt (text, password) {
-  var decipher = crypto.createDecipher(algorithm, password)
-  var dec = decipher.update(text, 'hex', 'utf8')
-  dec += decipher.final('utf8')
-  return dec
-}
-
-function symetricEncrypt (text, callback) {
-  generatePassword(function (err, password) {
-    if (err) return callback(err)
-
-    var cipher = crypto.createCipher(algorithm, password)
-    var crypted = cipher.update(text, 'utf8', 'hex')
-    crypted += cipher.final('hex')
-    callback(null, { crypted: crypted, password: password })
-  })
-}
diff --git a/helpers/requests.js b/helpers/requests.js
deleted file mode 100644
index e19afa5ca..000000000
--- a/helpers/requests.js
+++ /dev/null
@@ -1,109 +0,0 @@
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var request = require('request')
-var replay = require('request-replay')
-
-var constants = require('../initializers/constants')
-var logger = require('./logger')
-var peertubeCrypto = require('./peertubeCrypto')
-
-var http = config.get('webserver.https') ? 'https' : 'http'
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-
-var requests = {
-  makeMultipleRetryRequest: makeMultipleRetryRequest
-}
-
-function makeMultipleRetryRequest (all_data, pods, callbackEach, callback) {
-  if (!callback) {
-    callback = callbackEach
-    callbackEach = null
-  }
-
-  var url = http + '://' + host + ':' + port
-  var signature
-
-  // Add signature if it is specified in the params
-  if (all_data.method === 'POST' && all_data.data && all_data.sign === true) {
-    signature = peertubeCrypto.sign(url)
-  }
-
-  // Make a request for each pod
-  async.each(pods, function (pod, callback_each_async) {
-    function callbackEachRetryRequest (err, response, body, url, pod) {
-      if (callbackEach !== null) {
-        callbackEach(err, response, body, url, pod, function () {
-          callback_each_async()
-        })
-      } else {
-        callback_each_async()
-      }
-    }
-
-    var params = {
-      url: pod.url + all_data.path,
-      method: all_data.method
-    }
-
-    // Add data with POST requst ?
-    if (all_data.method === 'POST' && all_data.data) {
-      // Encrypt data ?
-      if (all_data.encrypt === true) {
-        // TODO: ES6 with let
-        ;(function (copy_params, copy_url, copy_pod, copy_signature) {
-          peertubeCrypto.encrypt(pod.publicKey, JSON.stringify(all_data.data), function (err, encrypted) {
-            if (err) return callback(err)
-
-            copy_params.json = {
-              data: encrypted.data,
-              key: encrypted.key
-            }
-
-            makeRetryRequest(copy_params, copy_url, copy_pod, copy_signature, callbackEachRetryRequest)
-          })
-        })(params, url, pod, signature)
-      } else {
-        params.json = { data: all_data.data }
-        makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
-      }
-    } else {
-      makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
-    }
-  }, callback)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = requests
-
-// ---------------------------------------------------------------------------
-
-function makeRetryRequest (params, from_url, to_pod, signature, callbackEach) {
-  // Append the signature
-  if (signature) {
-    params.json.signature = {
-      url: from_url,
-      signature: signature
-    }
-  }
-
-  logger.debug('Make retry requests to %s.', to_pod.url)
-
-  replay(
-    request.post(params, function (err, response, body) {
-      callbackEach(err, response, body, params.url, to_pod)
-    }),
-    {
-      retries: constants.REQUEST_RETRIES,
-      factor: 3,
-      maxTimeout: Infinity,
-      errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
-    }
-  ).on('replay', function (replay) {
-    logger.info('Replaying request to %s. Request failed: %d %s. Replay number: #%d. Will retry in: %d ms.',
-      params.url, replay.error.code, replay.error.message, replay.number, replay.delay)
-  })
-}
diff --git a/helpers/utils.js b/helpers/utils.js
deleted file mode 100644
index d2c9ad8b2..000000000
--- a/helpers/utils.js
+++ /dev/null
@@ -1,16 +0,0 @@
-'use strict'
-
-var logger = require('./logger')
-
-var utils = {
-  cleanForExit: cleanForExit
-}
-
-function cleanForExit (webtorrent_process) {
-  logger.info('Gracefully exiting.')
-  process.kill(-webtorrent_process.pid)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = utils
diff --git a/initializers/checker.js b/initializers/checker.js
deleted file mode 100644
index ec7bc0ad2..000000000
--- a/initializers/checker.js
+++ /dev/null
@@ -1,46 +0,0 @@
-'use strict'
-
-var config = require('config')
-var mkdirp = require('mkdirp')
-var path = require('path')
-
-var checker = {
-  checkConfig: checkConfig,
-  createDirectoriesIfNotExist: createDirectoriesIfNotExist
-}
-
-// Check the config files
-function checkConfig () {
-  var required = [ 'listen.port',
-    'webserver.https', 'webserver.host', 'webserver.port',
-    'database.host', 'database.port', 'database.suffix',
-    'storage.certs', 'storage.uploads', 'storage.logs',
-    'network.friends' ]
-  var miss = []
-
-  for (var key of required) {
-    if (!config.has(key)) {
-      miss.push(key)
-    }
-  }
-
-  return miss
-}
-
-// Create directories for the storage if it doesn't exist
-function createDirectoriesIfNotExist () {
-  var storages = config.get('storage')
-
-  for (var key of Object.keys(storages)) {
-    var dir = storages[key]
-    try {
-      mkdirp.sync(path.join(__dirname, '..', dir))
-    } catch (error) {
-      throw new Error('Cannot create ' + path + ':' + error)
-    }
-  }
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = checker
diff --git a/initializers/constants.js b/initializers/constants.js
deleted file mode 100644
index 16e50443b..000000000
--- a/initializers/constants.js
+++ /dev/null
@@ -1,42 +0,0 @@
-'use strict'
-
-// API version of our pod
-var API_VERSION = 'v1'
-
-// Score a pod has when we create it as a friend
-var FRIEND_BASE_SCORE = 100
-
-// Time to wait between requests to the friends
-var INTERVAL = 60000
-
-// Number of points we add/remove from a friend after a successful/bad request
-var PODS_SCORE = {
-  MALUS: -10,
-  BONUS: 10
-}
-
-// Number of retries we make for the make retry requests (to friends...)
-var REQUEST_RETRIES = 10
-
-// Special constants for a test instance
-if (isTestInstance() === true) {
-  FRIEND_BASE_SCORE = 20
-  INTERVAL = 10000
-  REQUEST_RETRIES = 2
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = {
-  API_VERSION: API_VERSION,
-  FRIEND_BASE_SCORE: FRIEND_BASE_SCORE,
-  INTERVAL: INTERVAL,
-  PODS_SCORE: PODS_SCORE,
-  REQUEST_RETRIES: REQUEST_RETRIES
-}
-
-// ---------------------------------------------------------------------------
-
-function isTestInstance () {
-  return (process.env.NODE_ENV === 'test')
-}
diff --git a/initializers/database.js b/initializers/database.js
deleted file mode 100644
index a917442ec..000000000
--- a/initializers/database.js
+++ /dev/null
@@ -1,29 +0,0 @@
-'use strict'
-
-var config = require('config')
-var mongoose = require('mongoose')
-
-var logger = require('../helpers/logger')
-
-var dbname = 'peertube' + config.get('database.suffix')
-var host = config.get('database.host')
-var port = config.get('database.port')
-
-var database = {
-  connect: connect
-}
-
-function connect () {
-  mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname)
-  mongoose.connection.on('error', function () {
-    throw new Error('Mongodb connection error.')
-  })
-
-  mongoose.connection.on('open', function () {
-    logger.info('Connected to mongodb.')
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = database
diff --git a/lib/friends.js b/lib/friends.js
deleted file mode 100644
index 006a64404..000000000
--- a/lib/friends.js
+++ /dev/null
@@ -1,228 +0,0 @@
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var fs = require('fs')
-var request = require('request')
-
-var constants = require('../initializers/constants')
-var logger = require('../helpers/logger')
-var peertubeCrypto = require('../helpers/peertubeCrypto')
-var Pods = require('../models/pods')
-var poolRequests = require('../lib/poolRequests')
-var requests = require('../helpers/requests')
-var Videos = require('../models/videos')
-
-var http = config.get('webserver.https') ? 'https' : 'http'
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-
-var pods = {
-  addVideoToFriends: addVideoToFriends,
-  hasFriends: hasFriends,
-  makeFriends: makeFriends,
-  quitFriends: quitFriends,
-  removeVideoToFriends: removeVideoToFriends
-}
-
-function addVideoToFriends (video) {
-  // To avoid duplicates
-  var id = video.name + video.magnetUri
-  // ensure namePath is null
-  video.namePath = null
-  poolRequests.addRequest(id, 'add', video)
-}
-
-function hasFriends (callback) {
-  Pods.count(function (err, count) {
-    if (err) return callback(err)
-
-    var has_friends = (count !== 0)
-    callback(null, has_friends)
-  })
-}
-
-function makeFriends (callback) {
-  var pods_score = {}
-
-  logger.info('Make friends!')
-  fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
-    if (err) {
-      logger.error('Cannot read public cert.')
-      return callback(err)
-    }
-
-    var urls = config.get('network.friends')
-
-    async.each(urls, function (url, callback) {
-      computeForeignPodsList(url, pods_score, callback)
-    }, function (err) {
-      if (err) return callback(err)
-
-      logger.debug('Pods scores computed.', { pods_score: pods_score })
-      var pods_list = computeWinningPods(urls, pods_score)
-      logger.debug('Pods that we keep computed.', { pods_to_keep: pods_list })
-
-      makeRequestsToWinningPods(cert, pods_list, callback)
-    })
-  })
-}
-
-function quitFriends (callback) {
-  // Stop pool requests
-  poolRequests.deactivate()
-  // Flush pool requests
-  poolRequests.forceSend()
-
-  Pods.list(function (err, pods) {
-    if (err) return callback(err)
-
-    var request = {
-      method: 'POST',
-      path: '/api/' + constants.API_VERSION + '/pods/remove',
-      sign: true,
-      encrypt: true,
-      data: {
-        url: 'me' // Fake data
-      }
-    }
-
-    // Announce we quit them
-    requests.makeMultipleRetryRequest(request, pods, function () {
-      Pods.removeAll(function (err) {
-        poolRequests.activate()
-
-        if (err) return callback(err)
-
-        logger.info('Broke friends, so sad :(')
-
-        Videos.removeAllRemotes(function (err) {
-          if (err) return callback(err)
-
-          logger.info('Removed all remote videos.')
-          callback(null)
-        })
-      })
-    })
-  })
-}
-
-function removeVideoToFriends (video) {
-  // To avoid duplicates
-  var id = video.name + video.magnetUri
-  poolRequests.addRequest(id, 'remove', video)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = pods
-
-// ---------------------------------------------------------------------------
-
-function computeForeignPodsList (url, pods_score, callback) {
-  // Let's give 1 point to the pod we ask the friends list
-  pods_score[url] = 1
-
-  getForeignPodsList(url, function (err, foreign_pods_list) {
-    if (err) return callback(err)
-    if (foreign_pods_list.length === 0) return callback()
-
-    async.each(foreign_pods_list, function (foreign_pod, callback_each) {
-      var foreign_url = foreign_pod.url
-
-      if (pods_score[foreign_url]) pods_score[foreign_url]++
-      else pods_score[foreign_url] = 1
-
-      callback_each()
-    }, function () {
-      callback()
-    })
-  })
-}
-
-function computeWinningPods (urls, pods_score) {
-  // Build the list of pods to add
-  // Only add a pod if it exists in more than a half base pods
-  var pods_list = []
-  var base_score = urls.length / 2
-  Object.keys(pods_score).forEach(function (pod) {
-    if (pods_score[pod] > base_score) pods_list.push({ url: pod })
-  })
-
-  return pods_list
-}
-
-function getForeignPodsList (url, callback) {
-  var path = '/api/' + constants.API_VERSION + '/pods'
-
-  request.get(url + path, function (err, response, body) {
-    if (err) return callback(err)
-
-    callback(null, JSON.parse(body))
-  })
-}
-
-function makeRequestsToWinningPods (cert, pods_list, callback) {
-  // Stop pool requests
-  poolRequests.deactivate()
-  // Flush pool requests
-  poolRequests.forceSend()
-
-  // Get the list of our videos to send to our new friends
-  Videos.listOwned(function (err, videos_list) {
-    if (err) {
-      logger.error('Cannot get the list of videos we own.')
-      return callback(err)
-    }
-
-    var data = {
-      url: http + '://' + host + ':' + port,
-      publicKey: cert,
-      videos: videos_list
-    }
-
-    requests.makeMultipleRetryRequest(
-      { method: 'POST', path: '/api/' + constants.API_VERSION + '/pods/', data: data },
-
-      pods_list,
-
-      function eachRequest (err, response, body, url, pod, callback_each_request) {
-        // We add the pod if it responded correctly with its public certificate
-        if (!err && response.statusCode === 200) {
-          Pods.add({ url: pod.url, publicKey: body.cert, score: constants.FRIEND_BASE_SCORE }, function (err) {
-            if (err) {
-              logger.error('Error with adding %s pod.', pod.url, { error: err })
-              return callback_each_request()
-            }
-
-            Videos.addRemotes(body.videos, function (err) {
-              if (err) {
-                logger.error('Error with adding videos of pod.', pod.url, { error: err })
-                return callback_each_request()
-              }
-
-              logger.debug('Adding remote videos from %s.', pod.url, { videos: body.videos })
-              return callback_each_request()
-            })
-          })
-        } else {
-          logger.error('Error with adding %s pod.', pod.url, { error: err || new Error('Status not 200') })
-          return callback_each_request()
-        }
-      },
-
-      function endRequests (err) {
-        // Now we made new friends, we can re activate the pool of requests
-        poolRequests.activate()
-
-        if (err) {
-          logger.error('There was some errors when we wanted to make friends.')
-          return callback(err)
-        }
-
-        logger.debug('makeRequestsToWinningPods finished.')
-        return callback(null)
-      }
-    )
-  })
-}
diff --git a/lib/poolRequests.js b/lib/poolRequests.js
deleted file mode 100644
index f786c3c7a..000000000
--- a/lib/poolRequests.js
+++ /dev/null
@@ -1,221 +0,0 @@
-'use strict'
-
-var async = require('async')
-var pluck = require('lodash-node/compat/collection/pluck')
-
-var constants = require('../initializers/constants')
-var logger = require('../helpers/logger')
-var Pods = require('../models/pods')
-var PoolRequests = require('../models/poolRequests')
-var requests = require('../helpers/requests')
-var Videos = require('../models/videos')
-
-var timer = null
-
-var poolRequests = {
-  activate: activate,
-  addRequest: addRequest,
-  deactivate: deactivate,
-  forceSend: forceSend
-}
-
-function activate () {
-  logger.info('Pool requests activated.')
-  timer = setInterval(makePoolRequests, constants.INTERVAL)
-}
-
-function addRequest (id, type, request) {
-  logger.debug('Add request to the pool requests.', { id: id, type: type, request: request })
-
-  PoolRequests.findById(id, function (err, entity) {
-    if (err) {
-      logger.error('Cannot find one pool request.', { error: err })
-      return // Abort
-    }
-
-    if (entity) {
-      if (entity.type === type) {
-        logger.error('Cannot insert two same requests.')
-        return // Abort
-      }
-
-      // Remove the request of the other type
-      PoolRequests.removeRequestById(id, function (err) {
-        if (err) {
-          logger.error('Cannot remove a pool request.', { error: err })
-          return // Abort
-        }
-      })
-    } else {
-      PoolRequests.create(id, type, request, function (err) {
-        if (err) logger.error('Cannot create a pool request.', { error: err })
-        return // Abort
-      })
-    }
-  })
-}
-
-function deactivate () {
-  logger.info('Pool requests deactivated.')
-  clearInterval(timer)
-}
-
-function forceSend () {
-  logger.info('Force pool requests sending.')
-  makePoolRequests()
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = poolRequests
-
-// ---------------------------------------------------------------------------
-
-function makePoolRequest (type, requests_to_make, callback) {
-  if (!callback) callback = function () {}
-
-  Pods.list(function (err, pods) {
-    if (err) return callback(err)
-
-    var params = {
-      encrypt: true,
-      sign: true,
-      method: 'POST',
-      path: null,
-      data: requests_to_make
-    }
-
-    if (type === 'add') {
-      params.path = '/api/' + constants.API_VERSION + '/remotevideos/add'
-    } else if (type === 'remove') {
-      params.path = '/api/' + constants.API_VERSION + '/remotevideos/remove'
-    } else {
-      return callback(new Error('Unkown pool request type.'))
-    }
-
-    var bad_pods = []
-    var good_pods = []
-
-    requests.makeMultipleRetryRequest(params, pods, callbackEachPodFinished, callbackAllPodsFinished)
-
-    function callbackEachPodFinished (err, response, body, url, pod, callback_each_pod_finished) {
-      if (err || (response.statusCode !== 200 && response.statusCode !== 204)) {
-        bad_pods.push(pod._id)
-        logger.error('Error sending secure request to %s pod.', url, { error: err || new Error('Status code not 20x') })
-      } else {
-        good_pods.push(pod._id)
-      }
-
-      return callback_each_pod_finished()
-    }
-
-    function callbackAllPodsFinished (err) {
-      if (err) return callback(err)
-
-      updatePodsScore(good_pods, bad_pods)
-      callback(null)
-    }
-  })
-}
-
-function makePoolRequests () {
-  logger.info('Making pool requests to friends.')
-
-  PoolRequests.list(function (err, pool_requests) {
-    if (err) {
-      logger.error('Cannot get the list of pool requests.', { err: err })
-      return // Abort
-    }
-
-    if (pool_requests.length === 0) return
-
-    var requests_to_make = {
-      add: {
-        ids: [],
-        requests: []
-      },
-      remove: {
-        ids: [],
-        requests: []
-      }
-    }
-
-    async.each(pool_requests, function (pool_request, callback_each) {
-      if (pool_request.type === 'add') {
-        requests_to_make.add.requests.push(pool_request.request)
-        requests_to_make.add.ids.push(pool_request._id)
-      } else if (pool_request.type === 'remove') {
-        requests_to_make.remove.requests.push(pool_request.request)
-        requests_to_make.remove.ids.push(pool_request._id)
-      } else {
-        logger.error('Unkown pool request type.', { request_type: pool_request.type })
-        return // abort
-      }
-
-      callback_each()
-    }, function () {
-      // Send the add requests
-      if (requests_to_make.add.requests.length !== 0) {
-        makePoolRequest('add', requests_to_make.add.requests, function (err) {
-          if (err) logger.error('Errors when sent add pool requests.', { error: err })
-
-          PoolRequests.removeRequests(requests_to_make.add.ids)
-        })
-      }
-
-      // Send the remove requests
-      if (requests_to_make.remove.requests.length !== 0) {
-        makePoolRequest('remove', requests_to_make.remove.requests, function (err) {
-          if (err) logger.error('Errors when sent remove pool requests.', { error: err })
-
-          PoolRequests.removeRequests(requests_to_make.remove.ids)
-        })
-      }
-    })
-  })
-}
-
-function removeBadPods () {
-  Pods.findBadPods(function (err, pods) {
-    if (err) {
-      logger.error('Cannot find bad pods.', { error: err })
-      return // abort
-    }
-
-    if (pods.length === 0) return
-
-    var urls = pluck(pods, 'url')
-    var ids = pluck(pods, '_id')
-
-    Videos.removeAllRemotesOf(urls, function (err, r) {
-      if (err) {
-        logger.error('Cannot remove videos from a pod that we removing.', { error: err })
-      } else {
-        var videos_removed = r.result.n
-        logger.info('Removed %d videos.', videos_removed)
-      }
-
-      Pods.removeAllByIds(ids, function (err, r) {
-        if (err) {
-          logger.error('Cannot remove bad pods.', { error: err })
-        } else {
-          var pods_removed = r.result.n
-          logger.info('Removed %d pods.', pods_removed)
-        }
-      })
-    })
-  })
-}
-
-function updatePodsScore (good_pods, bad_pods) {
-  logger.info('Updating %d good pods and %d bad pods scores.', good_pods.length, bad_pods.length)
-
-  Pods.incrementScores(good_pods, constants.PODS_SCORE.BONUS, function (err) {
-    if (err) logger.error('Cannot increment scores of good pods.')
-  })
-
-  Pods.incrementScores(bad_pods, constants.PODS_SCORE.MALUS, function (err) {
-    if (err) logger.error('Cannot increment scores of bad pods.')
-    removeBadPods()
-  })
-}
diff --git a/lib/videos.js b/lib/videos.js
deleted file mode 100644
index 2d7d9500d..000000000
--- a/lib/videos.js
+++ /dev/null
@@ -1,50 +0,0 @@
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var path = require('path')
-var webtorrent = require('../lib/webtorrent')
-
-var logger = require('../helpers/logger')
-var Videos = require('../models/videos')
-
-var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
-
-var videos = {
-  seed: seed,
-  seedAllExisting: seedAllExisting
-}
-
-function seed (path, callback) {
-  logger.info('Seeding %s...', path)
-
-  webtorrent.seed(path, function (torrent) {
-    logger.info('%s seeded (%s).', path, torrent.magnetURI)
-
-    return callback(null, torrent)
-  })
-}
-
-function seedAllExisting (callback) {
-  Videos.listOwned(function (err, videos_list) {
-    if (err) {
-      logger.error('Cannot get list of the videos to seed.')
-      return callback(err)
-    }
-
-    async.each(videos_list, function (video, each_callback) {
-      seed(uploadDir + video.namePath, function (err) {
-        if (err) {
-          logger.error('Cannot seed this video.')
-          return callback(err)
-        }
-
-        each_callback(null)
-      })
-    }, callback)
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = videos
diff --git a/lib/webtorrent.js b/lib/webtorrent.js
deleted file mode 100644
index cb641fead..000000000
--- a/lib/webtorrent.js
+++ /dev/null
@@ -1,157 +0,0 @@
-'use strict'
-
-var config = require('config')
-var ipc = require('node-ipc')
-var pathUtils = require('path')
-var spawn = require('electron-spawn')
-
-var logger = require('../helpers/logger')
-
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-var nodeKey = 'webtorrentnode' + port
-var processKey = 'webtorrentprocess' + port
-ipc.config.silent = true
-ipc.config.id = nodeKey
-
-var webtorrent = {
-  add: add,
-  app: null, // Pid of the app
-  create: create,
-  remove: remove,
-  seed: seed,
-  silent: false // Useful for beautiful tests
-}
-
-function create (options, callback) {
-  if (typeof options === 'function') {
-    callback = options
-    options = {}
-  }
-
-  // Override options
-  if (options.host) host = options.host
-  if (options.port) {
-    port = options.port
-    nodeKey = 'webtorrentnode' + port
-    processKey = 'webtorrentprocess' + port
-    ipc.config.id = nodeKey
-  }
-
-  ipc.serve(function () {
-    if (!webtorrent.silent) logger.info('IPC server ready.')
-
-    // Run a timeout of 30s after which we exit the process
-    var timeout_webtorrent_process = setTimeout(function () {
-      throw new Error('Timeout : cannot run the webtorrent process. Please ensure you have electron-prebuilt npm package installed with xvfb-run.')
-    }, 30000)
-
-    ipc.server.on(processKey + '.ready', function () {
-      if (!webtorrent.silent) logger.info('Webtorrent process ready.')
-      clearTimeout(timeout_webtorrent_process)
-      callback()
-    })
-
-    ipc.server.on(processKey + '.exception', function (data) {
-      throw new Error('Received exception error from webtorrent process.' + data.exception)
-    })
-
-    var webtorrent_process = spawn(pathUtils.join(__dirname, 'webtorrentProcess.js'), host, port, { detached: true })
-    webtorrent_process.stderr.on('data', function (data) {
-      // logger.debug('Webtorrent process stderr: ', data.toString())
-    })
-
-    webtorrent_process.stdout.on('data', function (data) {
-      // logger.debug('Webtorrent process:', data.toString())
-    })
-
-    webtorrent.app = webtorrent_process
-  })
-
-  ipc.server.start()
-}
-
-function seed (path, callback) {
-  var extension = pathUtils.extname(path)
-  var basename = pathUtils.basename(path, extension)
-  var data = {
-    _id: basename,
-    args: {
-      path: path
-    }
-  }
-
-  if (!webtorrent.silent) logger.debug('Node wants to seed %s.', data._id)
-
-  // Finish signal
-  var event_key = nodeKey + '.seedDone.' + data._id
-  ipc.server.on(event_key, function listener (received) {
-    if (!webtorrent.silent) logger.debug('Process seeded torrent %s.', received.magnetUri)
-
-    // This is a fake object, we just use the magnetUri in this project
-    var torrent = {
-      magnetURI: received.magnetUri
-    }
-
-    ipc.server.off(event_key)
-    callback(torrent)
-  })
-
-  ipc.server.broadcast(processKey + '.seed', data)
-}
-
-function add (magnetUri, callback) {
-  var data = {
-    _id: magnetUri,
-    args: {
-      magnetUri: magnetUri
-    }
-  }
-
-  if (!webtorrent.silent) logger.debug('Node wants to add ' + data._id)
-
-  // Finish signal
-  var event_key = nodeKey + '.addDone.' + data._id
-  ipc.server.on(event_key, function (received) {
-    if (!webtorrent.silent) logger.debug('Process added torrent.')
-
-    // This is a fake object, we just use the magnetUri in this project
-    var torrent = {
-      files: received.files
-    }
-
-    ipc.server.off(event_key)
-    callback(torrent)
-  })
-
-  ipc.server.broadcast(processKey + '.add', data)
-}
-
-function remove (magnetUri, callback) {
-  var data = {
-    _id: magnetUri,
-    args: {
-      magnetUri: magnetUri
-    }
-  }
-
-  if (!webtorrent.silent) logger.debug('Node wants to stop seeding %s.', data._id)
-
-  // Finish signal
-  var event_key = nodeKey + '.removeDone.' + data._id
-  ipc.server.on(event_key, function (received) {
-    if (!webtorrent.silent) logger.debug('Process removed torrent %s.', data._id)
-
-    var err = null
-    if (received.err) err = received.err
-
-    ipc.server.off(event_key)
-    callback(err)
-  })
-
-  ipc.server.broadcast(processKey + '.remove', data)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = webtorrent
diff --git a/lib/webtorrentProcess.js b/lib/webtorrentProcess.js
deleted file mode 100644
index 7da52523a..000000000
--- a/lib/webtorrentProcess.js
+++ /dev/null
@@ -1,92 +0,0 @@
-'use strict'
-
-var WebTorrent = require('webtorrent')
-var ipc = require('node-ipc')
-
-function webtorrent (args) {
-  if (args.length !== 3) {
-    throw new Error('Wrong arguments number: ' + args.length + '/3')
-  }
-
-  var host = args[1]
-  var port = args[2]
-  var nodeKey = 'webtorrentnode' + port
-  var processKey = 'webtorrentprocess' + port
-
-  ipc.config.silent = true
-  ipc.config.id = processKey
-
-  if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = []
-  else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket'
-  var wt = new WebTorrent({ dht: false })
-
-  function seed (data) {
-    var args = data.args
-    var path = args.path
-    var _id = data._id
-
-    wt.seed(path, { announceList: '' }, function (torrent) {
-      var to_send = {
-        magnetUri: torrent.magnetURI
-      }
-
-      ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send)
-    })
-  }
-
-  function add (data) {
-    var args = data.args
-    var magnetUri = args.magnetUri
-    var _id = data._id
-
-    wt.add(magnetUri, function (torrent) {
-      var to_send = {
-        files: []
-      }
-
-      torrent.files.forEach(function (file) {
-        to_send.files.push({ path: file.path })
-      })
-
-      ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send)
-    })
-  }
-
-  function remove (data) {
-    var args = data.args
-    var magnetUri = args.magnetUri
-    var _id = data._id
-
-    try {
-      wt.remove(magnetUri, callback)
-    } catch (err) {
-      console.log('Cannot remove the torrent from WebTorrent.')
-      return callback(null)
-    }
-
-    function callback () {
-      var to_send = {}
-      ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send)
-    }
-  }
-
-  console.log('Configuration: ' + host + ':' + port)
-  console.log('Connecting to IPC...')
-
-  ipc.connectTo(nodeKey, function () {
-    ipc.of[nodeKey].on(processKey + '.seed', seed)
-    ipc.of[nodeKey].on(processKey + '.add', add)
-    ipc.of[nodeKey].on(processKey + '.remove', remove)
-
-    ipc.of[nodeKey].emit(processKey + '.ready')
-    console.log('Ready.')
-  })
-
-  process.on('uncaughtException', function (e) {
-    ipc.of[nodeKey].emit(processKey + '.exception', { exception: e })
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = webtorrent
diff --git a/middlewares/cache.js b/middlewares/cache.js
deleted file mode 100644
index 0d3da0075..000000000
--- a/middlewares/cache.js
+++ /dev/null
@@ -1,23 +0,0 @@
-'use strict'
-
-var cacheMiddleware = {
-  cache: cache
-}
-
-function cache (cache) {
-  return function (req, res, next) {
-    // If we want explicitly a cache
-    // Or if we don't specify if we want a cache or no and we are in production
-    if (cache === true || (cache !== false && process.env.NODE_ENV === 'production')) {
-      res.setHeader('Cache-Control', 'public')
-    } else {
-      res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
-    }
-
-    next()
-  }
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = cacheMiddleware
diff --git a/middlewares/index.js b/middlewares/index.js
deleted file mode 100644
index c85899b0c..000000000
--- a/middlewares/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-'use strict'
-
-var cacheMiddleware = require('./cache')
-var reqValidatorsMiddleware = require('./reqValidators')
-var secureMiddleware = require('./secure')
-
-var middlewares = {
-  cache: cacheMiddleware,
-  reqValidators: reqValidatorsMiddleware,
-  secure: secureMiddleware
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = middlewares
diff --git a/middlewares/reqValidators/index.js b/middlewares/reqValidators/index.js
deleted file mode 100644
index 345dbd0e2..000000000
--- a/middlewares/reqValidators/index.js
+++ /dev/null
@@ -1,15 +0,0 @@
-'use strict'
-
-var podsReqValidators = require('./pods')
-var remoteReqValidators = require('./remote')
-var videosReqValidators = require('./videos')
-
-var reqValidators = {
-  pods: podsReqValidators,
-  remote: remoteReqValidators,
-  videos: videosReqValidators
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidators
diff --git a/middlewares/reqValidators/pods.js b/middlewares/reqValidators/pods.js
deleted file mode 100644
index ef09d51cf..000000000
--- a/middlewares/reqValidators/pods.js
+++ /dev/null
@@ -1,39 +0,0 @@
-'use strict'
-
-var checkErrors = require('./utils').checkErrors
-var friends = require('../../lib/friends')
-var logger = require('../../helpers/logger')
-
-var reqValidatorsPod = {
-  makeFriends: makeFriends,
-  podsAdd: podsAdd
-}
-
-function makeFriends (req, res, next) {
-  friends.hasFriends(function (err, has_friends) {
-    if (err) {
-      logger.error('Cannot know if we have friends.', { error: err })
-      res.sendStatus(500)
-    }
-
-    if (has_friends === true) {
-      // We need to quit our friends before make new ones
-      res.sendStatus(409)
-    } else {
-      return next()
-    }
-  })
-}
-
-function podsAdd (req, res, next) {
-  req.checkBody('data.url', 'Should have an url').notEmpty().isURL({ require_protocol: true })
-  req.checkBody('data.publicKey', 'Should have a public key').notEmpty()
-
-  logger.debug('Checking podsAdd parameters', { parameters: req.body })
-
-  checkErrors(req, res, next)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsPod
diff --git a/middlewares/reqValidators/remote.js b/middlewares/reqValidators/remote.js
deleted file mode 100644
index 88de16b49..000000000
--- a/middlewares/reqValidators/remote.js
+++ /dev/null
@@ -1,43 +0,0 @@
-'use strict'
-
-var checkErrors = require('./utils').checkErrors
-var logger = require('../../helpers/logger')
-
-var reqValidatorsRemote = {
-  remoteVideosAdd: remoteVideosAdd,
-  remoteVideosRemove: remoteVideosRemove,
-  secureRequest: secureRequest
-}
-
-function remoteVideosAdd (req, res, next) {
-  req.checkBody('data').isArray()
-  req.checkBody('data').eachIsRemoteVideosAddValid()
-
-  logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body })
-
-  checkErrors(req, res, next)
-}
-
-function remoteVideosRemove (req, res, next) {
-  req.checkBody('data').isArray()
-  req.checkBody('data').eachIsRemoteVideosRemoveValid()
-
-  logger.debug('Checking remoteVideosRemove parameters', { parameters: req.body })
-
-  checkErrors(req, res, next)
-}
-
-function secureRequest (req, res, next) {
-  req.checkBody('signature.url', 'Should have a signature url').isURL()
-  req.checkBody('signature.signature', 'Should have a signature').notEmpty()
-  req.checkBody('key', 'Should have a key').notEmpty()
-  req.checkBody('data', 'Should have data').notEmpty()
-
-  logger.debug('Checking secureRequest parameters', { parameters: { data: req.body.data, keyLength: req.body.key.length } })
-
-  checkErrors(req, res, next)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsRemote
diff --git a/middlewares/reqValidators/utils.js b/middlewares/reqValidators/utils.js
deleted file mode 100644
index 46c982571..000000000
--- a/middlewares/reqValidators/utils.js
+++ /dev/null
@@ -1,25 +0,0 @@
-'use strict'
-
-var util = require('util')
-
-var logger = require('../../helpers/logger')
-
-var reqValidatorsUtils = {
-  checkErrors: checkErrors
-}
-
-function checkErrors (req, res, next, status_code) {
-  if (status_code === undefined) status_code = 400
-  var errors = req.validationErrors()
-
-  if (errors) {
-    logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors })
-    return res.status(status_code).send('There have been validation errors: ' + util.inspect(errors))
-  }
-
-  return next()
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsUtils
diff --git a/middlewares/reqValidators/videos.js b/middlewares/reqValidators/videos.js
deleted file mode 100644
index 4e5f4391f..000000000
--- a/middlewares/reqValidators/videos.js
+++ /dev/null
@@ -1,74 +0,0 @@
-'use strict'
-
-var checkErrors = require('./utils').checkErrors
-var logger = require('../../helpers/logger')
-var Videos = require('../../models/videos')
-
-var reqValidatorsVideos = {
-  videosAdd: videosAdd,
-  videosGet: videosGet,
-  videosRemove: videosRemove,
-  videosSearch: videosSearch
-}
-
-function videosAdd (req, res, next) {
-  req.checkFiles('input_video[0].originalname', 'Should have an input video').notEmpty()
-  req.checkFiles('input_video[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i)
-  req.checkBody('name', 'Should have a name').isLength(1, 50)
-  req.checkBody('description', 'Should have a description').isLength(1, 250)
-
-  logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
-
-  checkErrors(req, res, next)
-}
-
-function videosGet (req, res, next) {
-  req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
-
-  logger.debug('Checking videosGet parameters', { parameters: req.params })
-
-  checkErrors(req, res, function () {
-    Videos.getVideoState(req.params.id, function (err, state) {
-      if (err) {
-        logger.error('Error in videosGet request validator.', { error: err })
-        res.sendStatus(500)
-      }
-
-      if (state.exist === false) return res.status(404).send('Video not found')
-
-      next()
-    })
-  })
-}
-
-function videosRemove (req, res, next) {
-  req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
-
-  logger.debug('Checking videosRemove parameters', { parameters: req.params })
-
-  checkErrors(req, res, function () {
-    Videos.getVideoState(req.params.id, function (err, state) {
-      if (err) {
-        logger.error('Error in videosRemove request validator.', { error: err })
-        res.sendStatus(500)
-      }
-
-      if (state.exist === false) return res.status(404).send('Video not found')
-      else if (state.owned === false) return res.status(403).send('Cannot remove video of another pod')
-
-      next()
-    })
-  })
-}
-
-function videosSearch (req, res, next) {
-  req.checkParams('name', 'Should have a name').notEmpty()
-
-  logger.debug('Checking videosSearch parameters', { parameters: req.params })
-
-  checkErrors(req, res, next)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = reqValidatorsVideos
diff --git a/middlewares/secure.js b/middlewares/secure.js
deleted file mode 100644
index bfd28316a..000000000
--- a/middlewares/secure.js
+++ /dev/null
@@ -1,49 +0,0 @@
-'use strict'
-
-var logger = require('../helpers/logger')
-var peertubeCrypto = require('../helpers/peertubeCrypto')
-var Pods = require('../models/pods')
-
-var secureMiddleware = {
-  decryptBody: decryptBody
-}
-
-function decryptBody (req, res, next) {
-  var url = req.body.signature.url
-  Pods.findByUrl(url, function (err, pod) {
-    if (err) {
-      logger.error('Cannot get signed url in decryptBody.', { error: err })
-      return res.sendStatus(500)
-    }
-
-    if (pod === null) {
-      logger.error('Unknown pod %s.', url)
-      return res.sendStatus(403)
-    }
-
-    logger.debug('Decrypting body from %s.', url)
-
-    var signature_ok = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature)
-
-    if (signature_ok === true) {
-      peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) {
-        if (err) {
-          logger.error('Cannot decrypt data.', { error: err })
-          return res.sendStatus(500)
-        }
-
-        req.body.data = JSON.parse(decrypted)
-        delete req.body.key
-
-        next()
-      })
-    } else {
-      logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url)
-      return res.sendStatus(403)
-    }
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = secureMiddleware
diff --git a/models/pods.js b/models/pods.js
deleted file mode 100644
index 57ed20292..000000000
--- a/models/pods.js
+++ /dev/null
@@ -1,88 +0,0 @@
-'use strict'
-
-var mongoose = require('mongoose')
-
-var constants = require('../initializers/constants')
-var logger = require('../helpers/logger')
-
-// ---------------------------------------------------------------------------
-
-var podsSchema = mongoose.Schema({
-  url: String,
-  publicKey: String,
-  score: { type: Number, max: constants.FRIEND_BASE_SCORE }
-})
-var PodsDB = mongoose.model('pods', podsSchema)
-
-// ---------------------------------------------------------------------------
-
-var Pods = {
-  add: add,
-  count: count,
-  findByUrl: findByUrl,
-  findBadPods: findBadPods,
-  incrementScores: incrementScores,
-  list: list,
-  remove: remove,
-  removeAll: removeAll,
-  removeAllByIds: removeAllByIds
-}
-
-// TODO: check if the pod is not already a friend
-function add (data, callback) {
-  if (!callback) callback = function () {}
-  var params = {
-    url: data.url,
-    publicKey: data.publicKey,
-    score: constants.FRIEND_BASE_SCORE
-  }
-
-  PodsDB.create(params, callback)
-}
-
-function count (callback) {
-  return PodsDB.count(callback)
-}
-
-function findBadPods (callback) {
-  PodsDB.find({ score: 0 }, callback)
-}
-
-function findByUrl (url, callback) {
-  PodsDB.findOne({ url: url }, callback)
-}
-
-function incrementScores (ids, value, callback) {
-  if (!callback) callback = function () {}
-  PodsDB.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback)
-}
-
-function list (callback) {
-  PodsDB.find(function (err, pods_list) {
-    if (err) {
-      logger.error('Cannot get the list of the pods.')
-      return callback(err)
-    }
-
-    return callback(null, pods_list)
-  })
-}
-
-function remove (url, callback) {
-  if (!callback) callback = function () {}
-  PodsDB.remove({ url: url }, callback)
-}
-
-function removeAll (callback) {
-  if (!callback) callback = function () {}
-  PodsDB.remove(callback)
-}
-
-function removeAllByIds (ids, callback) {
-  if (!callback) callback = function () {}
-  PodsDB.remove({ _id: { $in: ids } }, callback)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = Pods
diff --git a/models/poolRequests.js b/models/poolRequests.js
deleted file mode 100644
index 970315597..000000000
--- a/models/poolRequests.js
+++ /dev/null
@@ -1,55 +0,0 @@
-'use strict'
-
-var mongoose = require('mongoose')
-
-var logger = require('../helpers/logger')
-
-// ---------------------------------------------------------------------------
-
-var poolRequestsSchema = mongoose.Schema({
-  type: String,
-  id: String, // Special id to find duplicates (video created we want to remove...)
-  request: mongoose.Schema.Types.Mixed
-})
-var PoolRequestsDB = mongoose.model('poolRequests', poolRequestsSchema)
-
-// ---------------------------------------------------------------------------
-
-var PoolRequests = {
-  create: create,
-  findById: findById,
-  list: list,
-  removeRequestById: removeRequestById,
-  removeRequests: removeRequests
-}
-
-function create (id, type, request, callback) {
-  PoolRequestsDB.create({ id: id, type: type, request: request }, callback)
-}
-
-function findById (id, callback) {
-  PoolRequestsDB.findOne({ id: id }, callback)
-}
-
-function list (callback) {
-  PoolRequestsDB.find({}, { _id: 1, type: 1, request: 1 }, callback)
-}
-
-function removeRequestById (id, callback) {
-  PoolRequestsDB.remove({ id: id }, callback)
-}
-
-function removeRequests (ids) {
-  PoolRequestsDB.remove({ _id: { $in: ids } }, function (err) {
-    if (err) {
-      logger.error('Cannot remove requests from the pool requests database.', { error: err })
-      return // Abort
-    }
-
-    logger.info('Pool requests flushed.')
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = PoolRequests
diff --git a/models/videos.js b/models/videos.js
deleted file mode 100644
index 5e2eeae07..000000000
--- a/models/videos.js
+++ /dev/null
@@ -1,234 +0,0 @@
-'use strict'
-
-var async = require('async')
-var config = require('config')
-var dz = require('dezalgo')
-var fs = require('fs')
-var mongoose = require('mongoose')
-var path = require('path')
-
-var logger = require('../helpers/logger')
-
-var http = config.get('webserver.https') === true ? 'https' : 'http'
-var host = config.get('webserver.host')
-var port = config.get('webserver.port')
-var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
-
-// ---------------------------------------------------------------------------
-
-var videosSchema = mongoose.Schema({
-  name: String,
-  namePath: String,
-  description: String,
-  magnetUri: String,
-  podUrl: String
-})
-var VideosDB = mongoose.model('videos', videosSchema)
-
-// ---------------------------------------------------------------------------
-
-var Videos = {
-  add: add,
-  addRemotes: addRemotes,
-  get: get,
-  getVideoState: getVideoState,
-  isOwned: isOwned,
-  list: list,
-  listOwned: listOwned,
-  removeOwned: removeOwned,
-  removeAllRemotes: removeAllRemotes,
-  removeAllRemotesOf: removeAllRemotesOf,
-  removeRemotesOfByMagnetUris: removeRemotesOfByMagnetUris,
-  search: search
-}
-
-function add (video, callback) {
-  logger.info('Adding %s video to database.', video.name)
-
-  var params = video
-  params.podUrl = http + '://' + host + ':' + port
-
-  VideosDB.create(params, function (err, video) {
-    if (err) {
-      logger.error('Cannot insert this video into database.')
-      return callback(err)
-    }
-
-    callback(null)
-  })
-}
-
-// TODO: avoid doublons
-function addRemotes (videos, callback) {
-  if (!callback) callback = function () {}
-
-  var to_add = []
-
-  async.each(videos, function (video, callback_each) {
-    callback_each = dz(callback_each)
-    logger.debug('Add remote video from pod: %s', video.podUrl)
-
-    var params = {
-      name: video.name,
-      namePath: null,
-      description: video.description,
-      magnetUri: video.magnetUri,
-      podUrl: video.podUrl
-    }
-
-    to_add.push(params)
-
-    callback_each()
-  }, function () {
-    VideosDB.create(to_add, function (err, videos) {
-      if (err) {
-        logger.error('Cannot insert this remote video.')
-        return callback(err)
-      }
-
-      return callback(null, videos)
-    })
-  })
-}
-
-function get (id, callback) {
-  VideosDB.findById(id, function (err, video) {
-    if (err) {
-      logger.error('Cannot get this video.')
-      return callback(err)
-    }
-
-    return callback(null, video)
-  })
-}
-
-function getVideoState (id, callback) {
-  get(id, function (err, video) {
-    if (err) return callback(err)
-
-    var exist = (video !== null)
-    var owned = false
-    if (exist === true) {
-      owned = (video.namePath !== null)
-    }
-
-    return callback(null, { exist: exist, owned: owned })
-  })
-}
-
-function isOwned (id, callback) {
-  VideosDB.findById(id, function (err, video) {
-    if (err || !video) {
-      if (!err) err = new Error('Cannot find this video.')
-      logger.error('Cannot find this video.')
-      return callback(err)
-    }
-
-    if (video.namePath === null) {
-      var error_string = 'Cannot remove the video of another pod.'
-      logger.error(error_string)
-      return callback(new Error(error_string), false, video)
-    }
-
-    callback(null, true, video)
-  })
-}
-
-function list (callback) {
-  VideosDB.find(function (err, videos_list) {
-    if (err) {
-      logger.error('Cannot get the list of the videos.')
-      return callback(err)
-    }
-
-    return callback(null, videos_list)
-  })
-}
-
-function listOwned (callback) {
-  // If namePath is not null this is *our* video
-  VideosDB.find({ namePath: { $ne: null } }, function (err, videos_list) {
-    if (err) {
-      logger.error('Cannot get the list of owned videos.')
-      return callback(err)
-    }
-
-    return callback(null, videos_list)
-  })
-}
-
-function removeOwned (id, callback) {
-  VideosDB.findByIdAndRemove(id, function (err, video) {
-    if (err) {
-      logger.error('Cannot remove the torrent.')
-      return callback(err)
-    }
-
-    fs.unlink(uploadDir + video.namePath, function (err) {
-      if (err) {
-        logger.error('Cannot remove this video file.')
-        return callback(err)
-      }
-
-      callback(null)
-    })
-  })
-}
-
-function removeAllRemotes (callback) {
-  VideosDB.remove({ namePath: null }, callback)
-}
-
-function removeAllRemotesOf (fromUrl, callback) {
-  VideosDB.remove({ podUrl: fromUrl }, callback)
-}
-
-// Use the magnet Uri because the _id field is not the same on different servers
-function removeRemotesOfByMagnetUris (fromUrl, magnetUris, callback) {
-  if (callback === undefined) callback = function () {}
-
-  VideosDB.find({ magnetUri: { $in: magnetUris } }, function (err, videos) {
-    if (err || !videos) {
-      logger.error('Cannot find the torrent URI of these remote videos.')
-      return callback(err)
-    }
-
-    var to_remove = []
-    async.each(videos, function (video, callback_async) {
-      callback_async = dz(callback_async)
-
-      if (video.podUrl !== fromUrl) {
-        logger.error('The pod %s has not the rights on the video of %s.', fromUrl, video.podUrl)
-      } else {
-        to_remove.push(video._id)
-      }
-
-      callback_async()
-    }, function () {
-      VideosDB.remove({ _id: { $in: to_remove } }, function (err) {
-        if (err) {
-          logger.error('Cannot remove the remote videos.')
-          return callback(err)
-        }
-
-        logger.info('Removed remote videos from %s.', fromUrl)
-        callback(null)
-      })
-    })
-  })
-}
-
-function search (name, callback) {
-  VideosDB.find({ name: new RegExp(name) }, function (err, videos) {
-    if (err) {
-      logger.error('Cannot search the videos.')
-      return callback(err)
-    }
-
-    return callback(null, videos)
-  })
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = Videos
diff --git a/public/images/favicon.png b/public/images/favicon.png
deleted file mode 100644
index bb57ee6b0..000000000
Binary files a/public/images/favicon.png and /dev/null differ
diff --git a/public/images/loading.gif b/public/images/loading.gif
deleted file mode 100644
index f2a1bc0c6..000000000
Binary files a/public/images/loading.gif and /dev/null differ
diff --git a/public/javascripts/index.js b/public/javascripts/index.js
deleted file mode 100644
index 4910e4540..000000000
--- a/public/javascripts/index.js
+++ /dev/null
@@ -1,245 +0,0 @@
-;(function () {
-  'use strict'
-
-  var $ = require('jquery')
-  require('blueimp-file-upload')
-
-  var WebTorrent = require('webtorrent')
-  var client = new WebTorrent({ dht: false })
-
-  var $content = $('#ajax_load')
-
-  // Webtorrent events
-  client.on('error', function (err) {
-    console.error(err)
-  })
-
-  client.on('warning', function (err) {
-    console.warning(err)
-  })
-
-  // Events of the panel
-  $('#panel_get_videos').on('click', function () {
-    getVideos()
-  })
-
-  $('#panel_upload_video').on('click', function () {
-    uploadVideo()
-  })
-
-  $('#panel_make_friends').on('click', function () {
-    makeFriends()
-  })
-
-  $('#panel_quit_friends').on('click', function () {
-    quitFriends()
-  })
-
-  $('#search-video').on('keyup', function (e) {
-    var search = $(this).val()
-
-    if (search === '') return
-
-    if (e.keyCode === 13) {
-      $.ajax({
-        url: '/api/v1/videos/search/' + search,
-        type: 'GET',
-        dataType: 'json',
-        success: function (videos) {
-          printVideos(videos)
-        }
-      })
-    }
-  })
-
-  // Join a new network
-  function makeFriends () {
-    $.ajax({
-      url: '/api/v1/pods/makefriends',
-      type: 'GET',
-      dataType: 'json',
-      statusCode: {
-        409: function () {
-          alert('Already made friends.')
-        }
-      },
-      success: function () {
-        alert('Made friends!')
-      }
-    })
-  }
-
-  function quitFriends () {
-    $.ajax({
-      url: '/api/v1/pods/quitfriends',
-      type: 'GET',
-      dataType: 'json',
-      success: function () {
-        alert('Quit friends!')
-      }
-    })
-  }
-
-  function printVideos (videos) {
-    $content.empty()
-
-    if (videos.length === 0) {
-      $content.text('There is no videos.')
-    }
-
-    videos.forEach(function (video) {
-      var $video = $('
').addClass('video') - - var $video_name = $('').addClass('video_name').text(video.name) - var $video_pod = $('').addClass('video_pod_url').text(video.podUrl) - var $header = $('
').append([ $video_name, $video_pod ]) - - if (video.namePath !== null) { - var $remove = $('').addClass('span_action glyphicon glyphicon-remove') - - // Remove the video - $remove.on('click', function () { - if (!confirm('Are you sure ?')) return - - removeVideo(video) - }) - - $header.append($remove) - } - - var $video_description = $('
').addClass('video_description').text(video.description) - - // Get the video - $video_name.on('click', function () { - getVideo(video) - }) - - if (!video.magnetUri) { - $remove.css('display', 'none') - } - - $video.append([ $header, $video_description ]) - $content.append($video) - }) - } - - // Upload the video, the server will seed it - function uploadVideo () { - // Creating all the elements - var $video_label = $('').attr('for', 'name').text('Video name') - var $video_name = $('').addClass('form-control').attr({ - name: 'name', - id: 'name' - }) - var $video_block = $('
').addClass('form-group').append([ $video_label, $video_name ]) - - var $title = $('

').text('Upload a video') - - var $button_text = $('').text('Select the video...') - var $input_video = $('').attr({ - type: 'file', - name: 'input_video', - id: 'input_video' - }) - var $button = $('
').addClass('btn btn-default btn-file').append([ $button_text, $input_video ]) - - var $description_label = $('').attr('for', 'description').text('Description') - var $description_text = $('').addClass('form-control').attr({ - name: 'description', - id: 'description', - placeholder: 'Description...' - }) - var $description = $('
').addClass('form-group').append([ $description_label, $description_text ]) - - var $bar = $('
').attr('id', 'progress').append($bar) - - var $input_submit = $('').addClass('btn btn-default').attr({ - type: 'button', - value: 'Upload' - }) - - // JQuery plugin - var $form_video = $('
').append([ $video_block, $button, $progress_bar, $description, $input_submit ]) - $form_video.fileupload({ - singleFileUploads: true, - multipart: true, - url: '/api/v1/videos', - autoupload: false, - add: function (e, data) { - var $text = $('').addClass('name_file').text(data['files'][0]['name']) - $text.insertAfter($button) - $input_submit.off('click').on('click', function () { - $bar.css('display', 'block') - data.formData = $form_video.serializeArray() - data.submit() - }) - }, - progressall: function (e, data) { - $bar.attr({ - value: data.loaded, - max: data.total - }) - }, - done: function (e, data) { - // Print all the videos once it's finished - getVideos() - } - }) - - $content.empty() - $content.append([ $title, $form_video ]) - } - - // Print the list of all the videos - function getVideos () { - $.ajax({ - url: '/api/v1/videos/', - dataType: 'json', - type: 'GET', - success: function (videos) { - printVideos(videos) - } - }) - } - - function removeVideo (video) { - $.ajax({ - url: '/api/v1/videos/' + video._id, - type: 'DELETE', - success: function (response, status) { - getVideos() - } - }) - } - - // Get the video: add the torrent file and stream it into a video tag - function getVideo (video) { - var $waiting = $('').addClass('center-block loading').attr('src', '/images/loading.gif') - $content.empty() - $content.append($waiting) - - console.log('Getting ' + video) - client.add(video.magnetUri, function (torrent) { - var $embed = $('
').addClass('embed-responsive embed-responsive-16by9') - - $content.empty() - $content.append($embed) - - // Got torrent metadata! - console.log('Torrent info hash:', torrent.infoHash) - - // Let's say the first file is a webm (vp8) or mp4 (h264) video... - var file = torrent.files[0] - - file.appendTo($embed.get(0), function (err) { - if (err) { - alert('Cannot append the file.') - console.error(err) - } - }) - }) - } - - getVideos() -})() diff --git a/public/stylesheets/application.scss b/public/stylesheets/application.scss deleted file mode 100644 index bf9ec90ab..000000000 --- a/public/stylesheets/application.scss +++ /dev/null @@ -1,6 +0,0 @@ -$icon-font-path: "/stylesheets/vendor/fonts/bootstrap/"; - -@import "bootstrap-variables"; -@import "_bootstrap"; -@import "base"; -@import "index"; \ No newline at end of file diff --git a/public/stylesheets/base.scss b/public/stylesheets/base.scss deleted file mode 100644 index 37bdade1e..000000000 --- a/public/stylesheets/base.scss +++ /dev/null @@ -1,20 +0,0 @@ -body { - padding: 20px; -} - -footer { - border-top: 1px solid rgba(0, 0, 0, 0.2); - padding-top: 10px; - text-align: center; - font-size: small; -} - -.search-group { - .search-btn { - position: relative; - left: -40px; - top: 0; - - &:hover { text-decoration: none; } - } -} \ No newline at end of file diff --git a/public/stylesheets/bootstrap-variables.scss b/public/stylesheets/bootstrap-variables.scss deleted file mode 100644 index 5a49649f9..000000000 --- a/public/stylesheets/bootstrap-variables.scss +++ /dev/null @@ -1,875 +0,0 @@ -// Override Bootstrap variables here (defaults from bootstrap-sass v3.3.6): - -// -// Variables -// -------------------------------------------------- - - -//== Colors -// -//## Gray and brand colors for use across Bootstrap. - -// $gray-base: #000 -// $gray-darker: lighten($gray-base, 13.5%) // #222 -// $gray-dark: lighten($gray-base, 20%) // #333 -// $gray: lighten($gray-base, 33.5%) // #555 -// $gray-light: lighten($gray-base, 46.7%) // #777 -// $gray-lighter: lighten($gray-base, 93.5%) // #eee - -// $brand-primary: darken(#428bca, 6.5%) // #337ab7 -// $brand-success: #5cb85c -// $brand-info: #5bc0de -// $brand-warning: #f0ad4e -// $brand-danger: #d9534f - - -//== Scaffolding -// -//## Settings for some of the most global styles. - -//** Background color for ``. -// $body-bg: #fff -//** Global text color on ``. -// $text-color: $gray-dark - -//** Global textual link color. -// $link-color: $brand-primary -//** Link hover color set via `darken()` function. -// $link-hover-color: darken($link-color, 15%) -//** Link hover decoration. -// $link-hover-decoration: underline - - -//== Typography -// -//## Font, line-height, and color for body text, headings, and more. - -// $font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif -// $font-family-serif: Georgia, "Times New Roman", Times, serif -//** Default monospace fonts for ``, ``, and `
`.
-// $font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace
-// $font-family-base:        $font-family-sans-serif
-
-// $font-size-base:          14px
-// $font-size-large:         ceil(($font-size-base * 1.25)) // ~18px
-// $font-size-small:         ceil(($font-size-base * 0.85)) // ~12px
-
-// $font-size-h1:            floor(($font-size-base * 2.6)) // ~36px
-// $font-size-h2:            floor(($font-size-base * 2.15)) // ~30px
-// $font-size-h3:            ceil(($font-size-base * 1.7)) // ~24px
-// $font-size-h4:            ceil(($font-size-base * 1.25)) // ~18px
-// $font-size-h5:            $font-size-base
-// $font-size-h6:            ceil(($font-size-base * 0.85)) // ~12px
-
-//** Unit-less `line-height` for use in components like buttons.
-// $line-height-base:        1.428571429 // 20/14
-//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc.
-// $line-height-computed:    floor(($font-size-base * $line-height-base)) // ~20px
-
-//** By default, this inherits from the ``.
-// $headings-font-family:    inherit
-// $headings-font-weight:    500
-// $headings-line-height:    1.1
-// $headings-color:          inherit
-
-
-//== Iconography
-//
-//## Specify custom location and filename of the included Glyphicons icon font. Useful for those including Bootstrap via Bower.
-
-//** Load fonts from this directory.
-
-// [converter] If $bootstrap-sass-asset-helper if used, provide path relative to the assets load path.
-// [converter] This is because some asset helpers, such as Sprockets, do not work with file-relative paths.
-// $icon-font-path: if($bootstrap-sass-asset-helper, "bootstrap/", "../fonts/bootstrap/")
-
-//** File name for all font files.
-// $icon-font-name:          "glyphicons-halflings-regular"
-//** Element ID within SVG icon file.
-// $icon-font-svg-id:        "glyphicons_halflingsregular"
-
-
-//== Components
-//
-//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-
-// $padding-base-vertical:     6px
-// $padding-base-horizontal:   12px
-
-// $padding-large-vertical:    10px
-// $padding-large-horizontal:  16px
-
-// $padding-small-vertical:    5px
-// $padding-small-horizontal:  10px
-
-// $padding-xs-vertical:       1px
-// $padding-xs-horizontal:     5px
-
-// $line-height-large:         1.3333333 // extra decimals for Win 8.1 Chrome
-// $line-height-small:         1.5
-
-$border-radius-base:        0;
-$border-radius-large:       0;
-$border-radius-small:       0;
-
-//** Global color for active items (e.g., navs or dropdowns).
-// $component-active-color:    #fff
-//** Global background color for active items (e.g., navs or dropdowns).
-// $component-active-bg:       $brand-primary
-
-//** Width of the `border` for generating carets that indicator dropdowns.
-// $caret-width-base:          4px
-//** Carets increase slightly in size for larger components.
-// $caret-width-large:         5px
-
-
-//== Tables
-//
-//## Customizes the `.table` component with basic values, each used across all table variations.
-
-//** Padding for ``s and ``s.
-// $table-cell-padding:            8px
-//** Padding for cells in `.table-condensed`.
-// $table-condensed-cell-padding:  5px
-
-//** Default background color used for all tables.
-// $table-bg:                      transparent
-//** Background color used for `.table-striped`.
-// $table-bg-accent:               #f9f9f9
-//** Background color used for `.table-hover`.
-// $table-bg-hover:                #f5f5f5
-// $table-bg-active:               $table-bg-hover
-
-//** Border color for table and cell borders.
-// $table-border-color:            #ddd
-
-
-//== Buttons
-//
-//## For each of Bootstrap's buttons, define text, background and border color.
-
-// $btn-font-weight:                normal
-
-// $btn-default-color:              #333
-// $btn-default-bg:                 #fff
-// $btn-default-border:             #ccc
-
-// $btn-primary-color:              #fff
-// $btn-primary-bg:                 $brand-primary
-// $btn-primary-border:             darken($btn-primary-bg, 5%)
-
-// $btn-success-color:              #fff
-// $btn-success-bg:                 $brand-success
-// $btn-success-border:             darken($btn-success-bg, 5%)
-
-// $btn-info-color:                 #fff
-// $btn-info-bg:                    $brand-info
-// $btn-info-border:                darken($btn-info-bg, 5%)
-
-// $btn-warning-color:              #fff
-// $btn-warning-bg:                 $brand-warning
-// $btn-warning-border:             darken($btn-warning-bg, 5%)
-
-// $btn-danger-color:               #fff
-// $btn-danger-bg:                  $brand-danger
-// $btn-danger-border:              darken($btn-danger-bg, 5%)
-
-// $btn-link-disabled-color:        $gray-light
-
-// Allows for customizing button radius independently from global border radius
-// $btn-border-radius-base:         $border-radius-base
-// $btn-border-radius-large:        $border-radius-large
-// $btn-border-radius-small:        $border-radius-small
-
-
-//== Forms
-//
-//##
-
-//** `` background color
-// $input-bg:                       #fff
-//** `` background color
-// $input-bg-disabled:              $gray-lighter
-
-//** Text color for ``s
-// $input-color:                    $gray
-//** `` border color
-// $input-border:                   #ccc
-
-// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4
-//** Default `.form-control` border radius
-// This has no effect on ``s in CSS.
-// $input-border-radius:            $border-radius-base
-//** Large `.form-control` border radius
-// $input-border-radius-large:      $border-radius-large
-//** Small `.form-control` border radius
-// $input-border-radius-small:      $border-radius-small
-
-//** Border color for inputs on focus
-// $input-border-focus:             #66afe9
-
-//** Placeholder text color
-// $input-color-placeholder:        #999
-
-//** Default `.form-control` height
-// $input-height-base:              ($line-height-computed + ($padding-base-vertical * 2) + 2)
-//** Large `.form-control` height
-// $input-height-large:             (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2)
-//** Small `.form-control` height
-// $input-height-small:             (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2)
-
-//** `.form-group` margin
-// $form-group-margin-bottom:       15px
-
-// $legend-color:                   $gray-dark
-// $legend-border-color:            #e5e5e5
-
-//** Background color for textual input addons
-// $input-group-addon-bg:           $gray-lighter
-//** Border color for textual input addons
-// $input-group-addon-border-color: $input-border
-
-//** Disabled cursor for form controls and buttons.
-// $cursor-disabled:                not-allowed
-
-
-//== Dropdowns
-//
-//## Dropdown menu container and contents.
-
-//** Background for the dropdown menu.
-// $dropdown-bg:                    #fff
-//** Dropdown menu `border-color`.
-// $dropdown-border:                rgba(0,0,0,.15)
-//** Dropdown menu `border-color` **for IE8**.
-// $dropdown-fallback-border:       #ccc
-//** Divider color for between dropdown items.
-// $dropdown-divider-bg:            #e5e5e5
-
-//** Dropdown link text color.
-// $dropdown-link-color:            $gray-dark
-//** Hover color for dropdown links.
-// $dropdown-link-hover-color:      darken($gray-dark, 5%)
-//** Hover background for dropdown links.
-// $dropdown-link-hover-bg:         #f5f5f5
-
-//** Active dropdown menu item text color.
-// $dropdown-link-active-color:     $component-active-color
-//** Active dropdown menu item background color.
-// $dropdown-link-active-bg:        $component-active-bg
-
-//** Disabled dropdown menu item background color.
-// $dropdown-link-disabled-color:   $gray-light
-
-//** Text color for headers within dropdown menus.
-// $dropdown-header-color:          $gray-light
-
-//** Deprecated `$dropdown-caret-color` as of v3.1.0
-// $dropdown-caret-color:           #000
-
-
-//-- Z-index master list
-//
-// Warning: Avoid customizing these values. They're used for a bird's eye view
-// of components dependent on the z-axis and are designed to all work together.
-//
-// Note: These variables are not generated into the Customizer.
-
-// $zindex-navbar:            1000
-// $zindex-dropdown:          1000
-// $zindex-popover:           1060
-// $zindex-tooltip:           1070
-// $zindex-navbar-fixed:      1030
-// $zindex-modal-background:  1040
-// $zindex-modal:             1050
-
-
-//== Media queries breakpoints
-//
-//## Define the breakpoints at which your layout will change, adapting to different screen sizes.
-
-// Extra small screen / phone
-//** Deprecated `$screen-xs` as of v3.0.1
-// $screen-xs:                  480px
-//** Deprecated `$screen-xs-min` as of v3.2.0
-// $screen-xs-min:              $screen-xs
-//** Deprecated `$screen-phone` as of v3.0.1
-// $screen-phone:               $screen-xs-min
-
-// Small screen / tablet
-//** Deprecated `$screen-sm` as of v3.0.1
-// $screen-sm:                  768px
-// $screen-sm-min:              $screen-sm
-//** Deprecated `$screen-tablet` as of v3.0.1
-// $screen-tablet:              $screen-sm-min
-
-// Medium screen / desktop
-//** Deprecated `$screen-md` as of v3.0.1
-// $screen-md:                  992px
-// $screen-md-min:              $screen-md
-//** Deprecated `$screen-desktop` as of v3.0.1
-// $screen-desktop:             $screen-md-min
-
-// Large screen / wide desktop
-//** Deprecated `$screen-lg` as of v3.0.1
-// $screen-lg:                  1200px
-// $screen-lg-min:              $screen-lg
-//** Deprecated `$screen-lg-desktop` as of v3.0.1
-// $screen-lg-desktop:          $screen-lg-min
-
-// So media queries don't overlap when required, provide a maximum
-// $screen-xs-max:              ($screen-sm-min - 1)
-// $screen-sm-max:              ($screen-md-min - 1)
-// $screen-md-max:              ($screen-lg-min - 1)
-
-
-//== Grid system
-//
-//## Define your custom responsive grid.
-
-//** Number of columns in the grid.
-// $grid-columns:              12
-//** Padding between columns. Gets divided in half for the left and right.
-// $grid-gutter-width:         30px
-// Navbar collapse
-//** Point at which the navbar becomes uncollapsed.
-// $grid-float-breakpoint:     $screen-sm-min
-//** Point at which the navbar begins collapsing.
-// $grid-float-breakpoint-max: ($grid-float-breakpoint - 1)
-
-
-//== Container sizes
-//
-//## Define the maximum width of `.container` for different screen sizes.
-
-// Small screen / tablet
-// $container-tablet:             (720px + $grid-gutter-width)
-//** For `$screen-sm-min` and up.
-// $container-sm:                 $container-tablet
-
-// Medium screen / desktop
-// $container-desktop:            (940px + $grid-gutter-width)
-//** For `$screen-md-min` and up.
-// $container-md:                 $container-desktop
-
-// Large screen / wide desktop
-// $container-large-desktop:      (1140px + $grid-gutter-width)
-//** For `$screen-lg-min` and up.
-// $container-lg:                 $container-large-desktop
-
-
-//== Navbar
-//
-//##
-
-// Basics of a navbar
-// $navbar-height:                    50px
-// $navbar-margin-bottom:             $line-height-computed
-// $navbar-border-radius:             $border-radius-base
-// $navbar-padding-horizontal:        floor(($grid-gutter-width / 2))
-// $navbar-padding-vertical:          (($navbar-height - $line-height-computed) / 2)
-// $navbar-collapse-max-height:       340px
-
-// $navbar-default-color:             #777
-// $navbar-default-bg:                #f8f8f8
-// $navbar-default-border:            darken($navbar-default-bg, 6.5%)
-
-// Navbar links
-// $navbar-default-link-color:                #777
-// $navbar-default-link-hover-color:          #333
-// $navbar-default-link-hover-bg:             transparent
-// $navbar-default-link-active-color:         #555
-// $navbar-default-link-active-bg:            darken($navbar-default-bg, 6.5%)
-// $navbar-default-link-disabled-color:       #ccc
-// $navbar-default-link-disabled-bg:          transparent
-
-// Navbar brand label
-// $navbar-default-brand-color:               $navbar-default-link-color
-// $navbar-default-brand-hover-color:         darken($navbar-default-brand-color, 10%)
-// $navbar-default-brand-hover-bg:            transparent
-
-// Navbar toggle
-// $navbar-default-toggle-hover-bg:           #ddd
-// $navbar-default-toggle-icon-bar-bg:        #888
-// $navbar-default-toggle-border-color:       #ddd
-
-
-//=== Inverted navbar
-// Reset inverted navbar basics
-// $navbar-inverse-color:                      lighten($gray-light, 15%)
-// $navbar-inverse-bg:                         #222
-// $navbar-inverse-border:                     darken($navbar-inverse-bg, 10%)
-
-// Inverted navbar links
-// $navbar-inverse-link-color:                 lighten($gray-light, 15%)
-// $navbar-inverse-link-hover-color:           #fff
-// $navbar-inverse-link-hover-bg:              transparent
-// $navbar-inverse-link-active-color:          $navbar-inverse-link-hover-color
-// $navbar-inverse-link-active-bg:             darken($navbar-inverse-bg, 10%)
-// $navbar-inverse-link-disabled-color:        #444
-// $navbar-inverse-link-disabled-bg:           transparent
-
-// Inverted navbar brand label
-// $navbar-inverse-brand-color:                $navbar-inverse-link-color
-// $navbar-inverse-brand-hover-color:          #fff
-// $navbar-inverse-brand-hover-bg:             transparent
-
-// Inverted navbar toggle
-// $navbar-inverse-toggle-hover-bg:            #333
-// $navbar-inverse-toggle-icon-bar-bg:         #fff
-// $navbar-inverse-toggle-border-color:        #333
-
-
-//== Navs
-//
-//##
-
-//=== Shared nav styles
-// $nav-link-padding:                          10px 15px
-// $nav-link-hover-bg:                         $gray-lighter
-
-// $nav-disabled-link-color:                   $gray-light
-// $nav-disabled-link-hover-color:             $gray-light
-
-//== Tabs
-// $nav-tabs-border-color:                     #ddd
-
-// $nav-tabs-link-hover-border-color:          $gray-lighter
-
-// $nav-tabs-active-link-hover-bg:             $body-bg
-// $nav-tabs-active-link-hover-color:          $gray
-// $nav-tabs-active-link-hover-border-color:   #ddd
-
-// $nav-tabs-justified-link-border-color:            #ddd
-// $nav-tabs-justified-active-link-border-color:     $body-bg
-
-//== Pills
-// $nav-pills-border-radius:                   $border-radius-base
-// $nav-pills-active-link-hover-bg:            $component-active-bg
-// $nav-pills-active-link-hover-color:         $component-active-color
-
-
-//== Pagination
-//
-//##
-
-// $pagination-color:                     $link-color
-// $pagination-bg:                        #fff
-// $pagination-border:                    #ddd
-
-// $pagination-hover-color:               $link-hover-color
-// $pagination-hover-bg:                  $gray-lighter
-// $pagination-hover-border:              #ddd
-
-// $pagination-active-color:              #fff
-// $pagination-active-bg:                 $brand-primary
-// $pagination-active-border:             $brand-primary
-
-// $pagination-disabled-color:            $gray-light
-// $pagination-disabled-bg:               #fff
-// $pagination-disabled-border:           #ddd
-
-
-//== Pager
-//
-//##
-
-// $pager-bg:                             $pagination-bg
-// $pager-border:                         $pagination-border
-// $pager-border-radius:                  15px
-
-// $pager-hover-bg:                       $pagination-hover-bg
-
-// $pager-active-bg:                      $pagination-active-bg
-// $pager-active-color:                   $pagination-active-color
-
-// $pager-disabled-color:                 $pagination-disabled-color
-
-
-//== Jumbotron
-//
-//##
-
-// $jumbotron-padding:              30px
-// $jumbotron-color:                inherit
-// $jumbotron-bg:                   $gray-lighter
-// $jumbotron-heading-color:        inherit
-// $jumbotron-font-size:            ceil(($font-size-base * 1.5))
-// $jumbotron-heading-font-size:    ceil(($font-size-base * 4.5))
-
-
-//== Form states and alerts
-//
-//## Define colors for form feedback states and, by default, alerts.
-
-// $state-success-text:             #3c763d
-// $state-success-bg:               #dff0d8
-// $state-success-border:           darken(adjust-hue($state-success-bg, -10), 5%)
-
-// $state-info-text:                #31708f
-// $state-info-bg:                  #d9edf7
-// $state-info-border:              darken(adjust-hue($state-info-bg, -10), 7%)
-
-// $state-warning-text:             #8a6d3b
-// $state-warning-bg:               #fcf8e3
-// $state-warning-border:           darken(adjust-hue($state-warning-bg, -10), 5%)
-
-// $state-danger-text:              #a94442
-// $state-danger-bg:                #f2dede
-// $state-danger-border:            darken(adjust-hue($state-danger-bg, -10), 5%)
-
-
-//== Tooltips
-//
-//##
-
-//** Tooltip max width
-// $tooltip-max-width:           200px
-//** Tooltip text color
-// $tooltip-color:               #fff
-//** Tooltip background color
-// $tooltip-bg:                  #000
-// $tooltip-opacity:             .9
-
-//** Tooltip arrow width
-// $tooltip-arrow-width:         5px
-//** Tooltip arrow color
-// $tooltip-arrow-color:         $tooltip-bg
-
-
-//== Popovers
-//
-//##
-
-//** Popover body background color
-// $popover-bg:                          #fff
-//** Popover maximum width
-// $popover-max-width:                   276px
-//** Popover border color
-// $popover-border-color:                rgba(0,0,0,.2)
-//** Popover fallback border color
-// $popover-fallback-border-color:       #ccc
-
-//** Popover title background color
-// $popover-title-bg:                    darken($popover-bg, 3%)
-
-//** Popover arrow width
-// $popover-arrow-width:                 10px
-//** Popover arrow color
-// $popover-arrow-color:                 $popover-bg
-
-//** Popover outer arrow width
-// $popover-arrow-outer-width:           ($popover-arrow-width + 1)
-//** Popover outer arrow color
-// $popover-arrow-outer-color:           fade_in($popover-border-color, 0.05)
-//** Popover outer arrow fallback color
-// $popover-arrow-outer-fallback-color:  darken($popover-fallback-border-color, 20%)
-
-
-//== Labels
-//
-//##
-
-//** Default label background color
-// $label-default-bg:            $gray-light
-//** Primary label background color
-// $label-primary-bg:            $brand-primary
-//** Success label background color
-// $label-success-bg:            $brand-success
-//** Info label background color
-// $label-info-bg:               $brand-info
-//** Warning label background color
-// $label-warning-bg:            $brand-warning
-//** Danger label background color
-// $label-danger-bg:             $brand-danger
-
-//** Default label text color
-// $label-color:                 #fff
-//** Default text color of a linked label
-// $label-link-hover-color:      #fff
-
-
-//== Modals
-//
-//##
-
-//** Padding applied to the modal body
-// $modal-inner-padding:         15px
-
-//** Padding applied to the modal title
-// $modal-title-padding:         15px
-//** Modal title line-height
-// $modal-title-line-height:     $line-height-base
-
-//** Background color of modal content area
-// $modal-content-bg:                             #fff
-//** Modal content border color
-// $modal-content-border-color:                   rgba(0,0,0,.2)
-//** Modal content border color **for IE8**
-// $modal-content-fallback-border-color:          #999
-
-//** Modal backdrop background color
-// $modal-backdrop-bg:           #000
-//** Modal backdrop opacity
-// $modal-backdrop-opacity:      .5
-//** Modal header border color
-// $modal-header-border-color:   #e5e5e5
-//** Modal footer border color
-// $modal-footer-border-color:   $modal-header-border-color
-
-// $modal-lg:                    900px
-// $modal-md:                    600px
-// $modal-sm:                    300px
-
-
-//== Alerts
-//
-//## Define alert colors, border radius, and padding.
-
-// $alert-padding:               15px
-// $alert-border-radius:         $border-radius-base
-// $alert-link-font-weight:      bold
-
-// $alert-success-bg:            $state-success-bg
-// $alert-success-text:          $state-success-text
-// $alert-success-border:        $state-success-border
-
-// $alert-info-bg:               $state-info-bg
-// $alert-info-text:             $state-info-text
-// $alert-info-border:           $state-info-border
-
-// $alert-warning-bg:            $state-warning-bg
-// $alert-warning-text:          $state-warning-text
-// $alert-warning-border:        $state-warning-border
-
-// $alert-danger-bg:             $state-danger-bg
-// $alert-danger-text:           $state-danger-text
-// $alert-danger-border:         $state-danger-border
-
-
-//== Progress bars
-//
-//##
-
-//** Background color of the whole progress component
-// $progress-bg:                 #f5f5f5
-//** Progress bar text color
-// $progress-bar-color:          #fff
-//** Variable for setting rounded corners on progress bar.
-// $progress-border-radius:      $border-radius-base
-
-//** Default progress bar color
-// $progress-bar-bg:             $brand-primary
-//** Success progress bar color
-// $progress-bar-success-bg:     $brand-success
-//** Warning progress bar color
-// $progress-bar-warning-bg:     $brand-warning
-//** Danger progress bar color
-// $progress-bar-danger-bg:      $brand-danger
-//** Info progress bar color
-// $progress-bar-info-bg:        $brand-info
-
-
-//== List group
-//
-//##
-
-//** Background color on `.list-group-item`
-// $list-group-bg:                 #fff
-//** `.list-group-item` border color
-// $list-group-border:             #ddd
-//** List group border radius
-// $list-group-border-radius:      $border-radius-base
-
-//** Background color of single list items on hover
-// $list-group-hover-bg:           #f5f5f5
-//** Text color of active list items
-// $list-group-active-color:       $component-active-color
-//** Background color of active list items
-// $list-group-active-bg:          $component-active-bg
-//** Border color of active list elements
-// $list-group-active-border:      $list-group-active-bg
-//** Text color for content within active list items
-// $list-group-active-text-color:  lighten($list-group-active-bg, 40%)
-
-//** Text color of disabled list items
-// $list-group-disabled-color:      $gray-light
-//** Background color of disabled list items
-// $list-group-disabled-bg:         $gray-lighter
-//** Text color for content within disabled list items
-// $list-group-disabled-text-color: $list-group-disabled-color
-
-// $list-group-link-color:         #555
-// $list-group-link-hover-color:   $list-group-link-color
-// $list-group-link-heading-color: #333
-
-
-//== Panels
-//
-//##
-
-// $panel-bg:                    #fff
-// $panel-body-padding:          15px
-// $panel-heading-padding:       10px 15px
-// $panel-footer-padding:        $panel-heading-padding
-// $panel-border-radius:         $border-radius-base
-
-//** Border color for elements within panels
-// $panel-inner-border:          #ddd
-// $panel-footer-bg:             #f5f5f5
-
-// $panel-default-text:          $gray-dark
-// $panel-default-border:        #ddd
-// $panel-default-heading-bg:    #f5f5f5
-
-// $panel-primary-text:          #fff
-// $panel-primary-border:        $brand-primary
-// $panel-primary-heading-bg:    $brand-primary
-
-// $panel-success-text:          $state-success-text
-// $panel-success-border:        $state-success-border
-// $panel-success-heading-bg:    $state-success-bg
-
-// $panel-info-text:             $state-info-text
-// $panel-info-border:           $state-info-border
-// $panel-info-heading-bg:       $state-info-bg
-
-// $panel-warning-text:          $state-warning-text
-// $panel-warning-border:        $state-warning-border
-// $panel-warning-heading-bg:    $state-warning-bg
-
-// $panel-danger-text:           $state-danger-text
-// $panel-danger-border:         $state-danger-border
-// $panel-danger-heading-bg:     $state-danger-bg
-
-
-//== Thumbnails
-//
-//##
-
-//** Padding around the thumbnail image
-// $thumbnail-padding:           4px
-//** Thumbnail background color
-// $thumbnail-bg:                $body-bg
-//** Thumbnail border color
-// $thumbnail-border:            #ddd
-//** Thumbnail border radius
-// $thumbnail-border-radius:     $border-radius-base
-
-//** Custom text color for thumbnail captions
-// $thumbnail-caption-color:     $text-color
-//** Padding around the thumbnail caption
-// $thumbnail-caption-padding:   9px
-
-
-//== Wells
-//
-//##
-
-// $well-bg:                     #f5f5f5
-// $well-border:                 darken($well-bg, 7%)
-
-
-//== Badges
-//
-//##
-
-// $badge-color:                 #fff
-//** Linked badge text color on hover
-// $badge-link-hover-color:      #fff
-// $badge-bg:                    $gray-light
-
-//** Badge text color in active nav link
-// $badge-active-color:          $link-color
-//** Badge background color in active nav link
-// $badge-active-bg:             #fff
-
-// $badge-font-weight:           bold
-// $badge-line-height:           1
-// $badge-border-radius:         10px
-
-
-//== Breadcrumbs
-//
-//##
-
-// $breadcrumb-padding-vertical:   8px
-// $breadcrumb-padding-horizontal: 15px
-//** Breadcrumb background color
-// $breadcrumb-bg:                 #f5f5f5
-//** Breadcrumb text color
-// $breadcrumb-color:              #ccc
-//** Text color of current page in the breadcrumb
-// $breadcrumb-active-color:       $gray-light
-//** Textual separator for between breadcrumb elements
-// $breadcrumb-separator:          "/"
-
-
-//== Carousel
-//
-//##
-
-// $carousel-text-shadow:                        0 1px 2px rgba(0,0,0,.6)
-
-// $carousel-control-color:                      #fff
-// $carousel-control-width:                      15%
-// $carousel-control-opacity:                    .5
-// $carousel-control-font-size:                  20px
-
-// $carousel-indicator-active-bg:                #fff
-// $carousel-indicator-border-color:             #fff
-
-// $carousel-caption-color:                      #fff
-
-
-//== Close
-//
-//##
-
-// $close-font-weight:           bold
-// $close-color:                 #000
-// $close-text-shadow:           0 1px 0 #fff
-
-
-//== Code
-//
-//##
-
-// $code-color:                  #c7254e
-// $code-bg:                     #f9f2f4
-
-// $kbd-color:                   #fff
-// $kbd-bg:                      #333
-
-// $pre-bg:                      #f5f5f5
-// $pre-color:                   $gray-dark
-// $pre-border-color:            #ccc
-// $pre-scrollable-max-height:   340px
-
-
-//== Type
-//
-//##
-
-//** Horizontal offset for forms and lists.
-// $component-offset-horizontal: 180px
-//** Text muted color
-// $text-muted:                  $gray-light
-//** Abbreviations and acronyms border color
-// $abbr-border-color:           $gray-light
-//** Headings small color
-// $headings-small-color:        $gray-light
-//** Blockquote small color
-// $blockquote-small-color:      $gray-light
-//** Blockquote font size
-// $blockquote-font-size:        ($font-size-base * 1.25)
-//** Blockquote border color
-// $blockquote-border-color:     $gray-lighter
-//** Page header border color
-// $page-header-border-color:    $gray-lighter
-//** Width of horizontal description list titles
-// $dl-horizontal-offset:        $component-offset-horizontal
-//** Point at which .dl-horizontal becomes horizontal
-// $dl-horizontal-breakpoint:    $grid-float-breakpoint
-//** Horizontal line color.
-// $hr-border:                   $gray-lighter
diff --git a/public/stylesheets/index.scss b/public/stylesheets/index.scss
deleted file mode 100644
index d13dcfa90..000000000
--- a/public/stylesheets/index.scss
+++ /dev/null
@@ -1,88 +0,0 @@
-.span_action {
-  margin: 5px;
-  cursor: pointer;
-}
-
-header div {
-  height: 50px;
-  line-height: 25px;
-  margin-bottom: 50px;
-}
-
-menu {
-  margin-right: 20px;
-  border-right: 1px solid rgba(0, 0, 0, 0.2);
-}
-
-menu .panel_button {
-  margin: 8px;
-  cursor: pointer;
-  transition: margin 0.2s;
-}
-
-menu .panel_button:hover {
-  margin-left: 15px;
-}
-
-menu .glyphicon {
-  margin: 5px;
-}
-
-#ajax_load {
-  min-height: 500px;
-}
-
-.loading {
-  display: inline-block;
-  margin-top: 100px;
-}
-
-.video {
-  margin-bottom: 10px;
-  transition: margin 0.5s ease;
-}
-
-.video:hover {
-  margin-left: 5px;
-}
-
-.video_name {
-  cursor: pointer;
-  margin-right: 5px;
-}
-
-.video_pod_url {
-  font-size: small;
-  color: rgba(0, 0, 0, 0.5);
-}
-
-.video_description {
-  font-size: small;
-  font-style: italic;
-  margin-left: 7px;
-}
-
-.btn-file {
-  position: relative;
-  overflow: hidden;
-}
-.btn-file input[type=file] {
-  position: absolute;
-  top: 0;
-  right: 0;
-  min-width: 100%;
-  min-height: 100%;
-  font-size: 100px;
-  text-align: right;
-  filter: alpha(opacity=0);
-  opacity: 0;
-  outline: none;
-  background: white;
-  cursor: inherit;
-  display: block;
-}
-
-.name_file {
-  display: inline-block;
-  margin-left: 10px;
-}
\ No newline at end of file
diff --git a/server.js b/server.js
index c971982b3..5f474a196 100644
--- a/server.js
+++ b/server.js
@@ -14,7 +14,7 @@ var WebSocketServer = require('ws').Server
 var app = express()
 
 // ----------- Checker -----------
-var checker = require('./initializers/checker')
+var checker = require('./server/initializers/checker')
 
 var miss = checker.checkConfig()
 if (miss.length !== 0) {
@@ -25,16 +25,16 @@ checker.createDirectoriesIfNotExist()
 
 // ----------- PeerTube modules -----------
 var config = require('config')
-var constants = require('./initializers/constants')
-var customValidators = require('./helpers/customValidators')
-var database = require('./initializers/database')
-var logger = require('./helpers/logger')
-var peertubeCrypto = require('./helpers/peertubeCrypto')
-var poolRequests = require('./lib/poolRequests')
-var routes = require('./controllers')
-var utils = require('./helpers/utils')
-var videos = require('./lib/videos')
-var webtorrent = require('./lib/webtorrent')
+var constants = require('./server/initializers/constants')
+var customValidators = require('./server/helpers/customValidators')
+var database = require('./server/initializers/database')
+var logger = require('./server/helpers/logger')
+var peertubeCrypto = require('./server/helpers/peertubeCrypto')
+var poolRequests = require('./server/lib/poolRequests')
+var routes = require('./server/controllers')
+var utils = require('./server/helpers/utils')
+var videos = require('./server/lib/videos')
+var webtorrent = require('./server/lib/webtorrent')
 
 // Get configurations
 var port = config.get('listen.port')
@@ -67,11 +67,7 @@ app.use(require('connect-livereload')({
 require('segfault-handler').registerHandler()
 
 // Static files
-app.use(express.static(path.join(__dirname, '/public'), { maxAge: 0 }))
-
-// Jade template from ./views directory
-app.set('views', path.join(__dirname, '/views'))
-app.set('view engine', 'jade')
+app.use(express.static(path.join(__dirname, '/app'), { maxAge: 0 }))
 
 // API routes
 var api_route = '/api/' + constants.API_VERSION
diff --git a/server/controllers/api/v1/index.js b/server/controllers/api/v1/index.js
new file mode 100644
index 000000000..07a68ed9d
--- /dev/null
+++ b/server/controllers/api/v1/index.js
@@ -0,0 +1,17 @@
+'use strict'
+
+var express = require('express')
+
+var router = express.Router()
+
+var podsController = require('./pods')
+var remoteVideosController = require('./remoteVideos')
+var videosController = require('./videos')
+
+router.use('/pods', podsController)
+router.use('/remotevideos', remoteVideosController)
+router.use('/videos', videosController)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
diff --git a/server/controllers/api/v1/pods.js b/server/controllers/api/v1/pods.js
new file mode 100644
index 000000000..c93a86ee8
--- /dev/null
+++ b/server/controllers/api/v1/pods.js
@@ -0,0 +1,93 @@
+'use strict'
+
+var express = require('express')
+var fs = require('fs')
+
+var logger = require('../../../helpers/logger')
+var friends = require('../../../lib/friends')
+var middleware = require('../../../middlewares')
+var cacheMiddleware = middleware.cache
+var peertubeCrypto = require('../../../helpers/peertubeCrypto')
+var Pods = require('../../../models/pods')
+var reqValidator = middleware.reqValidators.pods
+var secureMiddleware = middleware.secure
+var secureRequest = middleware.reqValidators.remote.secureRequest
+var Videos = require('../../../models/videos')
+
+var router = express.Router()
+
+router.get('/', cacheMiddleware.cache(false), listPods)
+router.post('/', reqValidator.podsAdd, cacheMiddleware.cache(false), addPods)
+router.get('/makefriends', reqValidator.makeFriends, cacheMiddleware.cache(false), makeFriends)
+router.get('/quitfriends', cacheMiddleware.cache(false), quitFriends)
+// Post because this is a secured request
+router.post('/remove', secureRequest, secureMiddleware.decryptBody, removePods)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function addPods (req, res, next) {
+  var informations = req.body.data
+  Pods.add(informations, function (err) {
+    if (err) return next(err)
+
+    Videos.addRemotes(informations.videos)
+
+    fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
+      if (err) {
+        logger.error('Cannot read cert file.')
+        return next(err)
+      }
+
+      Videos.listOwned(function (err, videos_list) {
+        if (err) {
+          logger.error('Cannot get the list of owned videos.')
+          return next(err)
+        }
+
+        res.json({ cert: cert, videos: videos_list })
+      })
+    })
+  })
+}
+
+function listPods (req, res, next) {
+  Pods.list(function (err, pods_list) {
+    if (err) return next(err)
+
+    res.json(pods_list)
+  })
+}
+
+function makeFriends (req, res, next) {
+  friends.makeFriends(function (err) {
+    if (err) return next(err)
+
+    res.sendStatus(204)
+  })
+}
+
+function removePods (req, res, next) {
+  var url = req.body.signature.url
+  Pods.remove(url, function (err) {
+    if (err) return next(err)
+
+    Videos.removeAllRemotesOf(url, function (err) {
+      if (err) logger.error('Cannot remove all remote videos of %s.', url)
+      else logger.info('%s pod removed.', url)
+
+      res.sendStatus(204)
+    })
+  })
+}
+
+function quitFriends (req, res, next) {
+  friends.quitFriends(function (err) {
+    if (err) return next(err)
+
+    res.sendStatus(204)
+  })
+}
diff --git a/server/controllers/api/v1/remoteVideos.js b/server/controllers/api/v1/remoteVideos.js
new file mode 100644
index 000000000..475a874cf
--- /dev/null
+++ b/server/controllers/api/v1/remoteVideos.js
@@ -0,0 +1,53 @@
+'use strict'
+
+var express = require('express')
+var pluck = require('lodash-node/compat/collection/pluck')
+
+var middleware = require('../../../middlewares')
+var secureMiddleware = middleware.secure
+var cacheMiddleware = middleware.cache
+var reqValidator = middleware.reqValidators.remote
+var videos = require('../../../models/videos')
+
+var router = express.Router()
+
+router.post('/add',
+  reqValidator.secureRequest,
+  secureMiddleware.decryptBody,
+  reqValidator.remoteVideosAdd,
+  cacheMiddleware.cache(false),
+  addRemoteVideos
+)
+
+router.post('/remove',
+  reqValidator.secureRequest,
+  secureMiddleware.decryptBody,
+  reqValidator.remoteVideosRemove,
+  cacheMiddleware.cache(false),
+  removeRemoteVideo
+)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function addRemoteVideos (req, res, next) {
+  videos.addRemotes(req.body.data, function (err, videos) {
+    if (err) return next(err)
+
+    res.json(videos)
+  })
+}
+
+function removeRemoteVideo (req, res, next) {
+  var url = req.body.signature.url
+  var magnetUris = pluck(req.body.data, 'magnetUri')
+
+  videos.removeRemotesOfByMagnetUris(url, magnetUris, function (err) {
+    if (err) return next(err)
+
+    res.sendStatus(204)
+  })
+}
diff --git a/server/controllers/api/v1/videos.js b/server/controllers/api/v1/videos.js
new file mode 100644
index 000000000..620711925
--- /dev/null
+++ b/server/controllers/api/v1/videos.js
@@ -0,0 +1,144 @@
+'use strict'
+
+var config = require('config')
+var crypto = require('crypto')
+var express = require('express')
+var multer = require('multer')
+
+var logger = require('../../../helpers/logger')
+var friends = require('../../../lib/friends')
+var middleware = require('../../../middlewares')
+var cacheMiddleware = middleware.cache
+var reqValidator = middleware.reqValidators.videos
+var Videos = require('../../../models/videos') // model
+var videos = require('../../../lib/videos')
+var webtorrent = require('../../../lib/webtorrent')
+
+var router = express.Router()
+var uploads = config.get('storage.uploads')
+
+// multer configuration
+var storage = multer.diskStorage({
+  destination: function (req, file, cb) {
+    cb(null, uploads)
+  },
+
+  filename: function (req, file, cb) {
+    var extension = ''
+    if (file.mimetype === 'video/webm') extension = 'webm'
+    else if (file.mimetype === 'video/mp4') extension = 'mp4'
+    else if (file.mimetype === 'video/ogg') extension = 'ogv'
+    crypto.pseudoRandomBytes(16, function (err, raw) {
+      var fieldname = err ? undefined : raw.toString('hex')
+      cb(null, fieldname + '.' + extension)
+    })
+  }
+})
+
+var reqFiles = multer({ storage: storage }).fields([{ name: 'input_video', maxCount: 1 }])
+
+router.get('/', cacheMiddleware.cache(false), listVideos)
+router.post('/', reqFiles, reqValidator.videosAdd, cacheMiddleware.cache(false), addVideo)
+router.get('/:id', reqValidator.videosGet, cacheMiddleware.cache(false), getVideos)
+router.delete('/:id', reqValidator.videosRemove, cacheMiddleware.cache(false), removeVideo)
+router.get('/search/:name', reqValidator.videosSearch, cacheMiddleware.cache(false), searchVideos)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function addVideo (req, res, next) {
+  var video_file = req.files.input_video[0]
+  var video_infos = req.body
+
+  videos.seed(video_file.path, function (err, torrent) {
+    if (err) {
+      logger.error('Cannot seed this video.')
+      return next(err)
+    }
+
+    var video_data = {
+      name: video_infos.name,
+      namePath: video_file.filename,
+      description: video_infos.description,
+      magnetUri: torrent.magnetURI
+    }
+
+    Videos.add(video_data, function (err) {
+      if (err) {
+        // TODO unseed the video
+        logger.error('Cannot insert this video in the database.')
+        return next(err)
+      }
+
+      // Now we'll add the video's meta data to our friends
+      friends.addVideoToFriends(video_data)
+
+      // TODO : include Location of the new video
+      res.sendStatus(201)
+    })
+  })
+}
+
+function getVideos (req, res, next) {
+  Videos.get(req.params.id, function (err, video) {
+    if (err) return next(err)
+
+    if (video === null) {
+      return res.sendStatus(404)
+    }
+
+    res.json(video)
+  })
+}
+
+function listVideos (req, res, next) {
+  Videos.list(function (err, videos_list) {
+    if (err) return next(err)
+
+    res.json(videos_list)
+  })
+}
+
+function removeVideo (req, res, next) {
+  var video_id = req.params.id
+  Videos.get(video_id, function (err, video) {
+    if (err) return next(err)
+
+    removeTorrent(video.magnetUri, function () {
+      Videos.removeOwned(req.params.id, function (err) {
+        if (err) return next(err)
+
+        var params = {
+          name: video.name,
+          magnetUri: video.magnetUri
+        }
+
+        friends.removeVideoToFriends(params)
+        res.sendStatus(204)
+      })
+    })
+  })
+}
+
+function searchVideos (req, res, next) {
+  Videos.search(req.params.name, function (err, videos_list) {
+    if (err) return next(err)
+
+    res.json(videos_list)
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+// Maybe the torrent is not seeded, but we catch the error to don't stop the removing process
+function removeTorrent (magnetUri, callback) {
+  try {
+    webtorrent.remove(magnetUri, callback)
+  } catch (err) {
+    logger.warn('Cannot remove the torrent from WebTorrent', { err: err })
+    return callback(null)
+  }
+}
diff --git a/server/controllers/index.js b/server/controllers/index.js
new file mode 100644
index 000000000..858f493da
--- /dev/null
+++ b/server/controllers/index.js
@@ -0,0 +1,11 @@
+'use strict'
+
+var constants = require('../initializers/constants')
+
+var apiController = require('./api/' + constants.API_VERSION)
+var viewsController = require('./views')
+
+module.exports = {
+  api: apiController,
+  views: viewsController
+}
diff --git a/server/controllers/views.js b/server/controllers/views.js
new file mode 100644
index 000000000..aa9718079
--- /dev/null
+++ b/server/controllers/views.js
@@ -0,0 +1,27 @@
+'use strict'
+
+var express = require('express')
+
+var cacheMiddleware = require('../middlewares').cache
+
+var router = express.Router()
+
+router.get(/^\/(index)?$/, cacheMiddleware.cache(), getIndex)
+router.get('/partials/:directory/:name', cacheMiddleware.cache(), getPartial)
+
+// ---------------------------------------------------------------------------
+
+module.exports = router
+
+// ---------------------------------------------------------------------------
+
+function getIndex (req, res) {
+  res.render('index')
+}
+
+function getPartial (req, res) {
+  var directory = req.params.directory
+  var name = req.params.name
+
+  res.render('partials/' + directory + '/' + name)
+}
diff --git a/server/helpers/customValidators.js b/server/helpers/customValidators.js
new file mode 100644
index 000000000..20c41f5da
--- /dev/null
+++ b/server/helpers/customValidators.js
@@ -0,0 +1,32 @@
+'use strict'
+
+var validator = require('validator')
+
+var customValidators = {
+  eachIsRemoteVideosAddValid: eachIsRemoteVideosAddValid,
+  eachIsRemoteVideosRemoveValid: eachIsRemoteVideosRemoveValid,
+  isArray: isArray
+}
+
+function eachIsRemoteVideosAddValid (values) {
+  return values.every(function (val) {
+    return validator.isLength(val.name, 1, 50) &&
+      validator.isLength(val.description, 1, 50) &&
+      validator.isLength(val.magnetUri, 10) &&
+      validator.isURL(val.podUrl)
+  })
+}
+
+function eachIsRemoteVideosRemoveValid (values) {
+  return values.every(function (val) {
+    return validator.isLength(val.magnetUri, 10)
+  })
+}
+
+function isArray (value) {
+  return Array.isArray(value)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = customValidators
diff --git a/server/helpers/logger.js b/server/helpers/logger.js
new file mode 100644
index 000000000..67f69a875
--- /dev/null
+++ b/server/helpers/logger.js
@@ -0,0 +1,40 @@
+// Thanks http://tostring.it/2014/06/23/advanced-logging-with-nodejs/
+'use strict'
+
+var config = require('config')
+var path = require('path')
+var winston = require('winston')
+winston.emitErrs = true
+
+var logDir = path.join(__dirname, '..', config.get('storage.logs'))
+var logger = new winston.Logger({
+  transports: [
+    new winston.transports.File({
+      level: 'debug',
+      filename: path.join(logDir, 'all-logs.log'),
+      handleExceptions: true,
+      json: true,
+      maxsize: 5242880,
+      maxFiles: 5,
+      colorize: false
+    }),
+    new winston.transports.Console({
+      level: 'debug',
+      handleExceptions: true,
+      humanReadableUnhandledException: true,
+      json: false,
+      colorize: true
+    })
+  ],
+  exitOnError: true
+})
+
+logger.stream = {
+  write: function (message, encoding) {
+    logger.info(message)
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = logger
diff --git a/server/helpers/peertubeCrypto.js b/server/helpers/peertubeCrypto.js
new file mode 100644
index 000000000..29b9d79c9
--- /dev/null
+++ b/server/helpers/peertubeCrypto.js
@@ -0,0 +1,147 @@
+'use strict'
+
+var config = require('config')
+var crypto = require('crypto')
+var fs = require('fs')
+var openssl = require('openssl-wrapper')
+var path = require('path')
+var ursa = require('ursa')
+
+var logger = require('./logger')
+
+var certDir = path.join(__dirname, '..', config.get('storage.certs'))
+var algorithm = 'aes-256-ctr'
+
+var peertubeCrypto = {
+  checkSignature: checkSignature,
+  createCertsIfNotExist: createCertsIfNotExist,
+  decrypt: decrypt,
+  encrypt: encrypt,
+  getCertDir: getCertDir,
+  sign: sign
+}
+
+function checkSignature (public_key, raw_data, hex_signature) {
+  var crt = ursa.createPublicKey(public_key)
+  var is_valid = crt.hashAndVerify('sha256', new Buffer(raw_data).toString('hex'), hex_signature, 'hex')
+  return is_valid
+}
+
+function createCertsIfNotExist (callback) {
+  certsExist(function (exist) {
+    if (exist === true) {
+      return callback(null)
+    }
+
+    createCerts(function (err) {
+      return callback(err)
+    })
+  })
+}
+
+function decrypt (key, data, callback) {
+  fs.readFile(getCertDir() + 'peertube.key.pem', function (err, file) {
+    if (err) return callback(err)
+
+    var my_private_key = ursa.createPrivateKey(file)
+    var decrypted_key = my_private_key.decrypt(key, 'hex', 'utf8')
+    var decrypted_data = symetricDecrypt(data, decrypted_key)
+
+    return callback(null, decrypted_data)
+  })
+}
+
+function encrypt (public_key, data, callback) {
+  var crt = ursa.createPublicKey(public_key)
+
+  symetricEncrypt(data, function (err, dataEncrypted) {
+    if (err) return callback(err)
+
+    var key = crt.encrypt(dataEncrypted.password, 'utf8', 'hex')
+    var encrypted = {
+      data: dataEncrypted.crypted,
+      key: key
+    }
+
+    callback(null, encrypted)
+  })
+}
+
+function getCertDir () {
+  return certDir
+}
+
+function sign (data) {
+  var myKey = ursa.createPrivateKey(fs.readFileSync(certDir + 'peertube.key.pem'))
+  var signature = myKey.hashAndSign('sha256', data, 'utf8', 'hex')
+
+  return signature
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = peertubeCrypto
+
+// ---------------------------------------------------------------------------
+
+function certsExist (callback) {
+  fs.exists(certDir + 'peertube.key.pem', function (exists) {
+    return callback(exists)
+  })
+}
+
+function createCerts (callback) {
+  certsExist(function (exist) {
+    if (exist === true) {
+      var string = 'Certs already exist.'
+      logger.warning(string)
+      return callback(new Error(string))
+    }
+
+    logger.info('Generating a RSA key...')
+    openssl.exec('genrsa', { 'out': certDir + 'peertube.key.pem', '2048': false }, function (err) {
+      if (err) {
+        logger.error('Cannot create private key on this pod.')
+        return callback(err)
+      }
+      logger.info('RSA key generated.')
+
+      logger.info('Manage public key...')
+      openssl.exec('rsa', { 'in': certDir + 'peertube.key.pem', 'pubout': true, 'out': certDir + 'peertube.pub' }, function (err) {
+        if (err) {
+          logger.error('Cannot create public key on this pod.')
+          return callback(err)
+        }
+
+        logger.info('Public key managed.')
+        return callback(null)
+      })
+    })
+  })
+}
+
+function generatePassword (callback) {
+  crypto.randomBytes(32, function (err, buf) {
+    if (err) return callback(err)
+
+    callback(null, buf.toString('utf8'))
+  })
+}
+
+function symetricDecrypt (text, password) {
+  var decipher = crypto.createDecipher(algorithm, password)
+  var dec = decipher.update(text, 'hex', 'utf8')
+  dec += decipher.final('utf8')
+  return dec
+}
+
+function symetricEncrypt (text, callback) {
+  generatePassword(function (err, password) {
+    if (err) return callback(err)
+
+    var cipher = crypto.createCipher(algorithm, password)
+    var crypted = cipher.update(text, 'utf8', 'hex')
+    crypted += cipher.final('hex')
+    callback(null, { crypted: crypted, password: password })
+  })
+}
diff --git a/server/helpers/requests.js b/server/helpers/requests.js
new file mode 100644
index 000000000..e19afa5ca
--- /dev/null
+++ b/server/helpers/requests.js
@@ -0,0 +1,109 @@
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var request = require('request')
+var replay = require('request-replay')
+
+var constants = require('../initializers/constants')
+var logger = require('./logger')
+var peertubeCrypto = require('./peertubeCrypto')
+
+var http = config.get('webserver.https') ? 'https' : 'http'
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+
+var requests = {
+  makeMultipleRetryRequest: makeMultipleRetryRequest
+}
+
+function makeMultipleRetryRequest (all_data, pods, callbackEach, callback) {
+  if (!callback) {
+    callback = callbackEach
+    callbackEach = null
+  }
+
+  var url = http + '://' + host + ':' + port
+  var signature
+
+  // Add signature if it is specified in the params
+  if (all_data.method === 'POST' && all_data.data && all_data.sign === true) {
+    signature = peertubeCrypto.sign(url)
+  }
+
+  // Make a request for each pod
+  async.each(pods, function (pod, callback_each_async) {
+    function callbackEachRetryRequest (err, response, body, url, pod) {
+      if (callbackEach !== null) {
+        callbackEach(err, response, body, url, pod, function () {
+          callback_each_async()
+        })
+      } else {
+        callback_each_async()
+      }
+    }
+
+    var params = {
+      url: pod.url + all_data.path,
+      method: all_data.method
+    }
+
+    // Add data with POST requst ?
+    if (all_data.method === 'POST' && all_data.data) {
+      // Encrypt data ?
+      if (all_data.encrypt === true) {
+        // TODO: ES6 with let
+        ;(function (copy_params, copy_url, copy_pod, copy_signature) {
+          peertubeCrypto.encrypt(pod.publicKey, JSON.stringify(all_data.data), function (err, encrypted) {
+            if (err) return callback(err)
+
+            copy_params.json = {
+              data: encrypted.data,
+              key: encrypted.key
+            }
+
+            makeRetryRequest(copy_params, copy_url, copy_pod, copy_signature, callbackEachRetryRequest)
+          })
+        })(params, url, pod, signature)
+      } else {
+        params.json = { data: all_data.data }
+        makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
+      }
+    } else {
+      makeRetryRequest(params, url, pod, signature, callbackEachRetryRequest)
+    }
+  }, callback)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = requests
+
+// ---------------------------------------------------------------------------
+
+function makeRetryRequest (params, from_url, to_pod, signature, callbackEach) {
+  // Append the signature
+  if (signature) {
+    params.json.signature = {
+      url: from_url,
+      signature: signature
+    }
+  }
+
+  logger.debug('Make retry requests to %s.', to_pod.url)
+
+  replay(
+    request.post(params, function (err, response, body) {
+      callbackEach(err, response, body, params.url, to_pod)
+    }),
+    {
+      retries: constants.REQUEST_RETRIES,
+      factor: 3,
+      maxTimeout: Infinity,
+      errorCodes: [ 'EADDRINFO', 'ETIMEDOUT', 'ECONNRESET', 'ESOCKETTIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED' ]
+    }
+  ).on('replay', function (replay) {
+    logger.info('Replaying request to %s. Request failed: %d %s. Replay number: #%d. Will retry in: %d ms.',
+      params.url, replay.error.code, replay.error.message, replay.number, replay.delay)
+  })
+}
diff --git a/server/helpers/utils.js b/server/helpers/utils.js
new file mode 100644
index 000000000..d2c9ad8b2
--- /dev/null
+++ b/server/helpers/utils.js
@@ -0,0 +1,16 @@
+'use strict'
+
+var logger = require('./logger')
+
+var utils = {
+  cleanForExit: cleanForExit
+}
+
+function cleanForExit (webtorrent_process) {
+  logger.info('Gracefully exiting.')
+  process.kill(-webtorrent_process.pid)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = utils
diff --git a/server/initializers/checker.js b/server/initializers/checker.js
new file mode 100644
index 000000000..ec7bc0ad2
--- /dev/null
+++ b/server/initializers/checker.js
@@ -0,0 +1,46 @@
+'use strict'
+
+var config = require('config')
+var mkdirp = require('mkdirp')
+var path = require('path')
+
+var checker = {
+  checkConfig: checkConfig,
+  createDirectoriesIfNotExist: createDirectoriesIfNotExist
+}
+
+// Check the config files
+function checkConfig () {
+  var required = [ 'listen.port',
+    'webserver.https', 'webserver.host', 'webserver.port',
+    'database.host', 'database.port', 'database.suffix',
+    'storage.certs', 'storage.uploads', 'storage.logs',
+    'network.friends' ]
+  var miss = []
+
+  for (var key of required) {
+    if (!config.has(key)) {
+      miss.push(key)
+    }
+  }
+
+  return miss
+}
+
+// Create directories for the storage if it doesn't exist
+function createDirectoriesIfNotExist () {
+  var storages = config.get('storage')
+
+  for (var key of Object.keys(storages)) {
+    var dir = storages[key]
+    try {
+      mkdirp.sync(path.join(__dirname, '..', dir))
+    } catch (error) {
+      throw new Error('Cannot create ' + path + ':' + error)
+    }
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = checker
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
new file mode 100644
index 000000000..16e50443b
--- /dev/null
+++ b/server/initializers/constants.js
@@ -0,0 +1,42 @@
+'use strict'
+
+// API version of our pod
+var API_VERSION = 'v1'
+
+// Score a pod has when we create it as a friend
+var FRIEND_BASE_SCORE = 100
+
+// Time to wait between requests to the friends
+var INTERVAL = 60000
+
+// Number of points we add/remove from a friend after a successful/bad request
+var PODS_SCORE = {
+  MALUS: -10,
+  BONUS: 10
+}
+
+// Number of retries we make for the make retry requests (to friends...)
+var REQUEST_RETRIES = 10
+
+// Special constants for a test instance
+if (isTestInstance() === true) {
+  FRIEND_BASE_SCORE = 20
+  INTERVAL = 10000
+  REQUEST_RETRIES = 2
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = {
+  API_VERSION: API_VERSION,
+  FRIEND_BASE_SCORE: FRIEND_BASE_SCORE,
+  INTERVAL: INTERVAL,
+  PODS_SCORE: PODS_SCORE,
+  REQUEST_RETRIES: REQUEST_RETRIES
+}
+
+// ---------------------------------------------------------------------------
+
+function isTestInstance () {
+  return (process.env.NODE_ENV === 'test')
+}
diff --git a/server/initializers/database.js b/server/initializers/database.js
new file mode 100644
index 000000000..a917442ec
--- /dev/null
+++ b/server/initializers/database.js
@@ -0,0 +1,29 @@
+'use strict'
+
+var config = require('config')
+var mongoose = require('mongoose')
+
+var logger = require('../helpers/logger')
+
+var dbname = 'peertube' + config.get('database.suffix')
+var host = config.get('database.host')
+var port = config.get('database.port')
+
+var database = {
+  connect: connect
+}
+
+function connect () {
+  mongoose.connect('mongodb://' + host + ':' + port + '/' + dbname)
+  mongoose.connection.on('error', function () {
+    throw new Error('Mongodb connection error.')
+  })
+
+  mongoose.connection.on('open', function () {
+    logger.info('Connected to mongodb.')
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = database
diff --git a/server/lib/friends.js b/server/lib/friends.js
new file mode 100644
index 000000000..006a64404
--- /dev/null
+++ b/server/lib/friends.js
@@ -0,0 +1,228 @@
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var fs = require('fs')
+var request = require('request')
+
+var constants = require('../initializers/constants')
+var logger = require('../helpers/logger')
+var peertubeCrypto = require('../helpers/peertubeCrypto')
+var Pods = require('../models/pods')
+var poolRequests = require('../lib/poolRequests')
+var requests = require('../helpers/requests')
+var Videos = require('../models/videos')
+
+var http = config.get('webserver.https') ? 'https' : 'http'
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+
+var pods = {
+  addVideoToFriends: addVideoToFriends,
+  hasFriends: hasFriends,
+  makeFriends: makeFriends,
+  quitFriends: quitFriends,
+  removeVideoToFriends: removeVideoToFriends
+}
+
+function addVideoToFriends (video) {
+  // To avoid duplicates
+  var id = video.name + video.magnetUri
+  // ensure namePath is null
+  video.namePath = null
+  poolRequests.addRequest(id, 'add', video)
+}
+
+function hasFriends (callback) {
+  Pods.count(function (err, count) {
+    if (err) return callback(err)
+
+    var has_friends = (count !== 0)
+    callback(null, has_friends)
+  })
+}
+
+function makeFriends (callback) {
+  var pods_score = {}
+
+  logger.info('Make friends!')
+  fs.readFile(peertubeCrypto.getCertDir() + 'peertube.pub', 'utf8', function (err, cert) {
+    if (err) {
+      logger.error('Cannot read public cert.')
+      return callback(err)
+    }
+
+    var urls = config.get('network.friends')
+
+    async.each(urls, function (url, callback) {
+      computeForeignPodsList(url, pods_score, callback)
+    }, function (err) {
+      if (err) return callback(err)
+
+      logger.debug('Pods scores computed.', { pods_score: pods_score })
+      var pods_list = computeWinningPods(urls, pods_score)
+      logger.debug('Pods that we keep computed.', { pods_to_keep: pods_list })
+
+      makeRequestsToWinningPods(cert, pods_list, callback)
+    })
+  })
+}
+
+function quitFriends (callback) {
+  // Stop pool requests
+  poolRequests.deactivate()
+  // Flush pool requests
+  poolRequests.forceSend()
+
+  Pods.list(function (err, pods) {
+    if (err) return callback(err)
+
+    var request = {
+      method: 'POST',
+      path: '/api/' + constants.API_VERSION + '/pods/remove',
+      sign: true,
+      encrypt: true,
+      data: {
+        url: 'me' // Fake data
+      }
+    }
+
+    // Announce we quit them
+    requests.makeMultipleRetryRequest(request, pods, function () {
+      Pods.removeAll(function (err) {
+        poolRequests.activate()
+
+        if (err) return callback(err)
+
+        logger.info('Broke friends, so sad :(')
+
+        Videos.removeAllRemotes(function (err) {
+          if (err) return callback(err)
+
+          logger.info('Removed all remote videos.')
+          callback(null)
+        })
+      })
+    })
+  })
+}
+
+function removeVideoToFriends (video) {
+  // To avoid duplicates
+  var id = video.name + video.magnetUri
+  poolRequests.addRequest(id, 'remove', video)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = pods
+
+// ---------------------------------------------------------------------------
+
+function computeForeignPodsList (url, pods_score, callback) {
+  // Let's give 1 point to the pod we ask the friends list
+  pods_score[url] = 1
+
+  getForeignPodsList(url, function (err, foreign_pods_list) {
+    if (err) return callback(err)
+    if (foreign_pods_list.length === 0) return callback()
+
+    async.each(foreign_pods_list, function (foreign_pod, callback_each) {
+      var foreign_url = foreign_pod.url
+
+      if (pods_score[foreign_url]) pods_score[foreign_url]++
+      else pods_score[foreign_url] = 1
+
+      callback_each()
+    }, function () {
+      callback()
+    })
+  })
+}
+
+function computeWinningPods (urls, pods_score) {
+  // Build the list of pods to add
+  // Only add a pod if it exists in more than a half base pods
+  var pods_list = []
+  var base_score = urls.length / 2
+  Object.keys(pods_score).forEach(function (pod) {
+    if (pods_score[pod] > base_score) pods_list.push({ url: pod })
+  })
+
+  return pods_list
+}
+
+function getForeignPodsList (url, callback) {
+  var path = '/api/' + constants.API_VERSION + '/pods'
+
+  request.get(url + path, function (err, response, body) {
+    if (err) return callback(err)
+
+    callback(null, JSON.parse(body))
+  })
+}
+
+function makeRequestsToWinningPods (cert, pods_list, callback) {
+  // Stop pool requests
+  poolRequests.deactivate()
+  // Flush pool requests
+  poolRequests.forceSend()
+
+  // Get the list of our videos to send to our new friends
+  Videos.listOwned(function (err, videos_list) {
+    if (err) {
+      logger.error('Cannot get the list of videos we own.')
+      return callback(err)
+    }
+
+    var data = {
+      url: http + '://' + host + ':' + port,
+      publicKey: cert,
+      videos: videos_list
+    }
+
+    requests.makeMultipleRetryRequest(
+      { method: 'POST', path: '/api/' + constants.API_VERSION + '/pods/', data: data },
+
+      pods_list,
+
+      function eachRequest (err, response, body, url, pod, callback_each_request) {
+        // We add the pod if it responded correctly with its public certificate
+        if (!err && response.statusCode === 200) {
+          Pods.add({ url: pod.url, publicKey: body.cert, score: constants.FRIEND_BASE_SCORE }, function (err) {
+            if (err) {
+              logger.error('Error with adding %s pod.', pod.url, { error: err })
+              return callback_each_request()
+            }
+
+            Videos.addRemotes(body.videos, function (err) {
+              if (err) {
+                logger.error('Error with adding videos of pod.', pod.url, { error: err })
+                return callback_each_request()
+              }
+
+              logger.debug('Adding remote videos from %s.', pod.url, { videos: body.videos })
+              return callback_each_request()
+            })
+          })
+        } else {
+          logger.error('Error with adding %s pod.', pod.url, { error: err || new Error('Status not 200') })
+          return callback_each_request()
+        }
+      },
+
+      function endRequests (err) {
+        // Now we made new friends, we can re activate the pool of requests
+        poolRequests.activate()
+
+        if (err) {
+          logger.error('There was some errors when we wanted to make friends.')
+          return callback(err)
+        }
+
+        logger.debug('makeRequestsToWinningPods finished.')
+        return callback(null)
+      }
+    )
+  })
+}
diff --git a/server/lib/poolRequests.js b/server/lib/poolRequests.js
new file mode 100644
index 000000000..f786c3c7a
--- /dev/null
+++ b/server/lib/poolRequests.js
@@ -0,0 +1,221 @@
+'use strict'
+
+var async = require('async')
+var pluck = require('lodash-node/compat/collection/pluck')
+
+var constants = require('../initializers/constants')
+var logger = require('../helpers/logger')
+var Pods = require('../models/pods')
+var PoolRequests = require('../models/poolRequests')
+var requests = require('../helpers/requests')
+var Videos = require('../models/videos')
+
+var timer = null
+
+var poolRequests = {
+  activate: activate,
+  addRequest: addRequest,
+  deactivate: deactivate,
+  forceSend: forceSend
+}
+
+function activate () {
+  logger.info('Pool requests activated.')
+  timer = setInterval(makePoolRequests, constants.INTERVAL)
+}
+
+function addRequest (id, type, request) {
+  logger.debug('Add request to the pool requests.', { id: id, type: type, request: request })
+
+  PoolRequests.findById(id, function (err, entity) {
+    if (err) {
+      logger.error('Cannot find one pool request.', { error: err })
+      return // Abort
+    }
+
+    if (entity) {
+      if (entity.type === type) {
+        logger.error('Cannot insert two same requests.')
+        return // Abort
+      }
+
+      // Remove the request of the other type
+      PoolRequests.removeRequestById(id, function (err) {
+        if (err) {
+          logger.error('Cannot remove a pool request.', { error: err })
+          return // Abort
+        }
+      })
+    } else {
+      PoolRequests.create(id, type, request, function (err) {
+        if (err) logger.error('Cannot create a pool request.', { error: err })
+        return // Abort
+      })
+    }
+  })
+}
+
+function deactivate () {
+  logger.info('Pool requests deactivated.')
+  clearInterval(timer)
+}
+
+function forceSend () {
+  logger.info('Force pool requests sending.')
+  makePoolRequests()
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = poolRequests
+
+// ---------------------------------------------------------------------------
+
+function makePoolRequest (type, requests_to_make, callback) {
+  if (!callback) callback = function () {}
+
+  Pods.list(function (err, pods) {
+    if (err) return callback(err)
+
+    var params = {
+      encrypt: true,
+      sign: true,
+      method: 'POST',
+      path: null,
+      data: requests_to_make
+    }
+
+    if (type === 'add') {
+      params.path = '/api/' + constants.API_VERSION + '/remotevideos/add'
+    } else if (type === 'remove') {
+      params.path = '/api/' + constants.API_VERSION + '/remotevideos/remove'
+    } else {
+      return callback(new Error('Unkown pool request type.'))
+    }
+
+    var bad_pods = []
+    var good_pods = []
+
+    requests.makeMultipleRetryRequest(params, pods, callbackEachPodFinished, callbackAllPodsFinished)
+
+    function callbackEachPodFinished (err, response, body, url, pod, callback_each_pod_finished) {
+      if (err || (response.statusCode !== 200 && response.statusCode !== 204)) {
+        bad_pods.push(pod._id)
+        logger.error('Error sending secure request to %s pod.', url, { error: err || new Error('Status code not 20x') })
+      } else {
+        good_pods.push(pod._id)
+      }
+
+      return callback_each_pod_finished()
+    }
+
+    function callbackAllPodsFinished (err) {
+      if (err) return callback(err)
+
+      updatePodsScore(good_pods, bad_pods)
+      callback(null)
+    }
+  })
+}
+
+function makePoolRequests () {
+  logger.info('Making pool requests to friends.')
+
+  PoolRequests.list(function (err, pool_requests) {
+    if (err) {
+      logger.error('Cannot get the list of pool requests.', { err: err })
+      return // Abort
+    }
+
+    if (pool_requests.length === 0) return
+
+    var requests_to_make = {
+      add: {
+        ids: [],
+        requests: []
+      },
+      remove: {
+        ids: [],
+        requests: []
+      }
+    }
+
+    async.each(pool_requests, function (pool_request, callback_each) {
+      if (pool_request.type === 'add') {
+        requests_to_make.add.requests.push(pool_request.request)
+        requests_to_make.add.ids.push(pool_request._id)
+      } else if (pool_request.type === 'remove') {
+        requests_to_make.remove.requests.push(pool_request.request)
+        requests_to_make.remove.ids.push(pool_request._id)
+      } else {
+        logger.error('Unkown pool request type.', { request_type: pool_request.type })
+        return // abort
+      }
+
+      callback_each()
+    }, function () {
+      // Send the add requests
+      if (requests_to_make.add.requests.length !== 0) {
+        makePoolRequest('add', requests_to_make.add.requests, function (err) {
+          if (err) logger.error('Errors when sent add pool requests.', { error: err })
+
+          PoolRequests.removeRequests(requests_to_make.add.ids)
+        })
+      }
+
+      // Send the remove requests
+      if (requests_to_make.remove.requests.length !== 0) {
+        makePoolRequest('remove', requests_to_make.remove.requests, function (err) {
+          if (err) logger.error('Errors when sent remove pool requests.', { error: err })
+
+          PoolRequests.removeRequests(requests_to_make.remove.ids)
+        })
+      }
+    })
+  })
+}
+
+function removeBadPods () {
+  Pods.findBadPods(function (err, pods) {
+    if (err) {
+      logger.error('Cannot find bad pods.', { error: err })
+      return // abort
+    }
+
+    if (pods.length === 0) return
+
+    var urls = pluck(pods, 'url')
+    var ids = pluck(pods, '_id')
+
+    Videos.removeAllRemotesOf(urls, function (err, r) {
+      if (err) {
+        logger.error('Cannot remove videos from a pod that we removing.', { error: err })
+      } else {
+        var videos_removed = r.result.n
+        logger.info('Removed %d videos.', videos_removed)
+      }
+
+      Pods.removeAllByIds(ids, function (err, r) {
+        if (err) {
+          logger.error('Cannot remove bad pods.', { error: err })
+        } else {
+          var pods_removed = r.result.n
+          logger.info('Removed %d pods.', pods_removed)
+        }
+      })
+    })
+  })
+}
+
+function updatePodsScore (good_pods, bad_pods) {
+  logger.info('Updating %d good pods and %d bad pods scores.', good_pods.length, bad_pods.length)
+
+  Pods.incrementScores(good_pods, constants.PODS_SCORE.BONUS, function (err) {
+    if (err) logger.error('Cannot increment scores of good pods.')
+  })
+
+  Pods.incrementScores(bad_pods, constants.PODS_SCORE.MALUS, function (err) {
+    if (err) logger.error('Cannot increment scores of bad pods.')
+    removeBadPods()
+  })
+}
diff --git a/server/lib/videos.js b/server/lib/videos.js
new file mode 100644
index 000000000..2d7d9500d
--- /dev/null
+++ b/server/lib/videos.js
@@ -0,0 +1,50 @@
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var path = require('path')
+var webtorrent = require('../lib/webtorrent')
+
+var logger = require('../helpers/logger')
+var Videos = require('../models/videos')
+
+var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
+
+var videos = {
+  seed: seed,
+  seedAllExisting: seedAllExisting
+}
+
+function seed (path, callback) {
+  logger.info('Seeding %s...', path)
+
+  webtorrent.seed(path, function (torrent) {
+    logger.info('%s seeded (%s).', path, torrent.magnetURI)
+
+    return callback(null, torrent)
+  })
+}
+
+function seedAllExisting (callback) {
+  Videos.listOwned(function (err, videos_list) {
+    if (err) {
+      logger.error('Cannot get list of the videos to seed.')
+      return callback(err)
+    }
+
+    async.each(videos_list, function (video, each_callback) {
+      seed(uploadDir + video.namePath, function (err) {
+        if (err) {
+          logger.error('Cannot seed this video.')
+          return callback(err)
+        }
+
+        each_callback(null)
+      })
+    }, callback)
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = videos
diff --git a/server/lib/webtorrent.js b/server/lib/webtorrent.js
new file mode 100644
index 000000000..cb641fead
--- /dev/null
+++ b/server/lib/webtorrent.js
@@ -0,0 +1,157 @@
+'use strict'
+
+var config = require('config')
+var ipc = require('node-ipc')
+var pathUtils = require('path')
+var spawn = require('electron-spawn')
+
+var logger = require('../helpers/logger')
+
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+var nodeKey = 'webtorrentnode' + port
+var processKey = 'webtorrentprocess' + port
+ipc.config.silent = true
+ipc.config.id = nodeKey
+
+var webtorrent = {
+  add: add,
+  app: null, // Pid of the app
+  create: create,
+  remove: remove,
+  seed: seed,
+  silent: false // Useful for beautiful tests
+}
+
+function create (options, callback) {
+  if (typeof options === 'function') {
+    callback = options
+    options = {}
+  }
+
+  // Override options
+  if (options.host) host = options.host
+  if (options.port) {
+    port = options.port
+    nodeKey = 'webtorrentnode' + port
+    processKey = 'webtorrentprocess' + port
+    ipc.config.id = nodeKey
+  }
+
+  ipc.serve(function () {
+    if (!webtorrent.silent) logger.info('IPC server ready.')
+
+    // Run a timeout of 30s after which we exit the process
+    var timeout_webtorrent_process = setTimeout(function () {
+      throw new Error('Timeout : cannot run the webtorrent process. Please ensure you have electron-prebuilt npm package installed with xvfb-run.')
+    }, 30000)
+
+    ipc.server.on(processKey + '.ready', function () {
+      if (!webtorrent.silent) logger.info('Webtorrent process ready.')
+      clearTimeout(timeout_webtorrent_process)
+      callback()
+    })
+
+    ipc.server.on(processKey + '.exception', function (data) {
+      throw new Error('Received exception error from webtorrent process.' + data.exception)
+    })
+
+    var webtorrent_process = spawn(pathUtils.join(__dirname, 'webtorrentProcess.js'), host, port, { detached: true })
+    webtorrent_process.stderr.on('data', function (data) {
+      // logger.debug('Webtorrent process stderr: ', data.toString())
+    })
+
+    webtorrent_process.stdout.on('data', function (data) {
+      // logger.debug('Webtorrent process:', data.toString())
+    })
+
+    webtorrent.app = webtorrent_process
+  })
+
+  ipc.server.start()
+}
+
+function seed (path, callback) {
+  var extension = pathUtils.extname(path)
+  var basename = pathUtils.basename(path, extension)
+  var data = {
+    _id: basename,
+    args: {
+      path: path
+    }
+  }
+
+  if (!webtorrent.silent) logger.debug('Node wants to seed %s.', data._id)
+
+  // Finish signal
+  var event_key = nodeKey + '.seedDone.' + data._id
+  ipc.server.on(event_key, function listener (received) {
+    if (!webtorrent.silent) logger.debug('Process seeded torrent %s.', received.magnetUri)
+
+    // This is a fake object, we just use the magnetUri in this project
+    var torrent = {
+      magnetURI: received.magnetUri
+    }
+
+    ipc.server.off(event_key)
+    callback(torrent)
+  })
+
+  ipc.server.broadcast(processKey + '.seed', data)
+}
+
+function add (magnetUri, callback) {
+  var data = {
+    _id: magnetUri,
+    args: {
+      magnetUri: magnetUri
+    }
+  }
+
+  if (!webtorrent.silent) logger.debug('Node wants to add ' + data._id)
+
+  // Finish signal
+  var event_key = nodeKey + '.addDone.' + data._id
+  ipc.server.on(event_key, function (received) {
+    if (!webtorrent.silent) logger.debug('Process added torrent.')
+
+    // This is a fake object, we just use the magnetUri in this project
+    var torrent = {
+      files: received.files
+    }
+
+    ipc.server.off(event_key)
+    callback(torrent)
+  })
+
+  ipc.server.broadcast(processKey + '.add', data)
+}
+
+function remove (magnetUri, callback) {
+  var data = {
+    _id: magnetUri,
+    args: {
+      magnetUri: magnetUri
+    }
+  }
+
+  if (!webtorrent.silent) logger.debug('Node wants to stop seeding %s.', data._id)
+
+  // Finish signal
+  var event_key = nodeKey + '.removeDone.' + data._id
+  ipc.server.on(event_key, function (received) {
+    if (!webtorrent.silent) logger.debug('Process removed torrent %s.', data._id)
+
+    var err = null
+    if (received.err) err = received.err
+
+    ipc.server.off(event_key)
+    callback(err)
+  })
+
+  ipc.server.broadcast(processKey + '.remove', data)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = webtorrent
diff --git a/server/lib/webtorrentProcess.js b/server/lib/webtorrentProcess.js
new file mode 100644
index 000000000..7da52523a
--- /dev/null
+++ b/server/lib/webtorrentProcess.js
@@ -0,0 +1,92 @@
+'use strict'
+
+var WebTorrent = require('webtorrent')
+var ipc = require('node-ipc')
+
+function webtorrent (args) {
+  if (args.length !== 3) {
+    throw new Error('Wrong arguments number: ' + args.length + '/3')
+  }
+
+  var host = args[1]
+  var port = args[2]
+  var nodeKey = 'webtorrentnode' + port
+  var processKey = 'webtorrentprocess' + port
+
+  ipc.config.silent = true
+  ipc.config.id = processKey
+
+  if (host === 'client' && port === '1') global.WEBTORRENT_ANNOUNCE = []
+  else global.WEBTORRENT_ANNOUNCE = 'ws://' + host + ':' + port + '/tracker/socket'
+  var wt = new WebTorrent({ dht: false })
+
+  function seed (data) {
+    var args = data.args
+    var path = args.path
+    var _id = data._id
+
+    wt.seed(path, { announceList: '' }, function (torrent) {
+      var to_send = {
+        magnetUri: torrent.magnetURI
+      }
+
+      ipc.of[nodeKey].emit(nodeKey + '.seedDone.' + _id, to_send)
+    })
+  }
+
+  function add (data) {
+    var args = data.args
+    var magnetUri = args.magnetUri
+    var _id = data._id
+
+    wt.add(magnetUri, function (torrent) {
+      var to_send = {
+        files: []
+      }
+
+      torrent.files.forEach(function (file) {
+        to_send.files.push({ path: file.path })
+      })
+
+      ipc.of[nodeKey].emit(nodeKey + '.addDone.' + _id, to_send)
+    })
+  }
+
+  function remove (data) {
+    var args = data.args
+    var magnetUri = args.magnetUri
+    var _id = data._id
+
+    try {
+      wt.remove(magnetUri, callback)
+    } catch (err) {
+      console.log('Cannot remove the torrent from WebTorrent.')
+      return callback(null)
+    }
+
+    function callback () {
+      var to_send = {}
+      ipc.of[nodeKey].emit(nodeKey + '.removeDone.' + _id, to_send)
+    }
+  }
+
+  console.log('Configuration: ' + host + ':' + port)
+  console.log('Connecting to IPC...')
+
+  ipc.connectTo(nodeKey, function () {
+    ipc.of[nodeKey].on(processKey + '.seed', seed)
+    ipc.of[nodeKey].on(processKey + '.add', add)
+    ipc.of[nodeKey].on(processKey + '.remove', remove)
+
+    ipc.of[nodeKey].emit(processKey + '.ready')
+    console.log('Ready.')
+  })
+
+  process.on('uncaughtException', function (e) {
+    ipc.of[nodeKey].emit(processKey + '.exception', { exception: e })
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = webtorrent
diff --git a/server/middlewares/cache.js b/server/middlewares/cache.js
new file mode 100644
index 000000000..0d3da0075
--- /dev/null
+++ b/server/middlewares/cache.js
@@ -0,0 +1,23 @@
+'use strict'
+
+var cacheMiddleware = {
+  cache: cache
+}
+
+function cache (cache) {
+  return function (req, res, next) {
+    // If we want explicitly a cache
+    // Or if we don't specify if we want a cache or no and we are in production
+    if (cache === true || (cache !== false && process.env.NODE_ENV === 'production')) {
+      res.setHeader('Cache-Control', 'public')
+    } else {
+      res.setHeader('Cache-Control', 'no-cache, no-store, max-age=0, must-revalidate')
+    }
+
+    next()
+  }
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = cacheMiddleware
diff --git a/server/middlewares/index.js b/server/middlewares/index.js
new file mode 100644
index 000000000..c85899b0c
--- /dev/null
+++ b/server/middlewares/index.js
@@ -0,0 +1,15 @@
+'use strict'
+
+var cacheMiddleware = require('./cache')
+var reqValidatorsMiddleware = require('./reqValidators')
+var secureMiddleware = require('./secure')
+
+var middlewares = {
+  cache: cacheMiddleware,
+  reqValidators: reqValidatorsMiddleware,
+  secure: secureMiddleware
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = middlewares
diff --git a/server/middlewares/reqValidators/index.js b/server/middlewares/reqValidators/index.js
new file mode 100644
index 000000000..345dbd0e2
--- /dev/null
+++ b/server/middlewares/reqValidators/index.js
@@ -0,0 +1,15 @@
+'use strict'
+
+var podsReqValidators = require('./pods')
+var remoteReqValidators = require('./remote')
+var videosReqValidators = require('./videos')
+
+var reqValidators = {
+  pods: podsReqValidators,
+  remote: remoteReqValidators,
+  videos: videosReqValidators
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidators
diff --git a/server/middlewares/reqValidators/pods.js b/server/middlewares/reqValidators/pods.js
new file mode 100644
index 000000000..ef09d51cf
--- /dev/null
+++ b/server/middlewares/reqValidators/pods.js
@@ -0,0 +1,39 @@
+'use strict'
+
+var checkErrors = require('./utils').checkErrors
+var friends = require('../../lib/friends')
+var logger = require('../../helpers/logger')
+
+var reqValidatorsPod = {
+  makeFriends: makeFriends,
+  podsAdd: podsAdd
+}
+
+function makeFriends (req, res, next) {
+  friends.hasFriends(function (err, has_friends) {
+    if (err) {
+      logger.error('Cannot know if we have friends.', { error: err })
+      res.sendStatus(500)
+    }
+
+    if (has_friends === true) {
+      // We need to quit our friends before make new ones
+      res.sendStatus(409)
+    } else {
+      return next()
+    }
+  })
+}
+
+function podsAdd (req, res, next) {
+  req.checkBody('data.url', 'Should have an url').notEmpty().isURL({ require_protocol: true })
+  req.checkBody('data.publicKey', 'Should have a public key').notEmpty()
+
+  logger.debug('Checking podsAdd parameters', { parameters: req.body })
+
+  checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsPod
diff --git a/server/middlewares/reqValidators/remote.js b/server/middlewares/reqValidators/remote.js
new file mode 100644
index 000000000..88de16b49
--- /dev/null
+++ b/server/middlewares/reqValidators/remote.js
@@ -0,0 +1,43 @@
+'use strict'
+
+var checkErrors = require('./utils').checkErrors
+var logger = require('../../helpers/logger')
+
+var reqValidatorsRemote = {
+  remoteVideosAdd: remoteVideosAdd,
+  remoteVideosRemove: remoteVideosRemove,
+  secureRequest: secureRequest
+}
+
+function remoteVideosAdd (req, res, next) {
+  req.checkBody('data').isArray()
+  req.checkBody('data').eachIsRemoteVideosAddValid()
+
+  logger.debug('Checking remoteVideosAdd parameters', { parameters: req.body })
+
+  checkErrors(req, res, next)
+}
+
+function remoteVideosRemove (req, res, next) {
+  req.checkBody('data').isArray()
+  req.checkBody('data').eachIsRemoteVideosRemoveValid()
+
+  logger.debug('Checking remoteVideosRemove parameters', { parameters: req.body })
+
+  checkErrors(req, res, next)
+}
+
+function secureRequest (req, res, next) {
+  req.checkBody('signature.url', 'Should have a signature url').isURL()
+  req.checkBody('signature.signature', 'Should have a signature').notEmpty()
+  req.checkBody('key', 'Should have a key').notEmpty()
+  req.checkBody('data', 'Should have data').notEmpty()
+
+  logger.debug('Checking secureRequest parameters', { parameters: { data: req.body.data, keyLength: req.body.key.length } })
+
+  checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsRemote
diff --git a/server/middlewares/reqValidators/utils.js b/server/middlewares/reqValidators/utils.js
new file mode 100644
index 000000000..46c982571
--- /dev/null
+++ b/server/middlewares/reqValidators/utils.js
@@ -0,0 +1,25 @@
+'use strict'
+
+var util = require('util')
+
+var logger = require('../../helpers/logger')
+
+var reqValidatorsUtils = {
+  checkErrors: checkErrors
+}
+
+function checkErrors (req, res, next, status_code) {
+  if (status_code === undefined) status_code = 400
+  var errors = req.validationErrors()
+
+  if (errors) {
+    logger.warn('Incorrect request parameters', { path: req.originalUrl, err: errors })
+    return res.status(status_code).send('There have been validation errors: ' + util.inspect(errors))
+  }
+
+  return next()
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsUtils
diff --git a/server/middlewares/reqValidators/videos.js b/server/middlewares/reqValidators/videos.js
new file mode 100644
index 000000000..4e5f4391f
--- /dev/null
+++ b/server/middlewares/reqValidators/videos.js
@@ -0,0 +1,74 @@
+'use strict'
+
+var checkErrors = require('./utils').checkErrors
+var logger = require('../../helpers/logger')
+var Videos = require('../../models/videos')
+
+var reqValidatorsVideos = {
+  videosAdd: videosAdd,
+  videosGet: videosGet,
+  videosRemove: videosRemove,
+  videosSearch: videosSearch
+}
+
+function videosAdd (req, res, next) {
+  req.checkFiles('input_video[0].originalname', 'Should have an input video').notEmpty()
+  req.checkFiles('input_video[0].mimetype', 'Should have a correct mime type').matches(/video\/(webm)|(mp4)|(ogg)/i)
+  req.checkBody('name', 'Should have a name').isLength(1, 50)
+  req.checkBody('description', 'Should have a description').isLength(1, 250)
+
+  logger.debug('Checking videosAdd parameters', { parameters: req.body, files: req.files })
+
+  checkErrors(req, res, next)
+}
+
+function videosGet (req, res, next) {
+  req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+
+  logger.debug('Checking videosGet parameters', { parameters: req.params })
+
+  checkErrors(req, res, function () {
+    Videos.getVideoState(req.params.id, function (err, state) {
+      if (err) {
+        logger.error('Error in videosGet request validator.', { error: err })
+        res.sendStatus(500)
+      }
+
+      if (state.exist === false) return res.status(404).send('Video not found')
+
+      next()
+    })
+  })
+}
+
+function videosRemove (req, res, next) {
+  req.checkParams('id', 'Should have a valid id').notEmpty().isMongoId()
+
+  logger.debug('Checking videosRemove parameters', { parameters: req.params })
+
+  checkErrors(req, res, function () {
+    Videos.getVideoState(req.params.id, function (err, state) {
+      if (err) {
+        logger.error('Error in videosRemove request validator.', { error: err })
+        res.sendStatus(500)
+      }
+
+      if (state.exist === false) return res.status(404).send('Video not found')
+      else if (state.owned === false) return res.status(403).send('Cannot remove video of another pod')
+
+      next()
+    })
+  })
+}
+
+function videosSearch (req, res, next) {
+  req.checkParams('name', 'Should have a name').notEmpty()
+
+  logger.debug('Checking videosSearch parameters', { parameters: req.params })
+
+  checkErrors(req, res, next)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = reqValidatorsVideos
diff --git a/server/middlewares/secure.js b/server/middlewares/secure.js
new file mode 100644
index 000000000..bfd28316a
--- /dev/null
+++ b/server/middlewares/secure.js
@@ -0,0 +1,49 @@
+'use strict'
+
+var logger = require('../helpers/logger')
+var peertubeCrypto = require('../helpers/peertubeCrypto')
+var Pods = require('../models/pods')
+
+var secureMiddleware = {
+  decryptBody: decryptBody
+}
+
+function decryptBody (req, res, next) {
+  var url = req.body.signature.url
+  Pods.findByUrl(url, function (err, pod) {
+    if (err) {
+      logger.error('Cannot get signed url in decryptBody.', { error: err })
+      return res.sendStatus(500)
+    }
+
+    if (pod === null) {
+      logger.error('Unknown pod %s.', url)
+      return res.sendStatus(403)
+    }
+
+    logger.debug('Decrypting body from %s.', url)
+
+    var signature_ok = peertubeCrypto.checkSignature(pod.publicKey, url, req.body.signature.signature)
+
+    if (signature_ok === true) {
+      peertubeCrypto.decrypt(req.body.key, req.body.data, function (err, decrypted) {
+        if (err) {
+          logger.error('Cannot decrypt data.', { error: err })
+          return res.sendStatus(500)
+        }
+
+        req.body.data = JSON.parse(decrypted)
+        delete req.body.key
+
+        next()
+      })
+    } else {
+      logger.error('Signature is not okay in decryptBody for %s.', req.body.signature.url)
+      return res.sendStatus(403)
+    }
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = secureMiddleware
diff --git a/server/models/pods.js b/server/models/pods.js
new file mode 100644
index 000000000..57ed20292
--- /dev/null
+++ b/server/models/pods.js
@@ -0,0 +1,88 @@
+'use strict'
+
+var mongoose = require('mongoose')
+
+var constants = require('../initializers/constants')
+var logger = require('../helpers/logger')
+
+// ---------------------------------------------------------------------------
+
+var podsSchema = mongoose.Schema({
+  url: String,
+  publicKey: String,
+  score: { type: Number, max: constants.FRIEND_BASE_SCORE }
+})
+var PodsDB = mongoose.model('pods', podsSchema)
+
+// ---------------------------------------------------------------------------
+
+var Pods = {
+  add: add,
+  count: count,
+  findByUrl: findByUrl,
+  findBadPods: findBadPods,
+  incrementScores: incrementScores,
+  list: list,
+  remove: remove,
+  removeAll: removeAll,
+  removeAllByIds: removeAllByIds
+}
+
+// TODO: check if the pod is not already a friend
+function add (data, callback) {
+  if (!callback) callback = function () {}
+  var params = {
+    url: data.url,
+    publicKey: data.publicKey,
+    score: constants.FRIEND_BASE_SCORE
+  }
+
+  PodsDB.create(params, callback)
+}
+
+function count (callback) {
+  return PodsDB.count(callback)
+}
+
+function findBadPods (callback) {
+  PodsDB.find({ score: 0 }, callback)
+}
+
+function findByUrl (url, callback) {
+  PodsDB.findOne({ url: url }, callback)
+}
+
+function incrementScores (ids, value, callback) {
+  if (!callback) callback = function () {}
+  PodsDB.update({ _id: { $in: ids } }, { $inc: { score: value } }, { multi: true }, callback)
+}
+
+function list (callback) {
+  PodsDB.find(function (err, pods_list) {
+    if (err) {
+      logger.error('Cannot get the list of the pods.')
+      return callback(err)
+    }
+
+    return callback(null, pods_list)
+  })
+}
+
+function remove (url, callback) {
+  if (!callback) callback = function () {}
+  PodsDB.remove({ url: url }, callback)
+}
+
+function removeAll (callback) {
+  if (!callback) callback = function () {}
+  PodsDB.remove(callback)
+}
+
+function removeAllByIds (ids, callback) {
+  if (!callback) callback = function () {}
+  PodsDB.remove({ _id: { $in: ids } }, callback)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = Pods
diff --git a/server/models/poolRequests.js b/server/models/poolRequests.js
new file mode 100644
index 000000000..970315597
--- /dev/null
+++ b/server/models/poolRequests.js
@@ -0,0 +1,55 @@
+'use strict'
+
+var mongoose = require('mongoose')
+
+var logger = require('../helpers/logger')
+
+// ---------------------------------------------------------------------------
+
+var poolRequestsSchema = mongoose.Schema({
+  type: String,
+  id: String, // Special id to find duplicates (video created we want to remove...)
+  request: mongoose.Schema.Types.Mixed
+})
+var PoolRequestsDB = mongoose.model('poolRequests', poolRequestsSchema)
+
+// ---------------------------------------------------------------------------
+
+var PoolRequests = {
+  create: create,
+  findById: findById,
+  list: list,
+  removeRequestById: removeRequestById,
+  removeRequests: removeRequests
+}
+
+function create (id, type, request, callback) {
+  PoolRequestsDB.create({ id: id, type: type, request: request }, callback)
+}
+
+function findById (id, callback) {
+  PoolRequestsDB.findOne({ id: id }, callback)
+}
+
+function list (callback) {
+  PoolRequestsDB.find({}, { _id: 1, type: 1, request: 1 }, callback)
+}
+
+function removeRequestById (id, callback) {
+  PoolRequestsDB.remove({ id: id }, callback)
+}
+
+function removeRequests (ids) {
+  PoolRequestsDB.remove({ _id: { $in: ids } }, function (err) {
+    if (err) {
+      logger.error('Cannot remove requests from the pool requests database.', { error: err })
+      return // Abort
+    }
+
+    logger.info('Pool requests flushed.')
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = PoolRequests
diff --git a/server/models/videos.js b/server/models/videos.js
new file mode 100644
index 000000000..5e2eeae07
--- /dev/null
+++ b/server/models/videos.js
@@ -0,0 +1,234 @@
+'use strict'
+
+var async = require('async')
+var config = require('config')
+var dz = require('dezalgo')
+var fs = require('fs')
+var mongoose = require('mongoose')
+var path = require('path')
+
+var logger = require('../helpers/logger')
+
+var http = config.get('webserver.https') === true ? 'https' : 'http'
+var host = config.get('webserver.host')
+var port = config.get('webserver.port')
+var uploadDir = path.join(__dirname, '..', config.get('storage.uploads'))
+
+// ---------------------------------------------------------------------------
+
+var videosSchema = mongoose.Schema({
+  name: String,
+  namePath: String,
+  description: String,
+  magnetUri: String,
+  podUrl: String
+})
+var VideosDB = mongoose.model('videos', videosSchema)
+
+// ---------------------------------------------------------------------------
+
+var Videos = {
+  add: add,
+  addRemotes: addRemotes,
+  get: get,
+  getVideoState: getVideoState,
+  isOwned: isOwned,
+  list: list,
+  listOwned: listOwned,
+  removeOwned: removeOwned,
+  removeAllRemotes: removeAllRemotes,
+  removeAllRemotesOf: removeAllRemotesOf,
+  removeRemotesOfByMagnetUris: removeRemotesOfByMagnetUris,
+  search: search
+}
+
+function add (video, callback) {
+  logger.info('Adding %s video to database.', video.name)
+
+  var params = video
+  params.podUrl = http + '://' + host + ':' + port
+
+  VideosDB.create(params, function (err, video) {
+    if (err) {
+      logger.error('Cannot insert this video into database.')
+      return callback(err)
+    }
+
+    callback(null)
+  })
+}
+
+// TODO: avoid doublons
+function addRemotes (videos, callback) {
+  if (!callback) callback = function () {}
+
+  var to_add = []
+
+  async.each(videos, function (video, callback_each) {
+    callback_each = dz(callback_each)
+    logger.debug('Add remote video from pod: %s', video.podUrl)
+
+    var params = {
+      name: video.name,
+      namePath: null,
+      description: video.description,
+      magnetUri: video.magnetUri,
+      podUrl: video.podUrl
+    }
+
+    to_add.push(params)
+
+    callback_each()
+  }, function () {
+    VideosDB.create(to_add, function (err, videos) {
+      if (err) {
+        logger.error('Cannot insert this remote video.')
+        return callback(err)
+      }
+
+      return callback(null, videos)
+    })
+  })
+}
+
+function get (id, callback) {
+  VideosDB.findById(id, function (err, video) {
+    if (err) {
+      logger.error('Cannot get this video.')
+      return callback(err)
+    }
+
+    return callback(null, video)
+  })
+}
+
+function getVideoState (id, callback) {
+  get(id, function (err, video) {
+    if (err) return callback(err)
+
+    var exist = (video !== null)
+    var owned = false
+    if (exist === true) {
+      owned = (video.namePath !== null)
+    }
+
+    return callback(null, { exist: exist, owned: owned })
+  })
+}
+
+function isOwned (id, callback) {
+  VideosDB.findById(id, function (err, video) {
+    if (err || !video) {
+      if (!err) err = new Error('Cannot find this video.')
+      logger.error('Cannot find this video.')
+      return callback(err)
+    }
+
+    if (video.namePath === null) {
+      var error_string = 'Cannot remove the video of another pod.'
+      logger.error(error_string)
+      return callback(new Error(error_string), false, video)
+    }
+
+    callback(null, true, video)
+  })
+}
+
+function list (callback) {
+  VideosDB.find(function (err, videos_list) {
+    if (err) {
+      logger.error('Cannot get the list of the videos.')
+      return callback(err)
+    }
+
+    return callback(null, videos_list)
+  })
+}
+
+function listOwned (callback) {
+  // If namePath is not null this is *our* video
+  VideosDB.find({ namePath: { $ne: null } }, function (err, videos_list) {
+    if (err) {
+      logger.error('Cannot get the list of owned videos.')
+      return callback(err)
+    }
+
+    return callback(null, videos_list)
+  })
+}
+
+function removeOwned (id, callback) {
+  VideosDB.findByIdAndRemove(id, function (err, video) {
+    if (err) {
+      logger.error('Cannot remove the torrent.')
+      return callback(err)
+    }
+
+    fs.unlink(uploadDir + video.namePath, function (err) {
+      if (err) {
+        logger.error('Cannot remove this video file.')
+        return callback(err)
+      }
+
+      callback(null)
+    })
+  })
+}
+
+function removeAllRemotes (callback) {
+  VideosDB.remove({ namePath: null }, callback)
+}
+
+function removeAllRemotesOf (fromUrl, callback) {
+  VideosDB.remove({ podUrl: fromUrl }, callback)
+}
+
+// Use the magnet Uri because the _id field is not the same on different servers
+function removeRemotesOfByMagnetUris (fromUrl, magnetUris, callback) {
+  if (callback === undefined) callback = function () {}
+
+  VideosDB.find({ magnetUri: { $in: magnetUris } }, function (err, videos) {
+    if (err || !videos) {
+      logger.error('Cannot find the torrent URI of these remote videos.')
+      return callback(err)
+    }
+
+    var to_remove = []
+    async.each(videos, function (video, callback_async) {
+      callback_async = dz(callback_async)
+
+      if (video.podUrl !== fromUrl) {
+        logger.error('The pod %s has not the rights on the video of %s.', fromUrl, video.podUrl)
+      } else {
+        to_remove.push(video._id)
+      }
+
+      callback_async()
+    }, function () {
+      VideosDB.remove({ _id: { $in: to_remove } }, function (err) {
+        if (err) {
+          logger.error('Cannot remove the remote videos.')
+          return callback(err)
+        }
+
+        logger.info('Removed remote videos from %s.', fromUrl)
+        callback(null)
+      })
+    })
+  })
+}
+
+function search (name, callback) {
+  VideosDB.find({ name: new RegExp(name) }, function (err, videos) {
+    if (err) {
+      logger.error('Cannot search the videos.')
+      return callback(err)
+    }
+
+    return callback(null, videos)
+  })
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = Videos
diff --git a/server/tests/api/checkParams.js b/server/tests/api/checkParams.js
new file mode 100644
index 000000000..1c1ec71b3
--- /dev/null
+++ b/server/tests/api/checkParams.js
@@ -0,0 +1,300 @@
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var pathUtils = require('path')
+var request = require('supertest')
+
+var utils = require('./utils')
+
+describe('Test parameters validator', function () {
+  var app = null
+  var url = ''
+
+  function makePostRequest (path, fields, attach, done, fail) {
+    var status_code = 400
+    if (fail !== undefined && fail === false) status_code = 200
+
+    var req = request(url)
+      .post(path)
+      .set('Accept', 'application/json')
+
+    Object.keys(fields).forEach(function (field) {
+      var value = fields[field]
+      req.field(field, value)
+    })
+
+    req.expect(status_code, done)
+  }
+
+  function makePostBodyRequest (path, fields, done, fail) {
+    var status_code = 400
+    if (fail !== undefined && fail === false) status_code = 200
+
+    request(url)
+      .post(path)
+      .set('Accept', 'application/json')
+      .send(fields)
+      .expect(status_code, done)
+  }
+
+  // ---------------------------------------------------------------
+
+  before(function (done) {
+    this.timeout(20000)
+
+    async.series([
+      function (next) {
+        utils.flushTests(next)
+      },
+      function (next) {
+        utils.runServer(1, function (app1, url1) {
+          app = app1
+          url = url1
+          next()
+        })
+      }
+    ], done)
+  })
+
+  describe('Of the pods API', function () {
+    var path = '/api/v1/pods/'
+
+    describe('When adding a pod', function () {
+      it('Should fail with nothing', function (done) {
+        var data = {}
+        makePostBodyRequest(path, data, done)
+      })
+
+      it('Should fail without public key', function (done) {
+        var data = {
+          data: {
+            url: 'http://coucou.com'
+          }
+        }
+        makePostBodyRequest(path, data, done)
+      })
+
+      it('Should fail without an url', function (done) {
+        var data = {
+          data: {
+            publicKey: 'mysuperpublickey'
+          }
+        }
+        makePostBodyRequest(path, data, done)
+      })
+
+      it('Should fail with an incorrect url', function (done) {
+        var data = {
+          data: {
+            url: 'coucou.com',
+            publicKey: 'mysuperpublickey'
+          }
+        }
+        makePostBodyRequest(path, data, function () {
+          data.data.url = 'http://coucou'
+          makePostBodyRequest(path, data, function () {
+            data.data.url = 'coucou'
+            makePostBodyRequest(path, data, done)
+          })
+        })
+      })
+
+      it('Should succeed with the correct parameters', function (done) {
+        var data = {
+          data: {
+            url: 'http://coucou.com',
+            publicKey: 'mysuperpublickey'
+          }
+        }
+        makePostBodyRequest(path, data, done, false)
+      })
+    })
+  })
+
+  describe('Of the videos API', function () {
+    var path = '/api/v1/videos/'
+
+    describe('When searching a video', function () {
+      it('Should fail with nothing', function (done) {
+        request(url)
+          .get(pathUtils.join(path, 'search'))
+          .set('Accept', 'application/json')
+          .expect(400, done)
+      })
+    })
+
+    describe('When adding a video', function () {
+      it('Should fail with nothing', function (done) {
+        var data = {}
+        var attach = {}
+        makePostRequest(path, data, attach, done)
+      })
+
+      it('Should fail without name', function (done) {
+        var data = {
+          description: 'my super description'
+        }
+        var attach = {
+          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+        }
+        makePostRequest(path, data, attach, done)
+      })
+
+      it('Should fail with a long name', function (done) {
+        var data = {
+          name: 'My very very very very very very very very very very very very very very very very long name',
+          description: 'my super description'
+        }
+        var attach = {
+          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+        }
+        makePostRequest(path, data, attach, done)
+      })
+
+      it('Should fail without description', function (done) {
+        var data = {
+          name: 'my super name'
+        }
+        var attach = {
+          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+        }
+        makePostRequest(path, data, attach, done)
+      })
+
+      it('Should fail with a long description', function (done) {
+        var data = {
+          name: 'my super name',
+          description: 'my super description which is very very very very very very very very very very very very very very' +
+                       'very very very very very very very very very very very very very very very very very very very very very' +
+                       'very very very very very very very very very very very very very very very long'
+        }
+        var attach = {
+          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+        }
+        makePostRequest(path, data, attach, done)
+      })
+
+      it('Should fail without an input file', function (done) {
+        var data = {
+          name: 'my super name',
+          description: 'my super description'
+        }
+        var attach = {}
+        makePostRequest(path, data, attach, done)
+      })
+
+      it('Should fail without an incorrect input file', function (done) {
+        var data = {
+          name: 'my super name',
+          description: 'my super description'
+        }
+        var attach = {
+          'input_video': pathUtils.join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
+        }
+        makePostRequest(path, data, attach, done)
+      })
+
+      it('Should succeed with the correct parameters', function (done) {
+        var data = {
+          name: 'my super name',
+          description: 'my super description'
+        }
+        var attach = {
+          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
+        }
+        makePostRequest(path, data, attach, function () {
+          attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
+          makePostRequest(path, data, attach, function () {
+            attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
+            makePostRequest(path, data, attach, done, true)
+          }, true)
+        }, true)
+      })
+    })
+
+    describe('When getting a video', function () {
+      it('Should return the list of the videos with nothing', function (done) {
+        request(url)
+          .get(path)
+          .set('Accept', 'application/json')
+          .expect(200)
+          .expect('Content-Type', /json/)
+          .end(function (err, res) {
+            if (err) throw err
+
+            expect(res.body).to.be.an('array')
+            expect(res.body.length).to.equal(0)
+
+            done()
+          })
+      })
+
+      it('Should fail without a mongodb id', function (done) {
+        request(url)
+          .get(path + 'coucou')
+          .set('Accept', 'application/json')
+          .expect(400, done)
+      })
+
+      it('Should return 404 with an incorrect video', function (done) {
+        request(url)
+          .get(path + '123456789012345678901234')
+          .set('Accept', 'application/json')
+          .expect(404, done)
+      })
+
+      it('Should succeed with the correct parameters')
+    })
+
+    describe('When removing a video', function () {
+      it('Should have 404 with nothing', function (done) {
+        request(url)
+        .delete(path)
+        .expect(404, done)
+      })
+
+      it('Should fail without a mongodb id', function (done) {
+        request(url)
+          .delete(path + 'hello')
+          .expect(400, done)
+      })
+
+      it('Should fail with a video which does not exist', function (done) {
+        request(url)
+          .delete(path + '123456789012345678901234')
+          .expect(404, done)
+      })
+
+      it('Should fail with a video of another pod')
+
+      it('Should succeed with the correct parameters')
+    })
+  })
+
+  describe('Of the remote videos API', function () {
+    describe('When making a secure request', function () {
+      it('Should check a secure request')
+    })
+
+    describe('When adding a video', function () {
+      it('Should check when adding a video')
+    })
+
+    describe('When removing a video', function () {
+      it('Should check when removing a video')
+    })
+  })
+
+  after(function (done) {
+    process.kill(-app.pid)
+
+    // Keep the logs if the test failed
+    if (this.ok) {
+      utils.flushTests(done)
+    } else {
+      done()
+    }
+  })
+})
diff --git a/server/tests/api/fixtures/video_short.mp4 b/server/tests/api/fixtures/video_short.mp4
new file mode 100644
index 000000000..35678362b
Binary files /dev/null and b/server/tests/api/fixtures/video_short.mp4 differ
diff --git a/server/tests/api/fixtures/video_short.ogv b/server/tests/api/fixtures/video_short.ogv
new file mode 100644
index 000000000..9e253da82
Binary files /dev/null and b/server/tests/api/fixtures/video_short.ogv differ
diff --git a/server/tests/api/fixtures/video_short.webm b/server/tests/api/fixtures/video_short.webm
new file mode 100644
index 000000000..bf4b0ab6c
Binary files /dev/null and b/server/tests/api/fixtures/video_short.webm differ
diff --git a/server/tests/api/fixtures/video_short1.webm b/server/tests/api/fixtures/video_short1.webm
new file mode 100644
index 000000000..70ac0c644
Binary files /dev/null and b/server/tests/api/fixtures/video_short1.webm differ
diff --git a/server/tests/api/fixtures/video_short2.webm b/server/tests/api/fixtures/video_short2.webm
new file mode 100644
index 000000000..13d72dff7
Binary files /dev/null and b/server/tests/api/fixtures/video_short2.webm differ
diff --git a/server/tests/api/fixtures/video_short3.webm b/server/tests/api/fixtures/video_short3.webm
new file mode 100644
index 000000000..cde5dcd58
Binary files /dev/null and b/server/tests/api/fixtures/video_short3.webm differ
diff --git a/server/tests/api/fixtures/video_short_fake.webm b/server/tests/api/fixtures/video_short_fake.webm
new file mode 100644
index 000000000..d85290ae5
--- /dev/null
+++ b/server/tests/api/fixtures/video_short_fake.webm
@@ -0,0 +1 @@
+this is a fake video mouahahah
diff --git a/server/tests/api/friendsAdvanced.js b/server/tests/api/friendsAdvanced.js
new file mode 100644
index 000000000..9838d890f
--- /dev/null
+++ b/server/tests/api/friendsAdvanced.js
@@ -0,0 +1,250 @@
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+
+var utils = require('./utils')
+
+describe('Test advanced friends', function () {
+  var apps = []
+  var urls = []
+
+  function makeFriends (pod_number, callback) {
+    return utils.makeFriends(urls[pod_number - 1], callback)
+  }
+
+  function quitFriends (pod_number, callback) {
+    return utils.quitFriends(urls[pod_number - 1], callback)
+  }
+
+  function getFriendsList (pod_number, end) {
+    return utils.getFriendsList(urls[pod_number - 1], end)
+  }
+
+  function uploadVideo (pod_number, callback) {
+    var name = 'my super video'
+    var description = 'my super description'
+    var fixture = 'video_short.webm'
+
+    return utils.uploadVideo(urls[pod_number - 1], name, description, fixture, callback)
+  }
+
+  function getVideos (pod_number, callback) {
+    return utils.getVideosList(urls[pod_number - 1], callback)
+  }
+
+  // ---------------------------------------------------------------
+
+  before(function (done) {
+    this.timeout(30000)
+    utils.flushAndRunMultipleServers(6, function (apps_run, urls_run) {
+      apps = apps_run
+      urls = urls_run
+      done()
+    })
+  })
+
+  it('Should make friends with two pod each in a different group', function (done) {
+    this.timeout(20000)
+
+    async.series([
+      // Pod 3 makes friend with the first one
+      function (next) {
+        makeFriends(3, next)
+      },
+      // Pod 4 makes friend with the second one
+      function (next) {
+        makeFriends(4, next)
+      },
+      // Now if the fifth wants to make friends with the third et the first
+      function (next) {
+        makeFriends(5, next)
+      },
+      function (next) {
+        setTimeout(next, 11000)
+      }],
+      function (err) {
+        if (err) throw err
+
+        // It should have 0 friends
+        getFriendsList(5, function (err, res) {
+          if (err) throw err
+
+          expect(res.body.length).to.equal(0)
+
+          done()
+        })
+      }
+    )
+  })
+
+  it('Should quit all friends', function (done) {
+    this.timeout(10000)
+
+    async.series([
+      function (next) {
+        quitFriends(1, next)
+      },
+      function (next) {
+        quitFriends(2, next)
+      }],
+      function (err) {
+        if (err) throw err
+
+        async.each([ 1, 2, 3, 4, 5, 6 ], function (i, callback) {
+          getFriendsList(i, function (err, res) {
+            if (err) throw err
+
+            expect(res.body.length).to.equal(0)
+
+            callback()
+          })
+        }, done)
+      }
+    )
+  })
+
+  it('Should make friends with the pods 1, 2, 3', function (done) {
+    this.timeout(150000)
+
+    async.series([
+      // Pods 1, 2, 3 and 4 become friends
+      function (next) {
+        makeFriends(2, next)
+      },
+      function (next) {
+        makeFriends(1, next)
+      },
+      function (next) {
+        makeFriends(4, next)
+      },
+      // Kill pod 4
+      function (next) {
+        apps[3].kill()
+        next()
+      },
+      // Expulse pod 4 from pod 1 and 2
+      function (next) {
+        uploadVideo(1, next)
+      },
+      function (next) {
+        uploadVideo(2, next)
+      },
+      function (next) {
+        setTimeout(next, 11000)
+      },
+      function (next) {
+        uploadVideo(1, next)
+      },
+      function (next) {
+        uploadVideo(2, next)
+      },
+      function (next) {
+        setTimeout(next, 20000)
+      },
+      // Rerun server 4
+      function (next) {
+        utils.runServer(4, function (app, url) {
+          apps[3] = app
+          next()
+        })
+      },
+      function (next) {
+        getFriendsList(4, function (err, res) {
+          if (err) throw err
+
+          // Pod 4 didn't know pod 1 and 2 removed it
+          expect(res.body.length).to.equal(3)
+
+          next()
+        })
+      },
+      // Pod 6 ask pod 1, 2 and 3
+      function (next) {
+        makeFriends(6, next)
+      }],
+      function (err) {
+        if (err) throw err
+
+        getFriendsList(6, function (err, res) {
+          if (err) throw err
+
+          // Pod 4 should not be our friend
+          var result = res.body
+          expect(result.length).to.equal(3)
+          for (var pod of result) {
+            expect(pod.url).not.equal(urls[3])
+          }
+
+          done()
+        })
+      }
+    )
+  })
+
+  it('Should pod 1 quit friends', function (done) {
+    this.timeout(25000)
+
+    async.series([
+      // Upload a video on server 3 for aditionnal tests
+      function (next) {
+        uploadVideo(3, next)
+      },
+      function (next) {
+        setTimeout(next, 15000)
+      },
+      function (next) {
+        quitFriends(1, next)
+      },
+      // Remove pod 1 from pod 2
+      function (next) {
+        getVideos(1, function (err, res) {
+          if (err) throw err
+          expect(res.body).to.be.an('array')
+          expect(res.body.length).to.equal(2)
+
+          next()
+        })
+      }],
+      function (err) {
+        if (err) throw err
+
+        getVideos(2, function (err, res) {
+          if (err) throw err
+          expect(res.body).to.be.an('array')
+          expect(res.body.length).to.equal(3)
+          done()
+        })
+      }
+    )
+  })
+
+  it('Should make friends between pod 1 and 2 and exchange their videos', function (done) {
+    this.timeout(20000)
+    makeFriends(1, function () {
+      setTimeout(function () {
+        getVideos(1, function (err, res) {
+          if (err) throw err
+
+          expect(res.body).to.be.an('array')
+          expect(res.body.length).to.equal(5)
+
+          done()
+        })
+      }, 5000)
+    })
+  })
+
+  after(function (done) {
+    apps.forEach(function (app) {
+      process.kill(-app.pid)
+    })
+
+    if (this.ok) {
+      utils.flushTests(done)
+    } else {
+      done()
+    }
+  })
+})
diff --git a/server/tests/api/friendsBasic.js b/server/tests/api/friendsBasic.js
new file mode 100644
index 000000000..328724936
--- /dev/null
+++ b/server/tests/api/friendsBasic.js
@@ -0,0 +1,185 @@
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var request = require('supertest')
+
+var utils = require('./utils')
+
+describe('Test basic friends', function () {
+  var apps = []
+  var urls = []
+
+  function testMadeFriends (urls, url_to_test, callback) {
+    var friends = []
+    for (var i = 0; i < urls.length; i++) {
+      if (urls[i] === url_to_test) continue
+      friends.push(urls[i])
+    }
+
+    utils.getFriendsList(url_to_test, function (err, res) {
+      if (err) throw err
+
+      var result = res.body
+      var result_urls = [ result[0].url, result[1].url ]
+      expect(result).to.be.an('array')
+      expect(result.length).to.equal(2)
+      expect(result_urls[0]).to.not.equal(result_urls[1])
+
+      var error_string = 'Friends url do not correspond for ' + url_to_test
+      expect(friends).to.contain(result_urls[0], error_string)
+      expect(friends).to.contain(result_urls[1], error_string)
+      callback()
+    })
+  }
+
+  // ---------------------------------------------------------------
+
+  before(function (done) {
+    this.timeout(20000)
+    utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
+      apps = apps_run
+      urls = urls_run
+      done()
+    })
+  })
+
+  it('Should not have friends', function (done) {
+    async.each(urls, function (url, callback) {
+      utils.getFriendsList(url, function (err, res) {
+        if (err) throw err
+
+        var result = res.body
+        expect(result).to.be.an('array')
+        expect(result.length).to.equal(0)
+        callback()
+      })
+    }, done)
+  })
+
+  it('Should make friends', function (done) {
+    this.timeout(10000)
+
+    var path = '/api/v1/pods/makefriends'
+
+    async.series([
+      // The second pod make friend with the third
+      function (next) {
+        request(urls[1])
+          .get(path)
+          .set('Accept', 'application/json')
+          .expect(204)
+          .end(next)
+      },
+      // Wait for the request between pods
+      function (next) {
+        setTimeout(next, 1000)
+      },
+      // The second pod should have the third as a friend
+      function (next) {
+        utils.getFriendsList(urls[1], function (err, res) {
+          if (err) throw err
+
+          var result = res.body
+          expect(result).to.be.an('array')
+          expect(result.length).to.equal(1)
+          expect(result[0].url).to.be.equal(urls[2])
+
+          next()
+        })
+      },
+      // Same here, the third pod should have the second pod as a friend
+      function (next) {
+        utils.getFriendsList(urls[2], function (err, res) {
+          if (err) throw err
+
+          var result = res.body
+          expect(result).to.be.an('array')
+          expect(result.length).to.equal(1)
+          expect(result[0].url).to.be.equal(urls[1])
+
+          next()
+        })
+      },
+      // Finally the first pod make friend with the second pod
+      function (next) {
+        request(urls[0])
+          .get(path)
+          .set('Accept', 'application/json')
+          .expect(204)
+          .end(next)
+      },
+      // Wait for the request between pods
+      function (next) {
+        setTimeout(next, 1000)
+      }
+    ],
+    // Now each pod should be friend with the other ones
+    function (err) {
+      if (err) throw err
+      async.each(urls, function (url, callback) {
+        testMadeFriends(urls, url, callback)
+      }, done)
+    })
+  })
+
+  it('Should not be allowed to make friend again', function (done) {
+    utils.makeFriends(urls[1], 409, done)
+  })
+
+  it('Should quit friends of pod 2', function (done) {
+    async.series([
+      // Pod 1 quit friends
+      function (next) {
+        utils.quitFriends(urls[1], next)
+      },
+      // Pod 1 should not have friends anymore
+      function (next) {
+        utils.getFriendsList(urls[1], function (err, res) {
+          if (err) throw err
+
+          var result = res.body
+          expect(result).to.be.an('array')
+          expect(result.length).to.equal(0)
+
+          next()
+        })
+      },
+      // Other pods shouldn't have pod 1 too
+      function (next) {
+        async.each([ urls[0], urls[2] ], function (url, callback) {
+          utils.getFriendsList(url, function (err, res) {
+            if (err) throw err
+
+            var result = res.body
+            expect(result).to.be.an('array')
+            expect(result.length).to.equal(1)
+            expect(result[0].url).not.to.be.equal(urls[1])
+            callback()
+          })
+        }, next)
+      }
+    ], done)
+  })
+
+  it('Should allow pod 2 to make friend again', function (done) {
+    utils.makeFriends(urls[1], function () {
+      async.each(urls, function (url, callback) {
+        testMadeFriends(urls, url, callback)
+      }, done)
+    })
+  })
+
+  after(function (done) {
+    apps.forEach(function (app) {
+      process.kill(-app.pid)
+    })
+
+    if (this.ok) {
+      utils.flushTests(done)
+    } else {
+      done()
+    }
+  })
+})
diff --git a/server/tests/api/index.js b/server/tests/api/index.js
new file mode 100644
index 000000000..9c4fdd48a
--- /dev/null
+++ b/server/tests/api/index.js
@@ -0,0 +1,8 @@
+'use strict'
+
+// Order of the tests we want to execute
+require('./checkParams')
+require('./friendsBasic')
+require('./singlePod')
+require('./multiplePods')
+require('./friendsAdvanced')
diff --git a/server/tests/api/multiplePods.js b/server/tests/api/multiplePods.js
new file mode 100644
index 000000000..9fdd0f308
--- /dev/null
+++ b/server/tests/api/multiplePods.js
@@ -0,0 +1,328 @@
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var pathUtils = require('path')
+
+var utils = require('./utils')
+var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
+webtorrent.silent = true
+
+describe('Test multiple pods', function () {
+  var apps = []
+  var urls = []
+  var to_remove = []
+
+  before(function (done) {
+    this.timeout(30000)
+
+    async.series([
+      // Run servers
+      function (next) {
+        utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
+          apps = apps_run
+          urls = urls_run
+          next()
+        })
+      },
+      // The second pod make friend with the third
+      function (next) {
+        utils.makeFriends(urls[1], next)
+      },
+      // Wait for the request between pods
+      function (next) {
+        setTimeout(next, 10000)
+      },
+      // Pod 1 make friends too
+      function (next) {
+        utils.makeFriends(urls[0], next)
+      },
+      function (next) {
+        webtorrent.create({ host: 'client', port: '1' }, next)
+      }
+    ], done)
+  })
+
+  it('Should not have videos for all pods', function (done) {
+    async.each(urls, function (url, callback) {
+      utils.getVideosList(url, function (err, res) {
+        if (err) throw err
+
+        expect(res.body).to.be.an('array')
+        expect(res.body.length).to.equal(0)
+
+        callback()
+      })
+    }, done)
+  })
+
+  describe('Should upload the video and propagate on each pod', function () {
+    it('Should upload the video on pod 1 and propagate on each pod', function (done) {
+      this.timeout(15000)
+
+      async.series([
+        function (next) {
+          utils.uploadVideo(urls[0], 'my super name for pod 1', 'my super description for pod 1', 'video_short1.webm', next)
+        },
+        function (next) {
+          setTimeout(next, 11000)
+        }],
+        // All pods should have this video
+        function (err) {
+          if (err) throw err
+
+          async.each(urls, function (url, callback) {
+            var base_magnet = null
+
+            utils.getVideosList(url, function (err, res) {
+              if (err) throw err
+
+              var videos = res.body
+              expect(videos).to.be.an('array')
+              expect(videos.length).to.equal(1)
+              var video = videos[0]
+              expect(video.name).to.equal('my super name for pod 1')
+              expect(video.description).to.equal('my super description for pod 1')
+              expect(video.podUrl).to.equal('http://localhost:9001')
+              expect(video.magnetUri).to.exist
+
+              // All pods should have the same magnet Uri
+              if (base_magnet === null) {
+                base_magnet = video.magnetUri
+              } else {
+                expect(video.magnetUri).to.equal.magnetUri
+              }
+
+              callback()
+            })
+          }, done)
+        }
+      )
+    })
+
+    it('Should upload the video on pod 2 and propagate on each pod', function (done) {
+      this.timeout(15000)
+
+      async.series([
+        function (next) {
+          utils.uploadVideo(urls[1], 'my super name for pod 2', 'my super description for pod 2', 'video_short2.webm', next)
+        },
+        function (next) {
+          setTimeout(next, 11000)
+        }],
+        // All pods should have this video
+        function (err) {
+          if (err) throw err
+
+          async.each(urls, function (url, callback) {
+            var base_magnet = null
+
+            utils.getVideosList(url, function (err, res) {
+              if (err) throw err
+
+              var videos = res.body
+              expect(videos).to.be.an('array')
+              expect(videos.length).to.equal(2)
+              var video = videos[1]
+              expect(video.name).to.equal('my super name for pod 2')
+              expect(video.description).to.equal('my super description for pod 2')
+              expect(video.podUrl).to.equal('http://localhost:9002')
+              expect(video.magnetUri).to.exist
+
+              // All pods should have the same magnet Uri
+              if (base_magnet === null) {
+                base_magnet = video.magnetUri
+              } else {
+                expect(video.magnetUri).to.equal.magnetUri
+              }
+
+              callback()
+            })
+          }, done)
+        }
+      )
+    })
+
+    it('Should upload two videos on pod 3 and propagate on each pod', function (done) {
+      this.timeout(30000)
+
+      async.series([
+        function (next) {
+          utils.uploadVideo(urls[2], 'my super name for pod 3', 'my super description for pod 3', 'video_short3.webm', next)
+        },
+        function (next) {
+          utils.uploadVideo(urls[2], 'my super name for pod 3-2', 'my super description for pod 3-2', 'video_short.webm', next)
+        },
+        function (next) {
+          setTimeout(next, 22000)
+        }],
+        function (err) {
+          if (err) throw err
+
+          var base_magnet = null
+          // All pods should have this video
+          async.each(urls, function (url, callback) {
+            utils.getVideosList(url, function (err, res) {
+              if (err) throw err
+
+              var videos = res.body
+              expect(videos).to.be.an('array')
+              expect(videos.length).to.equal(4)
+              var video = videos[2]
+              expect(video.name).to.equal('my super name for pod 3')
+              expect(video.description).to.equal('my super description for pod 3')
+              expect(video.podUrl).to.equal('http://localhost:9003')
+              expect(video.magnetUri).to.exist
+
+              video = videos[3]
+              expect(video.name).to.equal('my super name for pod 3-2')
+              expect(video.description).to.equal('my super description for pod 3-2')
+              expect(video.podUrl).to.equal('http://localhost:9003')
+              expect(video.magnetUri).to.exist
+
+              // All pods should have the same magnet Uri
+              if (base_magnet === null) {
+                base_magnet = video.magnetUri
+              } else {
+                expect(video.magnetUri).to.equal.magnetUri
+              }
+
+              callback()
+            })
+          }, done)
+        }
+      )
+    })
+  })
+
+  describe('Should seed the uploaded video', function () {
+    it('Should add the file 1 by asking pod 3', function (done) {
+      // Yes, this could be long
+      this.timeout(200000)
+
+      utils.getVideosList(urls[2], function (err, res) {
+        if (err) throw err
+
+        var video = res.body[0]
+        to_remove.push(res.body[2]._id)
+        to_remove.push(res.body[3]._id)
+
+        webtorrent.add(video.magnetUri, function (torrent) {
+          expect(torrent.files).to.exist
+          expect(torrent.files.length).to.equal(1)
+          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+          done()
+        })
+      })
+    })
+
+    it('Should add the file 2 by asking pod 1', function (done) {
+      // Yes, this could be long
+      this.timeout(200000)
+
+      utils.getVideosList(urls[0], function (err, res) {
+        if (err) throw err
+
+        var video = res.body[1]
+
+        webtorrent.add(video.magnetUri, function (torrent) {
+          expect(torrent.files).to.exist
+          expect(torrent.files.length).to.equal(1)
+          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+          done()
+        })
+      })
+    })
+
+    it('Should add the file 3 by asking pod 2', function (done) {
+      // Yes, this could be long
+      this.timeout(200000)
+
+      utils.getVideosList(urls[1], function (err, res) {
+        if (err) throw err
+
+        var video = res.body[2]
+
+        webtorrent.add(video.magnetUri, function (torrent) {
+          expect(torrent.files).to.exist
+          expect(torrent.files.length).to.equal(1)
+          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+          done()
+        })
+      })
+    })
+
+    it('Should add the file 3-2 by asking pod 1', function (done) {
+      // Yes, this could be long
+      this.timeout(200000)
+
+      utils.getVideosList(urls[0], function (err, res) {
+        if (err) throw err
+
+        var video = res.body[3]
+
+        webtorrent.add(video.magnetUri, function (torrent) {
+          expect(torrent.files).to.exist
+          expect(torrent.files.length).to.equal(1)
+          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+          done()
+        })
+      })
+    })
+
+    it('Should remove the file 3 and 3-2 by asking pod 3', function (done) {
+      this.timeout(15000)
+
+      async.series([
+        function (next) {
+          utils.removeVideo(urls[2], to_remove[0], next)
+        },
+        function (next) {
+          utils.removeVideo(urls[2], to_remove[1], next)
+        }],
+        function (err) {
+          if (err) throw err
+          setTimeout(done, 11000)
+        }
+      )
+    })
+
+    it('Should have videos 1 and 3 on each pod', function (done) {
+      async.each(urls, function (url, callback) {
+        utils.getVideosList(url, function (err, res) {
+          if (err) throw err
+
+          var videos = res.body
+          expect(videos).to.be.an('array')
+          expect(videos.length).to.equal(2)
+          expect(videos[0]._id).not.to.equal(videos[1]._id)
+          expect(videos[0]._id).not.to.equal(to_remove[0])
+          expect(videos[1]._id).not.to.equal(to_remove[0])
+          expect(videos[0]._id).not.to.equal(to_remove[1])
+          expect(videos[1]._id).not.to.equal(to_remove[1])
+
+          callback()
+        })
+      }, done)
+    })
+  })
+
+  after(function (done) {
+    apps.forEach(function (app) {
+      process.kill(-app.pid)
+    })
+    process.kill(-webtorrent.app.pid)
+
+    // Keep the logs if the test failed
+    if (this.ok) {
+      utils.flushTests(done)
+    } else {
+      done()
+    }
+  })
+})
diff --git a/server/tests/api/singlePod.js b/server/tests/api/singlePod.js
new file mode 100644
index 000000000..3dd72c01b
--- /dev/null
+++ b/server/tests/api/singlePod.js
@@ -0,0 +1,146 @@
+'use strict'
+
+var async = require('async')
+var chai = require('chai')
+var expect = chai.expect
+var fs = require('fs')
+var pathUtils = require('path')
+
+var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
+webtorrent.silent = true
+
+var utils = require('./utils')
+
+describe('Test a single pod', function () {
+  var app = null
+  var url = ''
+  var video_id = -1
+
+  before(function (done) {
+    this.timeout(20000)
+
+    async.series([
+      function (next) {
+        utils.flushTests(next)
+      },
+      function (next) {
+        utils.runServer(1, function (app1, url1) {
+          app = app1
+          url = url1
+          next()
+        })
+      },
+      function (next) {
+        webtorrent.create({ host: 'client', port: '1' }, next)
+      }
+    ], done)
+  })
+
+  it('Should not have videos', function (done) {
+    utils.getVideosList(url, function (err, res) {
+      if (err) throw err
+
+      expect(res.body).to.be.an('array')
+      expect(res.body.length).to.equal(0)
+
+      done()
+    })
+  })
+
+  it('Should upload the video', function (done) {
+    this.timeout(5000)
+    utils.uploadVideo(url, 'my super name', 'my super description', 'video_short.webm', done)
+  })
+
+  it('Should seed the uploaded video', function (done) {
+    // Yes, this could be long
+    this.timeout(60000)
+
+    utils.getVideosList(url, function (err, res) {
+      if (err) throw err
+
+      expect(res.body).to.be.an('array')
+      expect(res.body.length).to.equal(1)
+
+      var video = res.body[0]
+      expect(video.name).to.equal('my super name')
+      expect(video.description).to.equal('my super description')
+      expect(video.podUrl).to.equal('http://localhost:9001')
+      expect(video.magnetUri).to.exist
+
+      video_id = video._id
+
+      webtorrent.add(video.magnetUri, function (torrent) {
+        expect(torrent.files).to.exist
+        expect(torrent.files.length).to.equal(1)
+        expect(torrent.files[0].path).to.exist.and.to.not.equal('')
+
+        done()
+      })
+    })
+  })
+
+  it('Should search the video', function (done) {
+    utils.searchVideo(url, 'my', function (err, res) {
+      if (err) throw err
+
+      expect(res.body).to.be.an('array')
+      expect(res.body.length).to.equal(1)
+
+      var video = res.body[0]
+      expect(video.name).to.equal('my super name')
+      expect(video.description).to.equal('my super description')
+      expect(video.podUrl).to.equal('http://localhost:9001')
+      expect(video.magnetUri).to.exist
+
+      done()
+    })
+  })
+
+  it('Should not find a search', function (done) {
+    utils.searchVideo(url, 'hello', function (err, res) {
+      if (err) throw err
+
+      expect(res.body).to.be.an('array')
+      expect(res.body.length).to.equal(0)
+
+      done()
+    })
+  })
+
+  it('Should remove the video', function (done) {
+    utils.removeVideo(url, video_id, function (err) {
+      if (err) throw err
+
+      fs.readdir(pathUtils.join(__dirname, '../../test1/uploads/'), function (err, files) {
+        if (err) throw err
+
+        expect(files.length).to.equal(0)
+        done()
+      })
+    })
+  })
+
+  it('Should not have videos', function (done) {
+    utils.getVideosList(url, function (err, res) {
+      if (err) throw err
+
+      expect(res.body).to.be.an('array')
+      expect(res.body.length).to.equal(0)
+
+      done()
+    })
+  })
+
+  after(function (done) {
+    process.kill(-app.pid)
+    process.kill(-webtorrent.app.pid)
+
+    // Keep the logs if the test failed
+    if (this.ok) {
+      utils.flushTests(done)
+    } else {
+      done()
+    }
+  })
+})
diff --git a/server/tests/api/utils.js b/server/tests/api/utils.js
new file mode 100644
index 000000000..47b706294
--- /dev/null
+++ b/server/tests/api/utils.js
@@ -0,0 +1,185 @@
+'use strict'
+
+var child_process = require('child_process')
+var exec = child_process.exec
+var fork = child_process.fork
+var pathUtils = require('path')
+var request = require('supertest')
+
+var testUtils = {
+  flushTests: flushTests,
+  getFriendsList: getFriendsList,
+  getVideosList: getVideosList,
+  makeFriends: makeFriends,
+  quitFriends: quitFriends,
+  removeVideo: removeVideo,
+  flushAndRunMultipleServers: flushAndRunMultipleServers,
+  runServer: runServer,
+  searchVideo: searchVideo,
+  uploadVideo: uploadVideo
+}
+
+// ---------------------- Export functions --------------------
+
+function flushTests (callback) {
+  exec(pathUtils.join(__dirname, '../../scripts/clean_test.sh'), callback)
+}
+
+function getFriendsList (url, end) {
+  var path = '/api/v1/pods/'
+
+  request(url)
+    .get(path)
+    .set('Accept', 'application/json')
+    .expect(200)
+    .expect('Content-Type', /json/)
+    .end(end)
+}
+
+function getVideosList (url, end) {
+  var path = '/api/v1/videos'
+
+  request(url)
+    .get(path)
+    .set('Accept', 'application/json')
+    .expect(200)
+    .expect('Content-Type', /json/)
+    .end(end)
+}
+
+function makeFriends (url, expected_status, callback) {
+  if (!callback) {
+    callback = expected_status
+    expected_status = 204
+  }
+
+  var path = '/api/v1/pods/makefriends'
+
+  // The first pod make friend with the third
+  request(url)
+    .get(path)
+    .set('Accept', 'application/json')
+    .expect(expected_status)
+    .end(function (err, res) {
+      if (err) throw err
+
+      // Wait for the request between pods
+      setTimeout(callback, 1000)
+    })
+}
+
+function quitFriends (url, callback) {
+  var path = '/api/v1/pods/quitfriends'
+
+  // The first pod make friend with the third
+  request(url)
+    .get(path)
+    .set('Accept', 'application/json')
+    .expect(204)
+    .end(function (err, res) {
+      if (err) throw err
+
+      // Wait for the request between pods
+      setTimeout(callback, 1000)
+    })
+}
+
+function removeVideo (url, id, end) {
+  var path = '/api/v1/videos'
+
+  request(url)
+    .delete(path + '/' + id)
+    .set('Accept', 'application/json')
+    .expect(204)
+    .end(end)
+}
+
+function flushAndRunMultipleServers (total_servers, serversRun) {
+  var apps = []
+  var urls = []
+  var i = 0
+
+  function anotherServerDone (number, app, url) {
+    apps[number - 1] = app
+    urls[number - 1] = url
+    i++
+    if (i === total_servers) {
+      serversRun(apps, urls)
+    }
+  }
+
+  flushTests(function () {
+    for (var j = 1; j <= total_servers; j++) {
+      (function (k) { // TODO: ES6 with let
+        // For the virtual buffer
+        setTimeout(function () {
+          runServer(k, function (app, url) {
+            anotherServerDone(k, app, url)
+          })
+        }, 1000 * k)
+      })(j)
+    }
+  })
+}
+
+function runServer (number, callback) {
+  var port = 9000 + number
+  var server_run_string = {
+    'Connected to mongodb': false,
+    'Server listening on port': false
+  }
+
+  // Share the environment
+  var env = Object.create(process.env)
+  env.NODE_ENV = 'test'
+  env.NODE_APP_INSTANCE = number
+  var options = {
+    silent: true,
+    env: env,
+    detached: true
+  }
+
+  var app = fork(pathUtils.join(__dirname, '../../server.js'), [], options)
+  app.stdout.on('data', function onStdout (data) {
+    var dont_continue = false
+    // Check if all required sentences are here
+    for (var key of Object.keys(server_run_string)) {
+      if (data.toString().indexOf(key) !== -1) server_run_string[key] = true
+      if (server_run_string[key] === false) dont_continue = true
+    }
+
+    // If no, there is maybe one thing not already initialized (mongodb...)
+    if (dont_continue === true) return
+
+    app.stdout.removeListener('data', onStdout)
+    callback(app, 'http://localhost:' + port)
+  })
+}
+
+function searchVideo (url, search, end) {
+  var path = '/api/v1/videos'
+
+  request(url)
+    .get(path + '/search/' + search)
+    .set('Accept', 'application/json')
+    .expect(200)
+    .expect('Content-Type', /json/)
+    .end(end)
+}
+
+function uploadVideo (url, name, description, fixture, end) {
+  var path = '/api/v1/videos'
+
+  request(url)
+    .post(path)
+    .set('Accept', 'application/json')
+    .field('name', name)
+    .field('description', description)
+    .attach('input_video', pathUtils.join(__dirname, 'fixtures', fixture))
+    .expect(201)
+    .end(end)
+}
+
+// ---------------------------------------------------------------------------
+
+module.exports = testUtils
diff --git a/server/tests/index.js b/server/tests/index.js
new file mode 100644
index 000000000..ccebbfe51
--- /dev/null
+++ b/server/tests/index.js
@@ -0,0 +1,6 @@
+;(function () {
+  'use strict'
+
+  // Order of the tests we want to execute
+  require('./api/')
+})()
diff --git a/tests/api/checkParams.js b/tests/api/checkParams.js
deleted file mode 100644
index 1c1ec71b3..000000000
--- a/tests/api/checkParams.js
+++ /dev/null
@@ -1,300 +0,0 @@
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var pathUtils = require('path')
-var request = require('supertest')
-
-var utils = require('./utils')
-
-describe('Test parameters validator', function () {
-  var app = null
-  var url = ''
-
-  function makePostRequest (path, fields, attach, done, fail) {
-    var status_code = 400
-    if (fail !== undefined && fail === false) status_code = 200
-
-    var req = request(url)
-      .post(path)
-      .set('Accept', 'application/json')
-
-    Object.keys(fields).forEach(function (field) {
-      var value = fields[field]
-      req.field(field, value)
-    })
-
-    req.expect(status_code, done)
-  }
-
-  function makePostBodyRequest (path, fields, done, fail) {
-    var status_code = 400
-    if (fail !== undefined && fail === false) status_code = 200
-
-    request(url)
-      .post(path)
-      .set('Accept', 'application/json')
-      .send(fields)
-      .expect(status_code, done)
-  }
-
-  // ---------------------------------------------------------------
-
-  before(function (done) {
-    this.timeout(20000)
-
-    async.series([
-      function (next) {
-        utils.flushTests(next)
-      },
-      function (next) {
-        utils.runServer(1, function (app1, url1) {
-          app = app1
-          url = url1
-          next()
-        })
-      }
-    ], done)
-  })
-
-  describe('Of the pods API', function () {
-    var path = '/api/v1/pods/'
-
-    describe('When adding a pod', function () {
-      it('Should fail with nothing', function (done) {
-        var data = {}
-        makePostBodyRequest(path, data, done)
-      })
-
-      it('Should fail without public key', function (done) {
-        var data = {
-          data: {
-            url: 'http://coucou.com'
-          }
-        }
-        makePostBodyRequest(path, data, done)
-      })
-
-      it('Should fail without an url', function (done) {
-        var data = {
-          data: {
-            publicKey: 'mysuperpublickey'
-          }
-        }
-        makePostBodyRequest(path, data, done)
-      })
-
-      it('Should fail with an incorrect url', function (done) {
-        var data = {
-          data: {
-            url: 'coucou.com',
-            publicKey: 'mysuperpublickey'
-          }
-        }
-        makePostBodyRequest(path, data, function () {
-          data.data.url = 'http://coucou'
-          makePostBodyRequest(path, data, function () {
-            data.data.url = 'coucou'
-            makePostBodyRequest(path, data, done)
-          })
-        })
-      })
-
-      it('Should succeed with the correct parameters', function (done) {
-        var data = {
-          data: {
-            url: 'http://coucou.com',
-            publicKey: 'mysuperpublickey'
-          }
-        }
-        makePostBodyRequest(path, data, done, false)
-      })
-    })
-  })
-
-  describe('Of the videos API', function () {
-    var path = '/api/v1/videos/'
-
-    describe('When searching a video', function () {
-      it('Should fail with nothing', function (done) {
-        request(url)
-          .get(pathUtils.join(path, 'search'))
-          .set('Accept', 'application/json')
-          .expect(400, done)
-      })
-    })
-
-    describe('When adding a video', function () {
-      it('Should fail with nothing', function (done) {
-        var data = {}
-        var attach = {}
-        makePostRequest(path, data, attach, done)
-      })
-
-      it('Should fail without name', function (done) {
-        var data = {
-          description: 'my super description'
-        }
-        var attach = {
-          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
-        }
-        makePostRequest(path, data, attach, done)
-      })
-
-      it('Should fail with a long name', function (done) {
-        var data = {
-          name: 'My very very very very very very very very very very very very very very very very long name',
-          description: 'my super description'
-        }
-        var attach = {
-          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
-        }
-        makePostRequest(path, data, attach, done)
-      })
-
-      it('Should fail without description', function (done) {
-        var data = {
-          name: 'my super name'
-        }
-        var attach = {
-          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
-        }
-        makePostRequest(path, data, attach, done)
-      })
-
-      it('Should fail with a long description', function (done) {
-        var data = {
-          name: 'my super name',
-          description: 'my super description which is very very very very very very very very very very very very very very' +
-                       'very very very very very very very very very very very very very very very very very very very very very' +
-                       'very very very very very very very very very very very very very very very long'
-        }
-        var attach = {
-          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
-        }
-        makePostRequest(path, data, attach, done)
-      })
-
-      it('Should fail without an input file', function (done) {
-        var data = {
-          name: 'my super name',
-          description: 'my super description'
-        }
-        var attach = {}
-        makePostRequest(path, data, attach, done)
-      })
-
-      it('Should fail without an incorrect input file', function (done) {
-        var data = {
-          name: 'my super name',
-          description: 'my super description'
-        }
-        var attach = {
-          'input_video': pathUtils.join(__dirname, '..', 'fixtures', 'video_short_fake.webm')
-        }
-        makePostRequest(path, data, attach, done)
-      })
-
-      it('Should succeed with the correct parameters', function (done) {
-        var data = {
-          name: 'my super name',
-          description: 'my super description'
-        }
-        var attach = {
-          'input_video': pathUtils.join(__dirname, 'fixtures', 'video_short.webm')
-        }
-        makePostRequest(path, data, attach, function () {
-          attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.mp4')
-          makePostRequest(path, data, attach, function () {
-            attach.input_video = pathUtils.join(__dirname, 'fixtures', 'video_short.ogv')
-            makePostRequest(path, data, attach, done, true)
-          }, true)
-        }, true)
-      })
-    })
-
-    describe('When getting a video', function () {
-      it('Should return the list of the videos with nothing', function (done) {
-        request(url)
-          .get(path)
-          .set('Accept', 'application/json')
-          .expect(200)
-          .expect('Content-Type', /json/)
-          .end(function (err, res) {
-            if (err) throw err
-
-            expect(res.body).to.be.an('array')
-            expect(res.body.length).to.equal(0)
-
-            done()
-          })
-      })
-
-      it('Should fail without a mongodb id', function (done) {
-        request(url)
-          .get(path + 'coucou')
-          .set('Accept', 'application/json')
-          .expect(400, done)
-      })
-
-      it('Should return 404 with an incorrect video', function (done) {
-        request(url)
-          .get(path + '123456789012345678901234')
-          .set('Accept', 'application/json')
-          .expect(404, done)
-      })
-
-      it('Should succeed with the correct parameters')
-    })
-
-    describe('When removing a video', function () {
-      it('Should have 404 with nothing', function (done) {
-        request(url)
-        .delete(path)
-        .expect(404, done)
-      })
-
-      it('Should fail without a mongodb id', function (done) {
-        request(url)
-          .delete(path + 'hello')
-          .expect(400, done)
-      })
-
-      it('Should fail with a video which does not exist', function (done) {
-        request(url)
-          .delete(path + '123456789012345678901234')
-          .expect(404, done)
-      })
-
-      it('Should fail with a video of another pod')
-
-      it('Should succeed with the correct parameters')
-    })
-  })
-
-  describe('Of the remote videos API', function () {
-    describe('When making a secure request', function () {
-      it('Should check a secure request')
-    })
-
-    describe('When adding a video', function () {
-      it('Should check when adding a video')
-    })
-
-    describe('When removing a video', function () {
-      it('Should check when removing a video')
-    })
-  })
-
-  after(function (done) {
-    process.kill(-app.pid)
-
-    // Keep the logs if the test failed
-    if (this.ok) {
-      utils.flushTests(done)
-    } else {
-      done()
-    }
-  })
-})
diff --git a/tests/api/fixtures/video_short.mp4 b/tests/api/fixtures/video_short.mp4
deleted file mode 100644
index 35678362b..000000000
Binary files a/tests/api/fixtures/video_short.mp4 and /dev/null differ
diff --git a/tests/api/fixtures/video_short.ogv b/tests/api/fixtures/video_short.ogv
deleted file mode 100644
index 9e253da82..000000000
Binary files a/tests/api/fixtures/video_short.ogv and /dev/null differ
diff --git a/tests/api/fixtures/video_short.webm b/tests/api/fixtures/video_short.webm
deleted file mode 100644
index bf4b0ab6c..000000000
Binary files a/tests/api/fixtures/video_short.webm and /dev/null differ
diff --git a/tests/api/fixtures/video_short1.webm b/tests/api/fixtures/video_short1.webm
deleted file mode 100644
index 70ac0c644..000000000
Binary files a/tests/api/fixtures/video_short1.webm and /dev/null differ
diff --git a/tests/api/fixtures/video_short2.webm b/tests/api/fixtures/video_short2.webm
deleted file mode 100644
index 13d72dff7..000000000
Binary files a/tests/api/fixtures/video_short2.webm and /dev/null differ
diff --git a/tests/api/fixtures/video_short3.webm b/tests/api/fixtures/video_short3.webm
deleted file mode 100644
index cde5dcd58..000000000
Binary files a/tests/api/fixtures/video_short3.webm and /dev/null differ
diff --git a/tests/api/fixtures/video_short_fake.webm b/tests/api/fixtures/video_short_fake.webm
deleted file mode 100644
index d85290ae5..000000000
--- a/tests/api/fixtures/video_short_fake.webm
+++ /dev/null
@@ -1 +0,0 @@
-this is a fake video mouahahah
diff --git a/tests/api/friendsAdvanced.js b/tests/api/friendsAdvanced.js
deleted file mode 100644
index 9838d890f..000000000
--- a/tests/api/friendsAdvanced.js
+++ /dev/null
@@ -1,250 +0,0 @@
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-
-var utils = require('./utils')
-
-describe('Test advanced friends', function () {
-  var apps = []
-  var urls = []
-
-  function makeFriends (pod_number, callback) {
-    return utils.makeFriends(urls[pod_number - 1], callback)
-  }
-
-  function quitFriends (pod_number, callback) {
-    return utils.quitFriends(urls[pod_number - 1], callback)
-  }
-
-  function getFriendsList (pod_number, end) {
-    return utils.getFriendsList(urls[pod_number - 1], end)
-  }
-
-  function uploadVideo (pod_number, callback) {
-    var name = 'my super video'
-    var description = 'my super description'
-    var fixture = 'video_short.webm'
-
-    return utils.uploadVideo(urls[pod_number - 1], name, description, fixture, callback)
-  }
-
-  function getVideos (pod_number, callback) {
-    return utils.getVideosList(urls[pod_number - 1], callback)
-  }
-
-  // ---------------------------------------------------------------
-
-  before(function (done) {
-    this.timeout(30000)
-    utils.flushAndRunMultipleServers(6, function (apps_run, urls_run) {
-      apps = apps_run
-      urls = urls_run
-      done()
-    })
-  })
-
-  it('Should make friends with two pod each in a different group', function (done) {
-    this.timeout(20000)
-
-    async.series([
-      // Pod 3 makes friend with the first one
-      function (next) {
-        makeFriends(3, next)
-      },
-      // Pod 4 makes friend with the second one
-      function (next) {
-        makeFriends(4, next)
-      },
-      // Now if the fifth wants to make friends with the third et the first
-      function (next) {
-        makeFriends(5, next)
-      },
-      function (next) {
-        setTimeout(next, 11000)
-      }],
-      function (err) {
-        if (err) throw err
-
-        // It should have 0 friends
-        getFriendsList(5, function (err, res) {
-          if (err) throw err
-
-          expect(res.body.length).to.equal(0)
-
-          done()
-        })
-      }
-    )
-  })
-
-  it('Should quit all friends', function (done) {
-    this.timeout(10000)
-
-    async.series([
-      function (next) {
-        quitFriends(1, next)
-      },
-      function (next) {
-        quitFriends(2, next)
-      }],
-      function (err) {
-        if (err) throw err
-
-        async.each([ 1, 2, 3, 4, 5, 6 ], function (i, callback) {
-          getFriendsList(i, function (err, res) {
-            if (err) throw err
-
-            expect(res.body.length).to.equal(0)
-
-            callback()
-          })
-        }, done)
-      }
-    )
-  })
-
-  it('Should make friends with the pods 1, 2, 3', function (done) {
-    this.timeout(150000)
-
-    async.series([
-      // Pods 1, 2, 3 and 4 become friends
-      function (next) {
-        makeFriends(2, next)
-      },
-      function (next) {
-        makeFriends(1, next)
-      },
-      function (next) {
-        makeFriends(4, next)
-      },
-      // Kill pod 4
-      function (next) {
-        apps[3].kill()
-        next()
-      },
-      // Expulse pod 4 from pod 1 and 2
-      function (next) {
-        uploadVideo(1, next)
-      },
-      function (next) {
-        uploadVideo(2, next)
-      },
-      function (next) {
-        setTimeout(next, 11000)
-      },
-      function (next) {
-        uploadVideo(1, next)
-      },
-      function (next) {
-        uploadVideo(2, next)
-      },
-      function (next) {
-        setTimeout(next, 20000)
-      },
-      // Rerun server 4
-      function (next) {
-        utils.runServer(4, function (app, url) {
-          apps[3] = app
-          next()
-        })
-      },
-      function (next) {
-        getFriendsList(4, function (err, res) {
-          if (err) throw err
-
-          // Pod 4 didn't know pod 1 and 2 removed it
-          expect(res.body.length).to.equal(3)
-
-          next()
-        })
-      },
-      // Pod 6 ask pod 1, 2 and 3
-      function (next) {
-        makeFriends(6, next)
-      }],
-      function (err) {
-        if (err) throw err
-
-        getFriendsList(6, function (err, res) {
-          if (err) throw err
-
-          // Pod 4 should not be our friend
-          var result = res.body
-          expect(result.length).to.equal(3)
-          for (var pod of result) {
-            expect(pod.url).not.equal(urls[3])
-          }
-
-          done()
-        })
-      }
-    )
-  })
-
-  it('Should pod 1 quit friends', function (done) {
-    this.timeout(25000)
-
-    async.series([
-      // Upload a video on server 3 for aditionnal tests
-      function (next) {
-        uploadVideo(3, next)
-      },
-      function (next) {
-        setTimeout(next, 15000)
-      },
-      function (next) {
-        quitFriends(1, next)
-      },
-      // Remove pod 1 from pod 2
-      function (next) {
-        getVideos(1, function (err, res) {
-          if (err) throw err
-          expect(res.body).to.be.an('array')
-          expect(res.body.length).to.equal(2)
-
-          next()
-        })
-      }],
-      function (err) {
-        if (err) throw err
-
-        getVideos(2, function (err, res) {
-          if (err) throw err
-          expect(res.body).to.be.an('array')
-          expect(res.body.length).to.equal(3)
-          done()
-        })
-      }
-    )
-  })
-
-  it('Should make friends between pod 1 and 2 and exchange their videos', function (done) {
-    this.timeout(20000)
-    makeFriends(1, function () {
-      setTimeout(function () {
-        getVideos(1, function (err, res) {
-          if (err) throw err
-
-          expect(res.body).to.be.an('array')
-          expect(res.body.length).to.equal(5)
-
-          done()
-        })
-      }, 5000)
-    })
-  })
-
-  after(function (done) {
-    apps.forEach(function (app) {
-      process.kill(-app.pid)
-    })
-
-    if (this.ok) {
-      utils.flushTests(done)
-    } else {
-      done()
-    }
-  })
-})
diff --git a/tests/api/friendsBasic.js b/tests/api/friendsBasic.js
deleted file mode 100644
index 328724936..000000000
--- a/tests/api/friendsBasic.js
+++ /dev/null
@@ -1,185 +0,0 @@
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var request = require('supertest')
-
-var utils = require('./utils')
-
-describe('Test basic friends', function () {
-  var apps = []
-  var urls = []
-
-  function testMadeFriends (urls, url_to_test, callback) {
-    var friends = []
-    for (var i = 0; i < urls.length; i++) {
-      if (urls[i] === url_to_test) continue
-      friends.push(urls[i])
-    }
-
-    utils.getFriendsList(url_to_test, function (err, res) {
-      if (err) throw err
-
-      var result = res.body
-      var result_urls = [ result[0].url, result[1].url ]
-      expect(result).to.be.an('array')
-      expect(result.length).to.equal(2)
-      expect(result_urls[0]).to.not.equal(result_urls[1])
-
-      var error_string = 'Friends url do not correspond for ' + url_to_test
-      expect(friends).to.contain(result_urls[0], error_string)
-      expect(friends).to.contain(result_urls[1], error_string)
-      callback()
-    })
-  }
-
-  // ---------------------------------------------------------------
-
-  before(function (done) {
-    this.timeout(20000)
-    utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
-      apps = apps_run
-      urls = urls_run
-      done()
-    })
-  })
-
-  it('Should not have friends', function (done) {
-    async.each(urls, function (url, callback) {
-      utils.getFriendsList(url, function (err, res) {
-        if (err) throw err
-
-        var result = res.body
-        expect(result).to.be.an('array')
-        expect(result.length).to.equal(0)
-        callback()
-      })
-    }, done)
-  })
-
-  it('Should make friends', function (done) {
-    this.timeout(10000)
-
-    var path = '/api/v1/pods/makefriends'
-
-    async.series([
-      // The second pod make friend with the third
-      function (next) {
-        request(urls[1])
-          .get(path)
-          .set('Accept', 'application/json')
-          .expect(204)
-          .end(next)
-      },
-      // Wait for the request between pods
-      function (next) {
-        setTimeout(next, 1000)
-      },
-      // The second pod should have the third as a friend
-      function (next) {
-        utils.getFriendsList(urls[1], function (err, res) {
-          if (err) throw err
-
-          var result = res.body
-          expect(result).to.be.an('array')
-          expect(result.length).to.equal(1)
-          expect(result[0].url).to.be.equal(urls[2])
-
-          next()
-        })
-      },
-      // Same here, the third pod should have the second pod as a friend
-      function (next) {
-        utils.getFriendsList(urls[2], function (err, res) {
-          if (err) throw err
-
-          var result = res.body
-          expect(result).to.be.an('array')
-          expect(result.length).to.equal(1)
-          expect(result[0].url).to.be.equal(urls[1])
-
-          next()
-        })
-      },
-      // Finally the first pod make friend with the second pod
-      function (next) {
-        request(urls[0])
-          .get(path)
-          .set('Accept', 'application/json')
-          .expect(204)
-          .end(next)
-      },
-      // Wait for the request between pods
-      function (next) {
-        setTimeout(next, 1000)
-      }
-    ],
-    // Now each pod should be friend with the other ones
-    function (err) {
-      if (err) throw err
-      async.each(urls, function (url, callback) {
-        testMadeFriends(urls, url, callback)
-      }, done)
-    })
-  })
-
-  it('Should not be allowed to make friend again', function (done) {
-    utils.makeFriends(urls[1], 409, done)
-  })
-
-  it('Should quit friends of pod 2', function (done) {
-    async.series([
-      // Pod 1 quit friends
-      function (next) {
-        utils.quitFriends(urls[1], next)
-      },
-      // Pod 1 should not have friends anymore
-      function (next) {
-        utils.getFriendsList(urls[1], function (err, res) {
-          if (err) throw err
-
-          var result = res.body
-          expect(result).to.be.an('array')
-          expect(result.length).to.equal(0)
-
-          next()
-        })
-      },
-      // Other pods shouldn't have pod 1 too
-      function (next) {
-        async.each([ urls[0], urls[2] ], function (url, callback) {
-          utils.getFriendsList(url, function (err, res) {
-            if (err) throw err
-
-            var result = res.body
-            expect(result).to.be.an('array')
-            expect(result.length).to.equal(1)
-            expect(result[0].url).not.to.be.equal(urls[1])
-            callback()
-          })
-        }, next)
-      }
-    ], done)
-  })
-
-  it('Should allow pod 2 to make friend again', function (done) {
-    utils.makeFriends(urls[1], function () {
-      async.each(urls, function (url, callback) {
-        testMadeFriends(urls, url, callback)
-      }, done)
-    })
-  })
-
-  after(function (done) {
-    apps.forEach(function (app) {
-      process.kill(-app.pid)
-    })
-
-    if (this.ok) {
-      utils.flushTests(done)
-    } else {
-      done()
-    }
-  })
-})
diff --git a/tests/api/index.js b/tests/api/index.js
deleted file mode 100644
index 9c4fdd48a..000000000
--- a/tests/api/index.js
+++ /dev/null
@@ -1,8 +0,0 @@
-'use strict'
-
-// Order of the tests we want to execute
-require('./checkParams')
-require('./friendsBasic')
-require('./singlePod')
-require('./multiplePods')
-require('./friendsAdvanced')
diff --git a/tests/api/multiplePods.js b/tests/api/multiplePods.js
deleted file mode 100644
index 9fdd0f308..000000000
--- a/tests/api/multiplePods.js
+++ /dev/null
@@ -1,328 +0,0 @@
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var pathUtils = require('path')
-
-var utils = require('./utils')
-var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
-webtorrent.silent = true
-
-describe('Test multiple pods', function () {
-  var apps = []
-  var urls = []
-  var to_remove = []
-
-  before(function (done) {
-    this.timeout(30000)
-
-    async.series([
-      // Run servers
-      function (next) {
-        utils.flushAndRunMultipleServers(3, function (apps_run, urls_run) {
-          apps = apps_run
-          urls = urls_run
-          next()
-        })
-      },
-      // The second pod make friend with the third
-      function (next) {
-        utils.makeFriends(urls[1], next)
-      },
-      // Wait for the request between pods
-      function (next) {
-        setTimeout(next, 10000)
-      },
-      // Pod 1 make friends too
-      function (next) {
-        utils.makeFriends(urls[0], next)
-      },
-      function (next) {
-        webtorrent.create({ host: 'client', port: '1' }, next)
-      }
-    ], done)
-  })
-
-  it('Should not have videos for all pods', function (done) {
-    async.each(urls, function (url, callback) {
-      utils.getVideosList(url, function (err, res) {
-        if (err) throw err
-
-        expect(res.body).to.be.an('array')
-        expect(res.body.length).to.equal(0)
-
-        callback()
-      })
-    }, done)
-  })
-
-  describe('Should upload the video and propagate on each pod', function () {
-    it('Should upload the video on pod 1 and propagate on each pod', function (done) {
-      this.timeout(15000)
-
-      async.series([
-        function (next) {
-          utils.uploadVideo(urls[0], 'my super name for pod 1', 'my super description for pod 1', 'video_short1.webm', next)
-        },
-        function (next) {
-          setTimeout(next, 11000)
-        }],
-        // All pods should have this video
-        function (err) {
-          if (err) throw err
-
-          async.each(urls, function (url, callback) {
-            var base_magnet = null
-
-            utils.getVideosList(url, function (err, res) {
-              if (err) throw err
-
-              var videos = res.body
-              expect(videos).to.be.an('array')
-              expect(videos.length).to.equal(1)
-              var video = videos[0]
-              expect(video.name).to.equal('my super name for pod 1')
-              expect(video.description).to.equal('my super description for pod 1')
-              expect(video.podUrl).to.equal('http://localhost:9001')
-              expect(video.magnetUri).to.exist
-
-              // All pods should have the same magnet Uri
-              if (base_magnet === null) {
-                base_magnet = video.magnetUri
-              } else {
-                expect(video.magnetUri).to.equal.magnetUri
-              }
-
-              callback()
-            })
-          }, done)
-        }
-      )
-    })
-
-    it('Should upload the video on pod 2 and propagate on each pod', function (done) {
-      this.timeout(15000)
-
-      async.series([
-        function (next) {
-          utils.uploadVideo(urls[1], 'my super name for pod 2', 'my super description for pod 2', 'video_short2.webm', next)
-        },
-        function (next) {
-          setTimeout(next, 11000)
-        }],
-        // All pods should have this video
-        function (err) {
-          if (err) throw err
-
-          async.each(urls, function (url, callback) {
-            var base_magnet = null
-
-            utils.getVideosList(url, function (err, res) {
-              if (err) throw err
-
-              var videos = res.body
-              expect(videos).to.be.an('array')
-              expect(videos.length).to.equal(2)
-              var video = videos[1]
-              expect(video.name).to.equal('my super name for pod 2')
-              expect(video.description).to.equal('my super description for pod 2')
-              expect(video.podUrl).to.equal('http://localhost:9002')
-              expect(video.magnetUri).to.exist
-
-              // All pods should have the same magnet Uri
-              if (base_magnet === null) {
-                base_magnet = video.magnetUri
-              } else {
-                expect(video.magnetUri).to.equal.magnetUri
-              }
-
-              callback()
-            })
-          }, done)
-        }
-      )
-    })
-
-    it('Should upload two videos on pod 3 and propagate on each pod', function (done) {
-      this.timeout(30000)
-
-      async.series([
-        function (next) {
-          utils.uploadVideo(urls[2], 'my super name for pod 3', 'my super description for pod 3', 'video_short3.webm', next)
-        },
-        function (next) {
-          utils.uploadVideo(urls[2], 'my super name for pod 3-2', 'my super description for pod 3-2', 'video_short.webm', next)
-        },
-        function (next) {
-          setTimeout(next, 22000)
-        }],
-        function (err) {
-          if (err) throw err
-
-          var base_magnet = null
-          // All pods should have this video
-          async.each(urls, function (url, callback) {
-            utils.getVideosList(url, function (err, res) {
-              if (err) throw err
-
-              var videos = res.body
-              expect(videos).to.be.an('array')
-              expect(videos.length).to.equal(4)
-              var video = videos[2]
-              expect(video.name).to.equal('my super name for pod 3')
-              expect(video.description).to.equal('my super description for pod 3')
-              expect(video.podUrl).to.equal('http://localhost:9003')
-              expect(video.magnetUri).to.exist
-
-              video = videos[3]
-              expect(video.name).to.equal('my super name for pod 3-2')
-              expect(video.description).to.equal('my super description for pod 3-2')
-              expect(video.podUrl).to.equal('http://localhost:9003')
-              expect(video.magnetUri).to.exist
-
-              // All pods should have the same magnet Uri
-              if (base_magnet === null) {
-                base_magnet = video.magnetUri
-              } else {
-                expect(video.magnetUri).to.equal.magnetUri
-              }
-
-              callback()
-            })
-          }, done)
-        }
-      )
-    })
-  })
-
-  describe('Should seed the uploaded video', function () {
-    it('Should add the file 1 by asking pod 3', function (done) {
-      // Yes, this could be long
-      this.timeout(200000)
-
-      utils.getVideosList(urls[2], function (err, res) {
-        if (err) throw err
-
-        var video = res.body[0]
-        to_remove.push(res.body[2]._id)
-        to_remove.push(res.body[3]._id)
-
-        webtorrent.add(video.magnetUri, function (torrent) {
-          expect(torrent.files).to.exist
-          expect(torrent.files.length).to.equal(1)
-          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
-          done()
-        })
-      })
-    })
-
-    it('Should add the file 2 by asking pod 1', function (done) {
-      // Yes, this could be long
-      this.timeout(200000)
-
-      utils.getVideosList(urls[0], function (err, res) {
-        if (err) throw err
-
-        var video = res.body[1]
-
-        webtorrent.add(video.magnetUri, function (torrent) {
-          expect(torrent.files).to.exist
-          expect(torrent.files.length).to.equal(1)
-          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
-          done()
-        })
-      })
-    })
-
-    it('Should add the file 3 by asking pod 2', function (done) {
-      // Yes, this could be long
-      this.timeout(200000)
-
-      utils.getVideosList(urls[1], function (err, res) {
-        if (err) throw err
-
-        var video = res.body[2]
-
-        webtorrent.add(video.magnetUri, function (torrent) {
-          expect(torrent.files).to.exist
-          expect(torrent.files.length).to.equal(1)
-          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
-          done()
-        })
-      })
-    })
-
-    it('Should add the file 3-2 by asking pod 1', function (done) {
-      // Yes, this could be long
-      this.timeout(200000)
-
-      utils.getVideosList(urls[0], function (err, res) {
-        if (err) throw err
-
-        var video = res.body[3]
-
-        webtorrent.add(video.magnetUri, function (torrent) {
-          expect(torrent.files).to.exist
-          expect(torrent.files.length).to.equal(1)
-          expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
-          done()
-        })
-      })
-    })
-
-    it('Should remove the file 3 and 3-2 by asking pod 3', function (done) {
-      this.timeout(15000)
-
-      async.series([
-        function (next) {
-          utils.removeVideo(urls[2], to_remove[0], next)
-        },
-        function (next) {
-          utils.removeVideo(urls[2], to_remove[1], next)
-        }],
-        function (err) {
-          if (err) throw err
-          setTimeout(done, 11000)
-        }
-      )
-    })
-
-    it('Should have videos 1 and 3 on each pod', function (done) {
-      async.each(urls, function (url, callback) {
-        utils.getVideosList(url, function (err, res) {
-          if (err) throw err
-
-          var videos = res.body
-          expect(videos).to.be.an('array')
-          expect(videos.length).to.equal(2)
-          expect(videos[0]._id).not.to.equal(videos[1]._id)
-          expect(videos[0]._id).not.to.equal(to_remove[0])
-          expect(videos[1]._id).not.to.equal(to_remove[0])
-          expect(videos[0]._id).not.to.equal(to_remove[1])
-          expect(videos[1]._id).not.to.equal(to_remove[1])
-
-          callback()
-        })
-      }, done)
-    })
-  })
-
-  after(function (done) {
-    apps.forEach(function (app) {
-      process.kill(-app.pid)
-    })
-    process.kill(-webtorrent.app.pid)
-
-    // Keep the logs if the test failed
-    if (this.ok) {
-      utils.flushTests(done)
-    } else {
-      done()
-    }
-  })
-})
diff --git a/tests/api/singlePod.js b/tests/api/singlePod.js
deleted file mode 100644
index 3dd72c01b..000000000
--- a/tests/api/singlePod.js
+++ /dev/null
@@ -1,146 +0,0 @@
-'use strict'
-
-var async = require('async')
-var chai = require('chai')
-var expect = chai.expect
-var fs = require('fs')
-var pathUtils = require('path')
-
-var webtorrent = require(pathUtils.join(__dirname, '../../lib/webtorrent'))
-webtorrent.silent = true
-
-var utils = require('./utils')
-
-describe('Test a single pod', function () {
-  var app = null
-  var url = ''
-  var video_id = -1
-
-  before(function (done) {
-    this.timeout(20000)
-
-    async.series([
-      function (next) {
-        utils.flushTests(next)
-      },
-      function (next) {
-        utils.runServer(1, function (app1, url1) {
-          app = app1
-          url = url1
-          next()
-        })
-      },
-      function (next) {
-        webtorrent.create({ host: 'client', port: '1' }, next)
-      }
-    ], done)
-  })
-
-  it('Should not have videos', function (done) {
-    utils.getVideosList(url, function (err, res) {
-      if (err) throw err
-
-      expect(res.body).to.be.an('array')
-      expect(res.body.length).to.equal(0)
-
-      done()
-    })
-  })
-
-  it('Should upload the video', function (done) {
-    this.timeout(5000)
-    utils.uploadVideo(url, 'my super name', 'my super description', 'video_short.webm', done)
-  })
-
-  it('Should seed the uploaded video', function (done) {
-    // Yes, this could be long
-    this.timeout(60000)
-
-    utils.getVideosList(url, function (err, res) {
-      if (err) throw err
-
-      expect(res.body).to.be.an('array')
-      expect(res.body.length).to.equal(1)
-
-      var video = res.body[0]
-      expect(video.name).to.equal('my super name')
-      expect(video.description).to.equal('my super description')
-      expect(video.podUrl).to.equal('http://localhost:9001')
-      expect(video.magnetUri).to.exist
-
-      video_id = video._id
-
-      webtorrent.add(video.magnetUri, function (torrent) {
-        expect(torrent.files).to.exist
-        expect(torrent.files.length).to.equal(1)
-        expect(torrent.files[0].path).to.exist.and.to.not.equal('')
-
-        done()
-      })
-    })
-  })
-
-  it('Should search the video', function (done) {
-    utils.searchVideo(url, 'my', function (err, res) {
-      if (err) throw err
-
-      expect(res.body).to.be.an('array')
-      expect(res.body.length).to.equal(1)
-
-      var video = res.body[0]
-      expect(video.name).to.equal('my super name')
-      expect(video.description).to.equal('my super description')
-      expect(video.podUrl).to.equal('http://localhost:9001')
-      expect(video.magnetUri).to.exist
-
-      done()
-    })
-  })
-
-  it('Should not find a search', function (done) {
-    utils.searchVideo(url, 'hello', function (err, res) {
-      if (err) throw err
-
-      expect(res.body).to.be.an('array')
-      expect(res.body.length).to.equal(0)
-
-      done()
-    })
-  })
-
-  it('Should remove the video', function (done) {
-    utils.removeVideo(url, video_id, function (err) {
-      if (err) throw err
-
-      fs.readdir(pathUtils.join(__dirname, '../../test1/uploads/'), function (err, files) {
-        if (err) throw err
-
-        expect(files.length).to.equal(0)
-        done()
-      })
-    })
-  })
-
-  it('Should not have videos', function (done) {
-    utils.getVideosList(url, function (err, res) {
-      if (err) throw err
-
-      expect(res.body).to.be.an('array')
-      expect(res.body.length).to.equal(0)
-
-      done()
-    })
-  })
-
-  after(function (done) {
-    process.kill(-app.pid)
-    process.kill(-webtorrent.app.pid)
-
-    // Keep the logs if the test failed
-    if (this.ok) {
-      utils.flushTests(done)
-    } else {
-      done()
-    }
-  })
-})
diff --git a/tests/api/utils.js b/tests/api/utils.js
deleted file mode 100644
index 47b706294..000000000
--- a/tests/api/utils.js
+++ /dev/null
@@ -1,185 +0,0 @@
-'use strict'
-
-var child_process = require('child_process')
-var exec = child_process.exec
-var fork = child_process.fork
-var pathUtils = require('path')
-var request = require('supertest')
-
-var testUtils = {
-  flushTests: flushTests,
-  getFriendsList: getFriendsList,
-  getVideosList: getVideosList,
-  makeFriends: makeFriends,
-  quitFriends: quitFriends,
-  removeVideo: removeVideo,
-  flushAndRunMultipleServers: flushAndRunMultipleServers,
-  runServer: runServer,
-  searchVideo: searchVideo,
-  uploadVideo: uploadVideo
-}
-
-// ---------------------- Export functions --------------------
-
-function flushTests (callback) {
-  exec(pathUtils.join(__dirname, '../../scripts/clean_test.sh'), callback)
-}
-
-function getFriendsList (url, end) {
-  var path = '/api/v1/pods/'
-
-  request(url)
-    .get(path)
-    .set('Accept', 'application/json')
-    .expect(200)
-    .expect('Content-Type', /json/)
-    .end(end)
-}
-
-function getVideosList (url, end) {
-  var path = '/api/v1/videos'
-
-  request(url)
-    .get(path)
-    .set('Accept', 'application/json')
-    .expect(200)
-    .expect('Content-Type', /json/)
-    .end(end)
-}
-
-function makeFriends (url, expected_status, callback) {
-  if (!callback) {
-    callback = expected_status
-    expected_status = 204
-  }
-
-  var path = '/api/v1/pods/makefriends'
-
-  // The first pod make friend with the third
-  request(url)
-    .get(path)
-    .set('Accept', 'application/json')
-    .expect(expected_status)
-    .end(function (err, res) {
-      if (err) throw err
-
-      // Wait for the request between pods
-      setTimeout(callback, 1000)
-    })
-}
-
-function quitFriends (url, callback) {
-  var path = '/api/v1/pods/quitfriends'
-
-  // The first pod make friend with the third
-  request(url)
-    .get(path)
-    .set('Accept', 'application/json')
-    .expect(204)
-    .end(function (err, res) {
-      if (err) throw err
-
-      // Wait for the request between pods
-      setTimeout(callback, 1000)
-    })
-}
-
-function removeVideo (url, id, end) {
-  var path = '/api/v1/videos'
-
-  request(url)
-    .delete(path + '/' + id)
-    .set('Accept', 'application/json')
-    .expect(204)
-    .end(end)
-}
-
-function flushAndRunMultipleServers (total_servers, serversRun) {
-  var apps = []
-  var urls = []
-  var i = 0
-
-  function anotherServerDone (number, app, url) {
-    apps[number - 1] = app
-    urls[number - 1] = url
-    i++
-    if (i === total_servers) {
-      serversRun(apps, urls)
-    }
-  }
-
-  flushTests(function () {
-    for (var j = 1; j <= total_servers; j++) {
-      (function (k) { // TODO: ES6 with let
-        // For the virtual buffer
-        setTimeout(function () {
-          runServer(k, function (app, url) {
-            anotherServerDone(k, app, url)
-          })
-        }, 1000 * k)
-      })(j)
-    }
-  })
-}
-
-function runServer (number, callback) {
-  var port = 9000 + number
-  var server_run_string = {
-    'Connected to mongodb': false,
-    'Server listening on port': false
-  }
-
-  // Share the environment
-  var env = Object.create(process.env)
-  env.NODE_ENV = 'test'
-  env.NODE_APP_INSTANCE = number
-  var options = {
-    silent: true,
-    env: env,
-    detached: true
-  }
-
-  var app = fork(pathUtils.join(__dirname, '../../server.js'), [], options)
-  app.stdout.on('data', function onStdout (data) {
-    var dont_continue = false
-    // Check if all required sentences are here
-    for (var key of Object.keys(server_run_string)) {
-      if (data.toString().indexOf(key) !== -1) server_run_string[key] = true
-      if (server_run_string[key] === false) dont_continue = true
-    }
-
-    // If no, there is maybe one thing not already initialized (mongodb...)
-    if (dont_continue === true) return
-
-    app.stdout.removeListener('data', onStdout)
-    callback(app, 'http://localhost:' + port)
-  })
-}
-
-function searchVideo (url, search, end) {
-  var path = '/api/v1/videos'
-
-  request(url)
-    .get(path + '/search/' + search)
-    .set('Accept', 'application/json')
-    .expect(200)
-    .expect('Content-Type', /json/)
-    .end(end)
-}
-
-function uploadVideo (url, name, description, fixture, end) {
-  var path = '/api/v1/videos'
-
-  request(url)
-    .post(path)
-    .set('Accept', 'application/json')
-    .field('name', name)
-    .field('description', description)
-    .attach('input_video', pathUtils.join(__dirname, 'fixtures', fixture))
-    .expect(201)
-    .end(end)
-}
-
-// ---------------------------------------------------------------------------
-
-module.exports = testUtils
diff --git a/tests/index.js b/tests/index.js
deleted file mode 100644
index ccebbfe51..000000000
--- a/tests/index.js
+++ /dev/null
@@ -1,6 +0,0 @@
-;(function () {
-  'use strict'
-
-  // Order of the tests we want to execute
-  require('./api/')
-})()
diff --git a/views/error.jade b/views/error.jade
deleted file mode 100644
index d9e8a7893..000000000
--- a/views/error.jade
+++ /dev/null
@@ -1,8 +0,0 @@
-extends layout
-
-block content
-  h1= message
-  if error
-    h2= error.status
-    pre #{error.stack}
-    
diff --git a/views/footer.jade b/views/footer.jade
deleted file mode 100644
index c7754f584..000000000
--- a/views/footer.jade
+++ /dev/null
@@ -1,3 +0,0 @@
-footer
-
-  | PeerTube, CopyLeft 2015
diff --git a/views/header.jade b/views/header.jade
deleted file mode 100644
index 6b84333f9..000000000
--- a/views/header.jade
+++ /dev/null
@@ -1,9 +0,0 @@
-.navbar.navbar-default
-  .container-fluid
-    .navbar-header
-      a.navbar-brand(href='/') PeerTube
-    .navbar-header
-      form.navbar-form(role='search')
-        .form-group.search-group
-          input.form-control#search-video(type='text' name='search_video' placeholder='Search a video...')
-          a.search-btn.btn.btn-link.glyphicon.glyphicon-search(type='submit')
\ No newline at end of file
diff --git a/views/index.jade b/views/index.jade
deleted file mode 100644
index a49758fc0..000000000
--- a/views/index.jade
+++ /dev/null
@@ -1,15 +0,0 @@
-extends layout
-
-block content
-  div(class='container')
-    div(class='row')
-      include panel
-
-      div(id='ajax_load' class='col-md-9')
-
-
-    include footer
-
-  // build:js /javascripts/global.min.js
-  script(src='/javascripts/bundle.js')
-  // endbuild
diff --git a/views/layout.jade b/views/layout.jade
deleted file mode 100644
index 43c28cd4f..000000000
--- a/views/layout.jade
+++ /dev/null
@@ -1,15 +0,0 @@
-doctype html
-html
-  head
-    link(rel='icon' type='image/png' href='/images/favicon.png')
-    meta(name='viewport' content='width=device-width, initial-scale=1')
-    title= title
-
-    // build:css /stylesheets/global.min.css
-    each file in [ 'global' ]
-      link(rel='stylesheet', href='/stylesheets/' + file + '.css')
-    // endbuild
-
-  body
-    include header
-    block content
diff --git a/views/panel.jade b/views/panel.jade
deleted file mode 100644
index 0d124fb7e..000000000
--- a/views/panel.jade
+++ /dev/null
@@ -1,17 +0,0 @@
-menu(class='col-md-2')
-
-  div(id='panel_get_videos' class='panel_button')
-    span(class='glyphicon glyphicon-list')
-    | Get videos
-
-  div(id='panel_upload_video' class='panel_button')
-    span(class='glyphicon glyphicon-cloud-upload')
-    | Upload a video
-
-  div(id='panel_make_friends' class='panel_button')
-    span(class='glyphicon glyphicon-user')
-    | Make friends
-
-  div(id='panel_quit_friends' class='panel_button')
-    span(class='glyphicon glyphicon-plane')
-    | Quit friends
-- 
cgit v1.2.3