From 1b30b0cf53792464a3c20898b5b5f04a260ad1ac Mon Sep 17 00:00:00 2001
From: Kelvin Zhang <me@iamkelv.in>
Date: Mon, 31 Aug 2015 15:30:02 +0100
Subject: [PATCH] Add controllers

---
 app/Http/Controllers/AccountController.php    |  18 ++
 app/Http/Controllers/AlbumsController.php     |  65 ++++++
 .../Api/Mobile/TracksController.php           |  44 ++++
 .../Controllers/Api/V1/TracksController.php   |  88 ++++++++
 .../Controllers/Api/Web/AccountController.php |  33 +++
 .../Controllers/Api/Web/AlbumsController.php  | 146 +++++++++++++
 .../Controllers/Api/Web/ArtistsController.php | 196 ++++++++++++++++++
 .../Controllers/Api/Web/AuthController.php    |  13 ++
 .../Api/Web/CommentsController.php            |  49 +++++
 .../Api/Web/DashboardController.php           |  46 ++++
 .../Api/Web/FavouritesController.php          | 109 ++++++++++
 .../Controllers/Api/Web/FollowController.php  |  14 ++
 .../Controllers/Api/Web/ImagesController.php  |  30 +++
 .../Api/Web/PlaylistsController.php           | 142 +++++++++++++
 .../Api/Web/ProfilerController.php            |  29 +++
 .../Api/Web/TaxonomiesController.php          |  25 +++
 .../Controllers/Api/Web/TracksController.php  | 158 ++++++++++++++
 app/Http/Controllers/ApiControllerBase.php    |  33 +++
 app/Http/Controllers/ArtistsController.php    |  32 +++
 app/Http/Controllers/Auth/AuthController.php  |  65 ------
 .../Controllers/Auth/PasswordController.php   |  32 ---
 app/Http/Controllers/AuthController.php       | 107 ++++++++++
 app/Http/Controllers/ContentController.php    |  21 ++
 app/Http/Controllers/FavouritesController.php |  21 ++
 app/Http/Controllers/HomeController.php       |  11 +
 app/Http/Controllers/ImagesController.php     |  48 +++++
 app/Http/Controllers/PlaylistsController.php  |  66 ++++++
 app/Http/Controllers/TracksController.php     | 147 +++++++++++++
 app/Http/Controllers/UploaderController.php   |  11 +
 app/Http/Controllers/UsersController.php      |  23 ++
 30 files changed, 1725 insertions(+), 97 deletions(-)
 create mode 100644 app/Http/Controllers/AccountController.php
 create mode 100644 app/Http/Controllers/AlbumsController.php
 create mode 100644 app/Http/Controllers/Api/Mobile/TracksController.php
 create mode 100644 app/Http/Controllers/Api/V1/TracksController.php
 create mode 100644 app/Http/Controllers/Api/Web/AccountController.php
 create mode 100644 app/Http/Controllers/Api/Web/AlbumsController.php
 create mode 100644 app/Http/Controllers/Api/Web/ArtistsController.php
 create mode 100644 app/Http/Controllers/Api/Web/AuthController.php
 create mode 100644 app/Http/Controllers/Api/Web/CommentsController.php
 create mode 100644 app/Http/Controllers/Api/Web/DashboardController.php
 create mode 100644 app/Http/Controllers/Api/Web/FavouritesController.php
 create mode 100644 app/Http/Controllers/Api/Web/FollowController.php
 create mode 100644 app/Http/Controllers/Api/Web/ImagesController.php
 create mode 100644 app/Http/Controllers/Api/Web/PlaylistsController.php
 create mode 100644 app/Http/Controllers/Api/Web/ProfilerController.php
 create mode 100644 app/Http/Controllers/Api/Web/TaxonomiesController.php
 create mode 100644 app/Http/Controllers/Api/Web/TracksController.php
 create mode 100644 app/Http/Controllers/ApiControllerBase.php
 create mode 100644 app/Http/Controllers/ArtistsController.php
 delete mode 100644 app/Http/Controllers/Auth/AuthController.php
 delete mode 100644 app/Http/Controllers/Auth/PasswordController.php
 create mode 100644 app/Http/Controllers/AuthController.php
 create mode 100644 app/Http/Controllers/ContentController.php
 create mode 100644 app/Http/Controllers/FavouritesController.php
 create mode 100644 app/Http/Controllers/HomeController.php
 create mode 100644 app/Http/Controllers/ImagesController.php
 create mode 100644 app/Http/Controllers/PlaylistsController.php
 create mode 100644 app/Http/Controllers/TracksController.php
 create mode 100644 app/Http/Controllers/UploaderController.php
 create mode 100644 app/Http/Controllers/UsersController.php

diff --git a/app/Http/Controllers/AccountController.php b/app/Http/Controllers/AccountController.php
new file mode 100644
index 00000000..0c97a3d5
--- /dev/null
+++ b/app/Http/Controllers/AccountController.php
@@ -0,0 +1,18 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\Redirect;
+
+class AccountController extends Controller
+{
+    public function getIndex()
+    {
+        return View::make('shared.null');
+    }
+
+    public function getRegister()
+    {
+        return Redirect::to(Config::get('poniverse.urls')['register']);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/AlbumsController.php b/app/Http/Controllers/AlbumsController.php
new file mode 100644
index 00000000..7622961c
--- /dev/null
+++ b/app/Http/Controllers/AlbumsController.php
@@ -0,0 +1,65 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use App\Album;
+use App\ResourceLogItem;
+use App\Track;
+
+class AlbumsController extends Controller
+{
+    public function getIndex()
+    {
+        return View::make('albums.index');
+    }
+
+    public function getShow($id, $slug)
+    {
+        $album = Album::find($id);
+        if (!$album) {
+            App::abort(404);
+        }
+
+        if ($album->slug != $slug) {
+            return Redirect::action('AlbumsController@getAlbum', [$id, $album->slug]);
+        }
+
+        return View::make('albums.show');
+    }
+
+    public function getShortlink($id)
+    {
+        $album = Album::find($id);
+        if (!$album) {
+            App::abort(404);
+        }
+
+        return Redirect::action('AlbumsController@getTrack', [$id, $album->slug]);
+    }
+
+    public function getDownload($id, $extension)
+    {
+        $album = Album::with('tracks', 'user')->find($id);
+        if (!$album) {
+            App::abort(404);
+        }
+
+        $format = null;
+        $formatName = null;
+
+        foreach (Track::$Formats as $name => $item) {
+            if ($item['extension'] == $extension) {
+                $format = $item;
+                $formatName = $name;
+                break;
+            }
+        }
+
+        if ($format == null) {
+            App::abort(404);
+        }
+
+        ResourceLogItem::logItem('album', $id, ResourceLogItem::DOWNLOAD, $format['index']);
+        $downloader = new AlbumDownloader($album, $formatName);
+        $downloader->download();
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Mobile/TracksController.php b/app/Http/Controllers/Api/Mobile/TracksController.php
new file mode 100644
index 00000000..687a348f
--- /dev/null
+++ b/app/Http/Controllers/Api/Mobile/TracksController.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace Api\Mobile;
+
+use App\Http\Controllers\Controller;
+use App\Track;
+use Response;
+
+class TracksController extends Controller
+{
+    public function latest()
+    {
+        $tracks = Track::summary()
+            ->userDetails()
+            ->listed()
+            ->explicitFilter()
+            ->published()
+            ->with('user', 'genre', 'cover', 'album', 'album.user')->take(10);
+
+        $json = [
+            'total_tracks' => $tracks->count(),
+            'tracks' => $tracks->toArray()
+        ];
+
+        return Response::json($json, 200);
+    }
+
+    public function popular()
+    {
+        $tracks = Track::popular(10)
+            ->userDetails()
+            ->listed()
+            ->explicitFilter()
+            ->published()
+            ->with('user', 'genre', 'cover', 'album', 'album.user')->take(10);
+
+        $json = [
+            'total_tracks' => $tracks->count(),
+            'tracks' => $tracks->toArray()
+        ];
+
+        return Response::json($json, 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/V1/TracksController.php b/app/Http/Controllers/Api/V1/TracksController.php
new file mode 100644
index 00000000..9b15b9f3
--- /dev/null
+++ b/app/Http/Controllers/Api/V1/TracksController.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Api\V1;
+
+use App\Image;
+use App\Track;
+use Cover;
+use Illuminate\Support\Facades\Response;
+
+class TracksController extends \ApiControllerBase
+{
+    public function getTrackRadioDetails($hash)
+    {
+        $track = Track
+            ::with('user', 'album', 'user.avatar', 'cover', 'comments', 'genre')
+            ->published()
+            ->whereHash($hash)->first();
+
+        if (!$track) {
+            return Response::json(['message' => 'Track not found.'], 403);
+        }
+
+        $comments = [];
+        foreach ($track->comments as $comment) {
+            $comments[] = [
+                'id' => $comment->id,
+                'created_at' => $comment->created_at,
+                'content' => $comment->content,
+                'user' => [
+                    'name' => $comment->user->display_name,
+                    'id' => $comment->user->id,
+                    'url' => $comment->user->url,
+                    'avatars' => [
+                        'normal' => $comment->user->getAvatarUrl(Image::NORMAL),
+                        'thumbnail' => $comment->user->getAvatarUrl(Image::THUMBNAIL),
+                        'small' => $comment->user->getAvatarUrl(Image::SMALL),
+                    ]
+                ]
+            ];
+        }
+
+        return Response::json([
+            'id' => $track->id,
+            'title' => $track->title,
+            'description' => $track->description,
+            'lyrics' => $track->lyrics,
+            'user' => [
+                'id' => $track->user->id,
+                'name' => $track->user->display_name,
+                'url' => $track->user->url,
+                'avatars' => [
+                    'thumbnail' => $track->user->getAvatarUrl(Image::THUMBNAIL),
+                    'small' => $track->user->getAvatarUrl(Image::SMALL),
+                    'normal' => $track->user->getAvatarUrl(Image::NORMAL)
+                ]
+            ],
+            'stats' => [
+                'views' => $track->view_count,
+                'plays' => $track->play_count,
+                'downloads' => $track->download_count,
+                'comments' => $track->comment_count,
+                'favourites' => $track->favourite_count
+            ],
+            'url' => $track->url,
+            'is_vocal' => !!$track->is_vocal,
+            'is_explicit' => !!$track->is_explicit,
+            'is_downloadable' => !!$track->is_downloadable,
+            'published_at' => $track->published_at,
+            'duration' => $track->duration,
+            'genre' => $track->genre != null
+                ?
+                [
+                    'id' => $track->genre->id,
+                    'name' => $track->genre->name
+                ] : null,
+            'type' => [
+                'id' => $track->track_type->id,
+                'name' => $track->track_type->title
+            ],
+            'covers' => [
+                'thumbnail' => $track->getCoverUrl(Image::THUMBNAIL),
+                'small' => $track->getCoverUrl(Image::SMALL),
+                'normal' => $track->getCoverUrl(Image::NORMAL)
+            ],
+            'comments' => $comments
+        ], 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/AccountController.php b/app/Http/Controllers/Api/Web/AccountController.php
new file mode 100644
index 00000000..579e03c0
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/AccountController.php
@@ -0,0 +1,33 @@
+<?php
+
+namespace Api\Web;
+
+use App\Commands\SaveAccountSettingsCommand;
+use Cover;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class AccountController extends \ApiControllerBase
+{
+    public function getSettings()
+    {
+        $user = Auth::user();
+
+        return Response::json([
+            'bio' => $user->bio,
+            'can_see_explicit_content' => $user->can_see_explicit_content == 1,
+            'display_name' => $user->display_name,
+            'sync_names' => $user->sync_names == 1,
+            'mlpforums_name' => $user->mlpforums_name,
+            'gravatar' => $user->gravatar ? $user->gravatar : $user->email,
+            'avatar_url' => !$user->uses_gravatar ? $user->getAvatarUrl() : null,
+            'uses_gravatar' => $user->uses_gravatar == 1
+        ], 200);
+    }
+
+    public function postSave()
+    {
+        return $this->execute(new SaveAccountSettingsCommand(Input::all()));
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/AlbumsController.php b/app/Http/Controllers/Api/Web/AlbumsController.php
new file mode 100644
index 00000000..e86279cd
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/AlbumsController.php
@@ -0,0 +1,146 @@
+<?php
+
+namespace Api\Web;
+
+use App\Album;
+use App\Commands\CreateAlbumCommand;
+use App\Commands\DeleteAlbumCommand;
+use App\Commands\EditAlbumCommand;
+use App\Image;
+use App\ResourceLogItem;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class AlbumsController extends \ApiControllerBase
+{
+    public function postCreate()
+    {
+        return $this->execute(new CreateAlbumCommand(Input::all()));
+    }
+
+    public function postEdit($id)
+    {
+        return $this->execute(new EditAlbumCommand($id, Input::all()));
+    }
+
+    public function postDelete($id)
+    {
+        return $this->execute(new DeleteAlbumCommand($id));
+    }
+
+    public function getShow($id)
+    {
+        $album = Album::with([
+            'tracks' => function ($query) {
+                $query->userDetails();
+            },
+            'tracks.cover',
+            'tracks.genre',
+            'tracks.user',
+            'user',
+            'comments',
+            'comments.user'
+        ])
+            ->userDetails()
+            ->find($id);
+
+        if (!$album) {
+            App::abort(404);
+        }
+
+        if (Input::get('log')) {
+            ResourceLogItem::logItem('album', $id, ResourceLogItem::VIEW);
+            $album->view_count++;
+        }
+
+        $returned_album = Album::mapPublicAlbumShow($album);
+        if ($returned_album['is_downloadable'] == 0) {
+            unset($returned_album['formats']);
+        }
+
+        return Response::json([
+            'album' => $returned_album
+        ], 200);
+    }
+
+    public function getIndex()
+    {
+        $page = 1;
+        if (Input::has('page')) {
+            $page = Input::get('page');
+        }
+
+        $query = Album::summary()
+            ->with('user', 'user.avatar', 'cover')
+            ->userDetails()
+            ->orderBy('created_at', 'desc')
+            ->where('track_count', '>', 0);
+
+        $count = $query->count();
+        $perPage = 40;
+
+        $query->skip(($page - 1) * $perPage)->take($perPage);
+        $albums = [];
+
+        foreach ($query->get() as $album) {
+            $albums[] = Album::mapPublicAlbumSummary($album);
+        }
+
+        return Response::json(["albums" => $albums, "current_page" => $page, "total_pages" => ceil($count / $perPage)],
+            200);
+    }
+
+    public function getOwned()
+    {
+        $query = Album::summary()->where('user_id', \Auth::user()->id)->orderBy('created_at', 'desc')->get();
+        $albums = [];
+        foreach ($query as $album) {
+            $albums[] = [
+                'id' => $album->id,
+                'title' => $album->title,
+                'slug' => $album->slug,
+                'created_at' => $album->created_at,
+                'covers' => [
+                    'small' => $album->getCoverUrl(Image::SMALL),
+                    'normal' => $album->getCoverUrl(Image::NORMAL)
+                ]
+            ];
+        }
+
+        return Response::json($albums, 200);
+    }
+
+    public function getEdit($id)
+    {
+        $album = Album::with('tracks')->find($id);
+        if (!$album) {
+            return $this->notFound('Album ' . $id . ' not found!');
+        }
+
+        if ($album->user_id != Auth::user()->id) {
+            return $this->notAuthorized();
+        }
+
+        $tracks = [];
+        foreach ($album->tracks as $track) {
+            $tracks[] = [
+                'id' => $track->id,
+                'title' => $track->title
+            ];
+        }
+
+        return Response::json([
+            'id' => $album->id,
+            'title' => $album->title,
+            'user_id' => $album->user_id,
+            'slug' => $album->slug,
+            'created_at' => $album->created_at,
+            'published_at' => $album->published_at,
+            'description' => $album->description,
+            'cover_url' => $album->hasCover() ? $album->getCoverUrl(Image::NORMAL) : null,
+            'real_cover_url' => $album->getCoverUrl(Image::NORMAL),
+            'tracks' => $tracks
+        ], 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/ArtistsController.php b/app/Http/Controllers/Api/Web/ArtistsController.php
new file mode 100644
index 00000000..8908cc0d
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/ArtistsController.php
@@ -0,0 +1,196 @@
+<?php
+
+namespace Api\Web;
+
+use App\Album;
+use App\Comment;
+use App\Favourite;
+use App\Image;
+use App\Track;
+use App\User;
+use Cover;
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class ArtistsController extends \ApiControllerBase
+{
+    public function getFavourites($slug)
+    {
+        $user = User::whereSlug($slug)->first();
+        if (!$user) {
+            App::abort(404);
+        }
+
+        $favs = Favourite::whereUserId($user->id)->with([
+            'track.genre',
+            'track.cover',
+            'track.user',
+            'album.cover',
+            'album.user',
+            'track' => function ($query) {
+                $query->userDetails();
+            },
+            'album' => function ($query) {
+                $query->userDetails();
+            }
+        ])->get();
+
+        $tracks = [];
+        $albums = [];
+
+        foreach ($favs as $fav) {
+            if ($fav->type == 'App\Track') {
+                $tracks[] = Track::mapPublicTrackSummary($fav->track);
+            } else {
+                if ($fav->type == 'App\Album') {
+                    $albums[] = Album::mapPublicAlbumSummary($fav->album);
+                }
+            }
+        }
+
+        return Response::json([
+            'tracks' => $tracks,
+            'albums' => $albums
+        ], 200);
+    }
+
+    public function getContent($slug)
+    {
+        $user = User::whereSlug($slug)->first();
+        if (!$user) {
+            App::abort(404);
+        }
+
+        $query = Track::summary()->published()->listed()->explicitFilter()->with('genre', 'cover',
+            'user')->userDetails()->whereUserId($user->id)->whereNotNull('published_at');
+        $tracks = [];
+        $singles = [];
+
+        foreach ($query->get() as $track) {
+            if ($track->album_id != null) {
+                $tracks[] = Track::mapPublicTrackSummary($track);
+            } else {
+                $singles[] = Track::mapPublicTrackSummary($track);
+            }
+        }
+
+        $query = Album::summary()
+            ->with('user')
+            ->orderBy('created_at', 'desc')
+            ->where('track_count', '>', 0)
+            ->whereUserId($user->id);
+
+        $albums = [];
+
+        foreach ($query->get() as $album) {
+            $albums[] = Album::mapPublicAlbumSummary($album);
+        }
+
+        return Response::json(['singles' => $singles, 'albumTracks' => $tracks, 'albums' => $albums], 200);
+    }
+
+    public function getShow($slug)
+    {
+        $user = User::whereSlug($slug)
+            ->userDetails()
+            ->with([
+                'comments' => function ($query) {
+                    $query->with('user');
+                }
+            ])
+            ->first();
+        if (!$user) {
+            App::abort(404);
+        }
+
+        $trackQuery = Track::summary()
+            ->published()
+            ->explicitFilter()
+            ->listed()
+            ->with('genre', 'cover', 'user')
+            ->userDetails()
+            ->whereUserId($user->id)
+            ->whereNotNull('published_at')
+            ->orderBy('created_at', 'desc')
+            ->take(20);
+
+        $latestTracks = [];
+        foreach ($trackQuery->get() as $track) {
+            $latestTracks[] = Track::mapPublicTrackSummary($track);
+        }
+
+        $comments = [];
+        foreach ($user->comments as $comment) {
+            $comments[] = Comment::mapPublic($comment);
+        }
+
+        $userData = [
+            'is_following' => false
+        ];
+
+        if ($user->users->count()) {
+            $userRow = $user->users[0];
+            $userData = [
+                'is_following' => $userRow->is_followed
+            ];
+        }
+
+        return Response::json([
+            'artist' => [
+                'id' => $user->id,
+                'name' => $user->display_name,
+                'slug' => $user->slug,
+                'is_archived' => $user->is_archived,
+                'avatars' => [
+                    'small' => $user->getAvatarUrl(Image::SMALL),
+                    'normal' => $user->getAvatarUrl(Image::NORMAL)
+                ],
+                'created_at' => $user->created_at,
+                'followers' => [],
+                'following' => [],
+                'latest_tracks' => $latestTracks,
+                'comments' => $comments,
+                'bio' => $user->bio,
+                'mlpforums_username' => $user->mlpforums_name,
+                'message_url' => $user->message_url,
+                'user_data' => $userData
+            ]
+        ], 200);
+    }
+
+    public function getIndex()
+    {
+        $page = 1;
+        if (Input::has('page')) {
+            $page = Input::get('page');
+        }
+
+        $query = User::orderBy('created_at', 'desc')
+            ->where('track_count', '>', 0);
+
+        $count = $query->count();
+        $perPage = 40;
+
+        $query->skip(($page - 1) * $perPage)->take($perPage);
+        $users = [];
+
+        foreach ($query->get() as $user) {
+            $users[] = [
+                'id' => $user->id,
+                'name' => $user->display_name,
+                'slug' => $user->slug,
+                'url' => $user->url,
+                'is_archived' => $user->is_archived,
+                'avatars' => [
+                    'small' => $user->getAvatarUrl(Image::SMALL),
+                    'normal' => $user->getAvatarUrl(Image::NORMAL)
+                ],
+                'created_at' => $user->created_at
+            ];
+        }
+
+        return Response::json(["artists" => $users, "current_page" => $page, "total_pages" => ceil($count / $perPage)],
+            200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/AuthController.php b/app/Http/Controllers/Api/Web/AuthController.php
new file mode 100644
index 00000000..c4242377
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/AuthController.php
@@ -0,0 +1,13 @@
+<?php
+
+namespace Api\Web;
+
+use App\Http\Controllers\Controller;
+
+class AuthController extends Controller
+{
+    public function postLogout()
+    {
+        \Auth::logout();
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/CommentsController.php b/app/Http/Controllers/Api/Web/CommentsController.php
new file mode 100644
index 00000000..9d42ad51
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/CommentsController.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Api\Web;
+
+use App;
+use App\Commands\CreateCommentCommand;
+use App\Comment;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class CommentsController extends \ApiControllerBase
+{
+    public function postCreate($type, $id)
+    {
+        return $this->execute(new CreateCommentCommand($type, $id, Input::all()));
+    }
+
+    public function getIndex($type, $id)
+    {
+        $column = '';
+
+        if ($type == 'track') {
+            $column = 'track_id';
+        } else {
+            if ($type == 'user') {
+                $column = 'profile_id';
+            } else {
+                if ($type == 'album') {
+                    $column = 'album_id';
+                } else {
+                    if ($type == 'playlist') {
+                        $column = 'playlist_id';
+                    } else {
+                        App::abort(500);
+                    }
+                }
+            }
+        }
+
+        $query = Comment::where($column, '=', $id)->orderBy('created_at', 'desc')->with('user');
+        $comments = [];
+
+        foreach ($query->get() as $comment) {
+            $comments[] = Comment::mapPublic($comment);
+        }
+
+        return Response::json(['list' => $comments, 'count' => count($comments)]);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/DashboardController.php b/app/Http/Controllers/Api/Web/DashboardController.php
new file mode 100644
index 00000000..3dc57d65
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/DashboardController.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace Api\Web;
+
+use App\News;
+use App\Track;
+use Cover;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class DashboardController extends \ApiControllerBase
+{
+    public function getIndex()
+    {
+        $recentQuery = Track::summary()
+            ->with(['genre', 'user', 'cover', 'user.avatar'])
+            ->whereIsLatest(true)
+            ->listed()
+            ->userDetails()
+            ->explicitFilter()
+            ->published()
+            ->orderBy('published_at', 'desc')
+            ->take(30);
+
+        $recentTracks = [];
+
+        foreach ($recentQuery->get() as $track) {
+            $recentTracks[] = Track::mapPublicTrackSummary($track);
+        }
+
+        return Response::json([
+            'recent_tracks' => $recentTracks,
+            'popular_tracks' => Track::popular(30, Auth::check() && Auth::user()->can_see_explicit_content),
+            'news' => News::getNews(0, 10)
+        ], 200);
+    }
+
+    public function postReadNews()
+    {
+        News::markPostAsRead(Input::get('url'));
+
+        return Response::json([
+        ]);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/FavouritesController.php b/app/Http/Controllers/Api/Web/FavouritesController.php
new file mode 100644
index 00000000..e855c3a4
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/FavouritesController.php
@@ -0,0 +1,109 @@
+<?php
+
+namespace Api\Web;
+
+use App\Album;
+use App\Commands\ToggleFavouriteCommand;
+use App\Favourite;
+use App\Playlist;
+use App\Track;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class FavouritesController extends \ApiControllerBase
+{
+    public function postToggle()
+    {
+        return $this->execute(new ToggleFavouriteCommand(Input::get('type'), Input::get('id')));
+    }
+
+    public function getTracks()
+    {
+        $query = Favourite
+            ::whereUserId(Auth::user()->id)
+            ->whereNotNull('track_id')
+            ->with([
+                'track' => function ($query) {
+                    $query
+                        ->userDetails()
+                        ->published();
+                },
+                'track.user',
+                'track.genre',
+                'track.cover',
+                'track.album',
+                'track.album.user'
+            ]);
+
+        $tracks = [];
+
+        foreach ($query->get() as $fav) {
+            if ($fav->track == null) // deleted track
+            {
+                continue;
+            }
+
+            $tracks[] = Track::mapPublicTrackSummary($fav->track);
+        }
+
+        return Response::json(["tracks" => $tracks], 200);
+    }
+
+    public function getAlbums()
+    {
+        $query = Favourite
+            ::whereUserId(Auth::user()->id)
+            ->whereNotNull('album_id')
+            ->with([
+                'album' => function ($query) {
+                    $query->userDetails();
+                },
+                'album.user',
+                'album.user.avatar',
+                'album.cover'
+            ]);
+
+        $albums = [];
+
+        foreach ($query->get() as $fav) {
+            if ($fav->album == null) // deleted album
+            {
+                continue;
+            }
+
+            $albums[] = Album::mapPublicAlbumSummary($fav->album);
+        }
+
+        return Response::json(["albums" => $albums], 200);
+    }
+
+    public function getPlaylists()
+    {
+        $query = Favourite
+            ::whereUserId(Auth::user()->id)
+            ->whereNotNull('playlist_id')
+            ->with([
+                'playlist' => function ($query) {
+                    $query->userDetails();
+                },
+                'playlist.user',
+                'playlist.user.avatar',
+                'playlist.tracks',
+                'playlist.tracks.cover'
+            ]);
+
+        $playlists = [];
+
+        foreach ($query->get() as $fav) {
+            if ($fav->playlist == null) // deleted playlist
+            {
+                continue;
+            }
+
+            $playlists[] = Playlist::mapPublicPlaylistSummary($fav->playlist);
+        }
+
+        return Response::json(["playlists" => $playlists], 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/FollowController.php b/app/Http/Controllers/Api/Web/FollowController.php
new file mode 100644
index 00000000..133b2122
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/FollowController.php
@@ -0,0 +1,14 @@
+<?php
+
+namespace Api\Web;
+
+use App\Commands\ToggleFollowingCommand;
+use Illuminate\Support\Facades\Input;
+
+class FollowController extends \ApiControllerBase
+{
+    public function postToggle()
+    {
+        return $this->execute(new ToggleFollowingCommand(Input::get('type'), Input::get('id')));
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/ImagesController.php b/app/Http/Controllers/Api/Web/ImagesController.php
new file mode 100644
index 00000000..8bdb5050
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/ImagesController.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Api\Web;
+
+use App\Image;
+use Cover;
+use Illuminate\Support\Facades\Response;
+
+class ImagesController extends \ApiControllerBase
+{
+    public function getOwned()
+    {
+        $query = Image::where('uploaded_by', \Auth::user()->id);
+        $images = [];
+        foreach ($query->get() as $image) {
+            $images[] = [
+                'id' => $image->id,
+                'urls' => [
+                    'small' => $image->getUrl(Image::SMALL),
+                    'normal' => $image->getUrl(Image::NORMAL),
+                    'thumbnail' => $image->getUrl(Image::THUMBNAIL),
+                    'original' => $image->getUrl(Image::ORIGINAL)
+                ],
+                'filename' => $image->filename
+            ];
+        }
+
+        return Response::json($images, 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/PlaylistsController.php b/app/Http/Controllers/Api/Web/PlaylistsController.php
new file mode 100644
index 00000000..9c47d62c
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/PlaylistsController.php
@@ -0,0 +1,142 @@
+<?php
+
+namespace Api\Web;
+
+use App\Commands\AddTrackToPlaylistCommand;
+use App\Commands\CreatePlaylistCommand;
+use App\Commands\DeletePlaylistCommand;
+use App\Commands\EditPlaylistCommand;
+use App\Image;
+use App\Playlist;
+use App\ResourceLogItem;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class PlaylistsController extends \ApiControllerBase
+{
+    public function postCreate()
+    {
+        return $this->execute(new CreatePlaylistCommand(Input::all()));
+    }
+
+    public function postEdit($id)
+    {
+        return $this->execute(new EditPlaylistCommand($id, Input::all()));
+    }
+
+    public function postDelete($id)
+    {
+        return $this->execute(new DeletePlaylistCommand($id, Input::all()));
+    }
+
+    public function postAddTrack($id)
+    {
+        return $this->execute(new AddTrackToPlaylistCommand($id, Input::get('track_id')));
+    }
+
+    public function getIndex()
+    {
+        $page = 1;
+        if (Input::has('page')) {
+            $page = Input::get('page');
+        }
+
+        $query = Playlist::summary()
+            ->with('user', 'user.avatar', 'tracks', 'tracks.cover', 'tracks.user', 'tracks.album', 'tracks.album.user')
+            ->userDetails()
+            ->orderBy('created_at', 'desc')
+            ->where('track_count', '>', 0)
+            ->whereIsPublic(true);
+
+        $count = $query->count();
+        $perPage = 40;
+
+        $query->skip(($page - 1) * $perPage)->take($perPage);
+        $playlists = [];
+
+        foreach ($query->get() as $playlist) {
+            $playlists[] = Playlist::mapPublicPlaylistSummary($playlist);
+        }
+
+        return Response::json([
+            "playlists" => $playlists,
+            "current_page" => $page,
+            "total_pages" => ceil($count / $perPage)
+        ], 200);
+    }
+
+    public function getShow($id)
+    {
+        $playlist = Playlist::with([
+            'tracks.user',
+            'tracks.genre',
+            'tracks.cover',
+            'tracks.album',
+            'tracks' => function ($query) {
+                $query->userDetails();
+            },
+            'comments',
+            'comments.user'
+        ])->userDetails()->find($id);
+        if (!$playlist || !$playlist->canView(Auth::user())) {
+            App::abort('404');
+        }
+
+        if (Input::get('log')) {
+            ResourceLogItem::logItem('playlist', $id, ResourceLogItem::VIEW);
+            $playlist->view_count++;
+        }
+
+        return Response::json(Playlist::mapPublicPlaylistShow($playlist), 200);
+    }
+
+    public function getPinned()
+    {
+        $query = Playlist
+            ::userDetails()
+            ->with('tracks', 'tracks.cover', 'tracks.user', 'user')
+            ->join('pinned_playlists', function ($join) {
+                $join->on('playlist_id', '=', 'playlists.id');
+            })
+            ->where('pinned_playlists.user_id', '=', Auth::user()->id)
+            ->orderBy('title', 'asc')
+            ->select('playlists.*')
+            ->get();
+
+        $playlists = [];
+        foreach ($query as $playlist) {
+            $mapped = Playlist::mapPublicPlaylistSummary($playlist);
+            $mapped['description'] = $playlist->description;
+            $mapped['is_pinned'] = true;
+            $playlists[] = $mapped;
+        }
+
+        return Response::json($playlists, 200);
+    }
+
+    public function getOwned()
+    {
+        $query = Playlist::summary()->with('pins', 'tracks', 'tracks.cover')->where('user_id',
+            \Auth::user()->id)->orderBy('title', 'asc')->get();
+        $playlists = [];
+        foreach ($query as $playlist) {
+            $playlists[] = [
+                'id' => $playlist->id,
+                'title' => $playlist->title,
+                'slug' => $playlist->slug,
+                'created_at' => $playlist->created_at,
+                'description' => $playlist->description,
+                'url' => $playlist->url,
+                'covers' => [
+                    'small' => $playlist->getCoverUrl(Image::SMALL),
+                    'normal' => $playlist->getCoverUrl(Image::NORMAL)
+                ],
+                'is_pinned' => $playlist->hasPinFor(Auth::user()->id),
+                'is_public' => $playlist->is_public == 1
+            ];
+        }
+
+        return Response::json($playlists, 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/ProfilerController.php b/app/Http/Controllers/Api/Web/ProfilerController.php
new file mode 100644
index 00000000..4fa87ef1
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/ProfilerController.php
@@ -0,0 +1,29 @@
+<?php
+
+namespace Api\Web;
+
+use App\Http\Controllers\Controller;
+use App\ProfileRequest;
+use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\Response;
+
+class ProfilerController extends Controller
+{
+    public function getRequest($id)
+    {
+        if (!Config::get('app.debug')) {
+            return;
+        }
+
+        $key = 'profiler-request-' . $id;
+        $request = Cache::get($key);
+        if (!$request) {
+            exit();
+        }
+
+        Cache::forget($key);
+
+        return Response::json(['request' => ProfileRequest::load($request)->toArray()], 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/TaxonomiesController.php b/app/Http/Controllers/Api/Web/TaxonomiesController.php
new file mode 100644
index 00000000..23f3bcf4
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/TaxonomiesController.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Api\Web;
+
+use App\Genre;
+use App\License;
+use App\ShowSong;
+use App\TrackType;
+use Illuminate\Support\Facades\DB;
+
+class TaxonomiesController extends \ApiControllerBase
+{
+    public function getAll()
+    {
+        return \Response::json([
+            'licenses' => License::all()->toArray(),
+            'genres' => Genre::select('genres.*',
+                DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.genre_id = genres.id AND tracks.published_at IS NOT NULL) AS track_count'))->orderBy('name')->get()->toArray(),
+            'track_types' => TrackType::select('track_types.*',
+                DB::raw('(SELECT COUNT(id) FROM tracks WHERE tracks.track_type_id = track_types.id AND tracks.published_at IS NOT NULL) AS track_count'))->get()->toArray(),
+            'show_songs' => ShowSong::select('title', 'id', 'slug',
+                DB::raw('(SELECT COUNT(tracks.id) FROM show_song_track INNER JOIN tracks ON tracks.id = show_song_track.track_id WHERE show_song_track.show_song_id = show_songs.id AND tracks.published_at IS NOT NULL) AS track_count'))->get()->toArray()
+        ], 200);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Api/Web/TracksController.php b/app/Http/Controllers/Api/Web/TracksController.php
new file mode 100644
index 00000000..8c1e8a0a
--- /dev/null
+++ b/app/Http/Controllers/Api/Web/TracksController.php
@@ -0,0 +1,158 @@
+<?php
+
+namespace Api\Web;
+
+use App\Commands\DeleteTrackCommand;
+use App\Commands\EditTrackCommand;
+use App\Commands\UploadTrackCommand;
+use App\ResourceLogItem;
+use App\Track;
+use Cover;
+use Illuminate\Support\Facades\Auth;
+use Illuminate\Support\Facades\Input;
+use Illuminate\Support\Facades\Response;
+
+class TracksController extends \ApiControllerBase
+{
+    public function postUpload()
+    {
+        session_write_close();
+
+        return $this->execute(new UploadTrackCommand());
+    }
+
+    public function postDelete($id)
+    {
+        return $this->execute(new DeleteTrackCommand($id));
+    }
+
+    public function postEdit($id)
+    {
+        return $this->execute(new EditTrackCommand($id, Input::all()));
+    }
+
+    public function getShow($id)
+    {
+        $track = Track::userDetails()->withComments()->find($id);
+        if (!$track || !$track->canView(Auth::user())) {
+            return $this->notFound('Track not found!');
+        }
+
+        if (Input::get('log')) {
+            ResourceLogItem::logItem('track', $id, ResourceLogItem::VIEW);
+            $track->view_count++;
+        }
+
+        $returned_track = Track::mapPublicTrackShow($track);
+        if ($returned_track['is_downloadable'] != 1) {
+            unset($returned_track['formats']);
+        }
+
+        return Response::json(['track' => $returned_track], 200);
+    }
+
+    public function getIndex()
+    {
+        $page = 1;
+        $perPage = 45;
+
+        if (Input::has('page')) {
+            $page = Input::get('page');
+        }
+
+        $query = Track::summary()
+            ->userDetails()
+            ->listed()
+            ->explicitFilter()
+            ->published()
+            ->with('user', 'genre', 'cover', 'album', 'album.user');
+
+        $this->applyFilters($query);
+
+        $totalCount = $query->count();
+        $query->take($perPage)->skip($perPage * ($page - 1));
+
+        $tracks = [];
+        $ids = [];
+
+        foreach ($query->get(['tracks.*']) as $track) {
+            $tracks[] = Track::mapPublicTrackSummary($track);
+            $ids[] = $track->id;
+        }
+
+        return Response::json([
+            "tracks" => $tracks,
+            "current_page" => $page,
+            "total_pages" => ceil($totalCount / $perPage)
+        ], 200);
+    }
+
+    public function getOwned()
+    {
+        $query = Track::summary()->where('user_id', \Auth::user()->id)->orderBy('created_at', 'desc');
+
+        $tracks = [];
+        foreach ($query->get() as $track) {
+            $tracks[] = Track::mapPrivateTrackSummary($track);
+        }
+
+        return Response::json($tracks, 200);
+    }
+
+    public function getEdit($id)
+    {
+        $track = Track::with('showSongs')->find($id);
+        if (!$track) {
+            return $this->notFound('Track ' . $id . ' not found!');
+        }
+
+        if ($track->user_id != Auth::user()->id) {
+            return $this->notAuthorized();
+        }
+
+        return Response::json(Track::mapPrivateTrackShow($track), 200);
+    }
+
+    private function applyFilters($query)
+    {
+        if (Input::has('order')) {
+            $order = \Input::get('order');
+            $parts = explode(',', $order);
+            $query->orderBy($parts[0], $parts[1]);
+        }
+
+        if (Input::has('is_vocal')) {
+            $isVocal = \Input::get('is_vocal');
+            if ($isVocal == 'true') {
+                $query->whereIsVocal(true);
+            } else {
+                $query->whereIsVocal(false);
+            }
+        }
+
+        if (Input::has('in_album')) {
+            if (Input::get('in_album') == 'true') {
+                $query->whereNotNull('album_id');
+            } else {
+                $query->whereNull('album_id');
+            }
+        }
+
+        if (Input::has('genres')) {
+            $query->whereIn('genre_id', Input::get('genres'));
+        }
+
+        if (Input::has('types')) {
+            $query->whereIn('track_type_id', Input::get('types'));
+        }
+
+        if (Input::has('songs')) {
+            $query->join('show_song_track', function ($join) {
+                $join->on('tracks.id', '=', 'show_song_track.track_id');
+            });
+            $query->whereIn('show_song_track.show_song_id', Input::get('songs'));
+        }
+
+        return $query;
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/ApiControllerBase.php b/app/Http/Controllers/ApiControllerBase.php
new file mode 100644
index 00000000..a62ead0b
--- /dev/null
+++ b/app/Http/Controllers/ApiControllerBase.php
@@ -0,0 +1,33 @@
+<?php
+
+use App\Http\Controllers\Controller;
+
+abstract class ApiControllerBase extends Controller
+{
+    protected function execute($command)
+    {
+        if (!$command->authorize()) {
+            return $this->notAuthorized();
+        }
+
+        $result = $command->execute();
+        if ($result->didFail()) {
+            return Response::json([
+                'message' => 'Validation failed',
+                'errors' => $result->getValidator()->messages()->getMessages()
+            ], 400);
+        }
+
+        return Response::json($result->getResponse(), 200);
+    }
+
+    public function notAuthorized()
+    {
+        return Response::json(['message' => 'You may not do this!'], 403);
+    }
+
+    public function notFound($message)
+    {
+        return Response::json(['message' => $message], 403);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/ArtistsController.php b/app/Http/Controllers/ArtistsController.php
new file mode 100644
index 00000000..5d8da45d
--- /dev/null
+++ b/app/Http/Controllers/ArtistsController.php
@@ -0,0 +1,32 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use App\User;
+
+class ArtistsController extends Controller
+{
+    public function getIndex()
+    {
+        return View::make('artists.index');
+    }
+
+    public function getProfile($slug)
+    {
+        $user = User::whereSlug($slug)->first();
+        if (!$user) {
+            App::abort('404');
+        }
+
+        return View::make('artists.profile');
+    }
+
+    public function getShortlink($id)
+    {
+        $user = User::find($id);
+        if (!$user) {
+            App::abort('404');
+        }
+
+        return Redirect::action('ArtistsController@getProfile', [$id]);
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php
deleted file mode 100644
index c0ad3b8e..00000000
--- a/app/Http/Controllers/Auth/AuthController.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\Auth;
-
-use App\User;
-use Validator;
-use App\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\ThrottlesLogins;
-use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
-
-class AuthController extends Controller
-{
-    /*
-    |--------------------------------------------------------------------------
-    | Registration & Login Controller
-    |--------------------------------------------------------------------------
-    |
-    | This controller handles the registration of new users, as well as the
-    | authentication of existing users. By default, this controller uses
-    | a simple trait to add these behaviors. Why don't you explore it?
-    |
-    */
-
-    use AuthenticatesAndRegistersUsers, ThrottlesLogins;
-
-    /**
-     * Create a new authentication controller instance.
-     *
-     * @return void
-     */
-    public function __construct()
-    {
-        $this->middleware('guest', ['except' => 'getLogout']);
-    }
-
-    /**
-     * Get a validator for an incoming registration request.
-     *
-     * @param  array  $data
-     * @return \Illuminate\Contracts\Validation\Validator
-     */
-    protected function validator(array $data)
-    {
-        return Validator::make($data, [
-            'name' => 'required|max:255',
-            'email' => 'required|email|max:255|unique:users',
-            'password' => 'required|confirmed|min:6',
-        ]);
-    }
-
-    /**
-     * Create a new user instance after a valid registration.
-     *
-     * @param  array  $data
-     * @return User
-     */
-    protected function create(array $data)
-    {
-        return User::create([
-            'name' => $data['name'],
-            'email' => $data['email'],
-            'password' => bcrypt($data['password']),
-        ]);
-    }
-}
diff --git a/app/Http/Controllers/Auth/PasswordController.php b/app/Http/Controllers/Auth/PasswordController.php
deleted file mode 100644
index 1ceed97b..00000000
--- a/app/Http/Controllers/Auth/PasswordController.php
+++ /dev/null
@@ -1,32 +0,0 @@
-<?php
-
-namespace App\Http\Controllers\Auth;
-
-use App\Http\Controllers\Controller;
-use Illuminate\Foundation\Auth\ResetsPasswords;
-
-class PasswordController extends Controller
-{
-    /*
-    |--------------------------------------------------------------------------
-    | Password Reset Controller
-    |--------------------------------------------------------------------------
-    |
-    | This controller is responsible for handling password reset requests
-    | and uses a simple trait to include this behavior. You're free to
-    | explore this trait and override any methods you wish to tweak.
-    |
-    */
-
-    use ResetsPasswords;
-
-    /**
-     * Create a new password controller instance.
-     *
-     * @return void
-     */
-    public function __construct()
-    {
-        $this->middleware('guest');
-    }
-}
diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php
new file mode 100644
index 00000000..d120e2ff
--- /dev/null
+++ b/app/Http/Controllers/AuthController.php
@@ -0,0 +1,107 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use App\User;
+
+class AuthController extends Controller
+{
+    protected $poniverse;
+
+    public function __construct()
+    {
+        $this->poniverse = new Poniverse(Config::get('poniverse.client_id'), Config::get('poniverse.secret'));
+        $this->poniverse->setRedirectUri(URL::to('/auth/oauth'));
+    }
+
+    public function getLogin()
+    {
+        if (Auth::guest()) {
+            return Redirect::to($this->poniverse->getAuthenticationUrl('login'));
+        }
+
+        return Redirect::to('/');
+    }
+
+    public function postLogout()
+    {
+        Auth::logout();
+
+        return Redirect::to('/');
+    }
+
+    public function getOAuth()
+    {
+        $code = $this->poniverse->getClient()->getAccessToken(
+            Config::get('poniverse.urls')['token'],
+            'authorization_code',
+            [
+                'code' => Input::query('code'),
+                'redirect_uri' => URL::to('/auth/oauth')
+            ]);
+
+        if ($code['code'] != 200) {
+            if ($code['code'] == 400 && $code['result']['error_description'] == 'The authorization code has expired' && !isset($this->request['login_attempt'])) {
+                return Redirect::to($this->poniverse->getAuthenticationUrl('login_attempt'));
+            }
+
+            return Redirect::to('/')->with('message',
+                'Unfortunately we are having problems attempting to log you in at the moment. Please try again at a later time.');
+        }
+
+        $this->poniverse->setAccessToken($code['result']['access_token']);
+        $poniverseUser = $this->poniverse->getUser();
+        $token = DB::table('oauth2_tokens')->where('external_user_id', '=', $poniverseUser['id'])->where('service', '=',
+            'poniverse')->first();
+
+        $setData = [
+            'access_token' => $code['result']['access_token'],
+            'expires' => date('Y-m-d H:i:s', strtotime("+" . $code['result']['expires_in'] . " Seconds", time())),
+            'type' => $code['result']['token_type'],
+        ];
+
+        if (isset($code['result']['refresh_token']) && !empty($code['result']['refresh_token'])) {
+            $setData['refresh_token'] = $code['result']['refresh_token'];
+        }
+
+        if ($token) {
+            //User already exists, update access token and refresh token if provided.
+            DB::table('oauth2_tokens')->where('id', '=', $token->id)->update($setData);
+
+            return $this->loginRedirect(User::find($token->user_id));
+        }
+
+        //Check by email to see if they already have an account
+        $localMember = User::where('email', '=', $poniverseUser['email'])->first();
+
+        if ($localMember) {
+            return $this->loginRedirect($localMember);
+        }
+
+        $user = new User;
+
+        $user->mlpforums_name = $poniverseUser['username'];
+        $user->display_name = $poniverseUser['display_name'];
+        $user->email = $poniverseUser['email'];
+        $user->created_at = gmdate("Y-m-d H:i:s", time());
+        $user->uses_gravatar = 1;
+
+        $user->save();
+
+        //We need to insert a new token row :O
+
+        $setData['user_id'] = $user->id;
+        $setData['external_user_id'] = $poniverseUser['id'];
+        $setData['service'] = 'poniverse';
+
+        DB::table('oauth2_tokens')->insert($setData);
+
+        return $this->loginRedirect($user);
+    }
+
+    protected function loginRedirect($user, $rememberMe = true)
+    {
+        Auth::login($user, $rememberMe);
+
+        return Redirect::to('/');
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/ContentController.php b/app/Http/Controllers/ContentController.php
new file mode 100644
index 00000000..51fa4370
--- /dev/null
+++ b/app/Http/Controllers/ContentController.php
@@ -0,0 +1,21 @@
+<?php
+
+use App\Http\Controllers\Controller;
+
+class ContentController extends Controller
+{
+    public function getTracks()
+    {
+        return View::make('shared.null');
+    }
+
+    public function getAlbums()
+    {
+        return View::make('shared.null');
+    }
+
+    public function getPlaylists()
+    {
+        return View::make('shared.null');
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/FavouritesController.php b/app/Http/Controllers/FavouritesController.php
new file mode 100644
index 00000000..e4995b58
--- /dev/null
+++ b/app/Http/Controllers/FavouritesController.php
@@ -0,0 +1,21 @@
+<?php
+
+use App\Http\Controllers\Controller;
+
+class FavouritesController extends Controller
+{
+    public function getTracks()
+    {
+        return View::make('shared.null');
+    }
+
+    public function getAlbums()
+    {
+        return View::make('shared.null');
+    }
+
+    public function getPlaylists()
+    {
+        return View::make('shared.null');
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
new file mode 100644
index 00000000..2fa5fe8e
--- /dev/null
+++ b/app/Http/Controllers/HomeController.php
@@ -0,0 +1,11 @@
+<?php
+
+use App\Http\Controllers\Controller;
+
+class HomeController extends Controller
+{
+    public function getIndex()
+    {
+        return View::make('home.index');
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/ImagesController.php b/app/Http/Controllers/ImagesController.php
new file mode 100644
index 00000000..c8561a92
--- /dev/null
+++ b/app/Http/Controllers/ImagesController.php
@@ -0,0 +1,48 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use App\Image;
+use Illuminate\Support\Facades\App;
+use Illuminate\Support\Facades\Redirect;
+
+class ImagesController extends Controller
+{
+    public function getImage($id, $type)
+    {
+        $coverType = Image::getImageTypeFromName($type);
+
+        if ($coverType == null) {
+            App::abort(404);
+        }
+
+        $image = Image::find($id);
+        if (!$image) {
+            App::abort(404);
+        }
+
+        $response = Response::make('', 200);
+        $filename = $image->getFile($coverType['id']);
+
+        if (!is_file($filename)) {
+            $redirect = URL::to('/images/icons/profile_' . Image::$ImageTypes[$coverType['id']]['name'] . '.png');
+
+            return Redirect::to($redirect);
+        }
+
+        if (Config::get('app.sendfile')) {
+            $response->header('X-Sendfile', $filename);
+        } else {
+            $response->header('X-Accel-Redirect', $filename);
+        }
+
+        $response->header('Content-Disposition', 'filename="' . $filename . '"');
+        $response->header('Content-Type', 'image/png');
+
+        $lastModified = filemtime($filename);
+
+        $response->header('Last-Modified', $lastModified);
+        $response->header('Cache-Control', 'max-age=' . (60 * 60 * 24 * 7));
+
+        return $response;
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/PlaylistsController.php b/app/Http/Controllers/PlaylistsController.php
new file mode 100644
index 00000000..58314b16
--- /dev/null
+++ b/app/Http/Controllers/PlaylistsController.php
@@ -0,0 +1,66 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use App\Playlist;
+use App\ResourceLogItem;
+use App\Track;
+use Illuminate\Support\Facades\Redirect;
+
+class PlaylistsController extends Controller
+{
+    public function getIndex()
+    {
+        return View::make('playlists.index');
+    }
+
+    public function getPlaylist($id, $slug)
+    {
+        $playlist = Playlist::find($id);
+        if (!$playlist || !$playlist->canView(Auth::user())) {
+            App::abort(404);
+        }
+
+        if ($playlist->slug != $slug) {
+            return Redirect::action('PlaylistsController@getPlaylist', [$id, $playlist->slug]);
+        }
+
+        return View::make('playlists.show');
+    }
+
+    public function getShortlink($id)
+    {
+        $playlist = Playlist::find($id);
+        if (!$playlist || !$playlist->canView(Auth::user())) {
+            App::abort(404);
+        }
+
+        return Redirect::action('PlaylistsController@getPlaylist', [$id, $playlist->slug]);
+    }
+
+    public function getDownload($id, $extension)
+    {
+        $playlist = Playlist::with('tracks', 'user', 'tracks.album')->find($id);
+        if (!$playlist || !$playlist->is_public) {
+            App::abort(404);
+        }
+
+        $format = null;
+        $formatName = null;
+
+        foreach (Track::$Formats as $name => $item) {
+            if ($item['extension'] == $extension) {
+                $format = $item;
+                $formatName = $name;
+                break;
+            }
+        }
+
+        if ($format == null) {
+            App::abort(404);
+        }
+
+        ResourceLogItem::logItem('playlist', $id, ResourceLogItem::DOWNLOAD, $format['index']);
+        $downloader = new PlaylistDownloader($playlist, $formatName);
+        $downloader->download();
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/TracksController.php b/app/Http/Controllers/TracksController.php
new file mode 100644
index 00000000..50c71455
--- /dev/null
+++ b/app/Http/Controllers/TracksController.php
@@ -0,0 +1,147 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use App\ResourceLogItem;
+use App\Track;
+use App\TrackFile;
+use Illuminate\Support\Facades\App;
+
+class TracksController extends Controller
+{
+    public function getIndex()
+    {
+        return View::make('tracks.index');
+    }
+
+    public function getEmbed($id)
+    {
+        $track = Track
+            ::whereId($id)
+            ->published()
+            ->userDetails()
+            ->with(
+                'user',
+                'user.avatar',
+                'genre'
+            )->first();
+
+        if (!$track || !$track->canView(Auth::user())) {
+            App::abort(404);
+        }
+
+        $userData = [
+            'stats' => [
+                'views' => 0,
+                'plays' => 0,
+                'downloads' => 0
+            ],
+            'is_favourited' => false
+        ];
+
+        if ($track->users->count()) {
+            $userRow = $track->users[0];
+            $userData = [
+                'stats' => [
+                    'views' => $userRow->view_count,
+                    'plays' => $userRow->play_count,
+                    'downloads' => $userRow->download_count,
+                ],
+                'is_favourited' => $userRow->is_favourited
+            ];
+        }
+
+        return View::make('tracks.embed', ['track' => $track, 'user' => $userData]);
+    }
+
+    public function getTrack($id, $slug)
+    {
+        $track = Track::find($id);
+        if (!$track || !$track->canView(Auth::user())) {
+            App::abort(404);
+        }
+
+        if ($track->slug != $slug) {
+            return Redirect::action('TracksController@getTrack', [$id, $track->slug]);
+        }
+
+        return View::make('tracks.show');
+    }
+
+    public function getShortlink($id)
+    {
+        $track = Track::find($id);
+        if (!$track || !$track->canView(Auth::user())) {
+            App::abort(404);
+        }
+
+        return Redirect::action('TracksController@getTrack', [$id, $track->slug]);
+    }
+
+    public function getStream($id, $extension)
+    {
+        $track = Track::find($id);
+        if (!$track || !$track->canView(Auth::user())) {
+            App::abort(404);
+        }
+
+        $trackFile = TrackFile::findOrFailByExtension($track->id, $extension);
+        ResourceLogItem::logItem('track', $id, ResourceLogItem::PLAY, $trackFile->getFormat()['index']);
+
+        $response = Response::make('', 200);
+        $filename = $trackFile->getFile();
+
+        if (Config::get('app.sendfile')) {
+            $response->header('X-Sendfile', $filename);
+        } else {
+            $response->header('X-Accel-Redirect', $filename);
+        }
+
+        $time = gmdate(filemtime($filename));
+
+        if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $time == $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
+            header('HTTP/1.0 304 Not Modified');
+            exit();
+        }
+
+        $response->header('Last-Modified', $time);
+        $response->header('Content-Type', $trackFile->getFormat()['mime_type']);
+
+        return $response;
+    }
+
+    public function getDownload($id, $extension)
+    {
+        $track = Track::find($id);
+        if (!$track || !$track->canView(Auth::user())) {
+            App::abort(404);
+        }
+
+        $trackFile = TrackFile::findOrFailByExtension($track->id, $extension);
+        ResourceLogItem::logItem('track', $id, ResourceLogItem::DOWNLOAD, $trackFile->getFormat()['index']);
+
+        $response = Response::make('', 200);
+        $filename = $trackFile->getFile();
+
+        if (Config::get('app.sendfile')) {
+            $response->header('X-Sendfile', $filename);
+            $response->header('Content-Disposition',
+                'attachment; filename="' . $trackFile->getDownloadFilename() . '"');
+        } else {
+            $response->header('X-Accel-Redirect', $filename);
+            $response->header('Content-Disposition',
+                'attachment; filename="' . $trackFile->getDownloadFilename() . '"');
+        }
+
+        $time = gmdate(filemtime($filename));
+
+        if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $time == $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
+            header('HTTP/1.0 304 Not Modified');
+            exit();
+        }
+
+        $response->header('Last-Modified', $time);
+        $response->header('Content-Type', $trackFile->getFormat()['mime_type']);
+
+        return $response;
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/UploaderController.php b/app/Http/Controllers/UploaderController.php
new file mode 100644
index 00000000..0d4e9a9b
--- /dev/null
+++ b/app/Http/Controllers/UploaderController.php
@@ -0,0 +1,11 @@
+<?php
+
+use App\Http\Controllers\Controller;
+
+class UploaderController extends Controller
+{
+    public function getIndex()
+    {
+        return View::make('shared.null');
+    }
+}
\ No newline at end of file
diff --git a/app/Http/Controllers/UsersController.php b/app/Http/Controllers/UsersController.php
new file mode 100644
index 00000000..6e1137e3
--- /dev/null
+++ b/app/Http/Controllers/UsersController.php
@@ -0,0 +1,23 @@
+<?php
+
+use App\Http\Controllers\Controller;
+use Illuminate\Support\Facades\App;
+
+class UsersController extends Controller
+{
+    public function getAvatar($id, $type)
+    {
+        $coverType = Cover::getCoverFromName($type);
+
+        if ($coverType == null) {
+            App::abort(404);
+        }
+
+        $user = User::find($id);
+        if (!$user) {
+            App::abort(404);
+        }
+
+        return File::inline($user->getAvatarFile($coverType['id']), 'image/png', 'cover.png');
+    }
+}
\ No newline at end of file