From 5c2b9deb096491528a01946b03bec100fa814439 Mon Sep 17 00:00:00 2001
From: Dane Everitt <DaneEveritt@users.noreply.github.com>
Date: Sat, 10 Jun 2017 22:28:44 -0500
Subject: [PATCH] Push initial implementations of new repository structure

This breaks almost the entire panel, do not pull this branch in this state.

Mostly just moved old repository files to a new folder without updating anything else in order to start doing new things. Structure is not finalized.
---
 app/Console/Commands/MakeUser.php             |   4 +-
 app/Contracts/Criteria/CriteriaInterface.php  |  39 +++
 .../Repositories/RepositoryInterface.php      | 164 +++++++++++++
 .../SearchableRepositoryInterface.php         |  36 +++
 app/Contracts/Repositories/UserInterface.php  |  30 +++
 app/Exceptions/DisplayException.php           |   2 +-
 app/Exceptions/DisplayValidationException.php |   2 +-
 app/Exceptions/PterodactylException.php       |  30 +++
 .../Repository/RepositoryException.php        |  30 +++
 .../Controllers/API/Admin/UserController.php  |   8 +-
 .../Controllers/Admin/OptionController.php    |  36 ++-
 app/Http/Controllers/Admin/UserController.php | 112 ++++-----
 .../Controllers/Base/AccountController.php    |   4 +-
 app/Http/Requests/Admin/AdminFormRequest.php  |  59 +++++
 .../Admin/Service/EditOptionScript.php        |  46 ++++
 .../Admin/Service/StoreOptionVariable.php     |  32 +--
 app/Http/Requests/Admin/UserFormRequest.php   |  79 ++++++
 app/Models/User.php                           |  18 +-
 app/Observers/UserObserver.php                |  15 +-
 app/Providers/RepositoryServiceProvider.php   |  40 +++
 app/Repositories/Eloquent/UserRepository.php  |  78 ++++++
 app/Repositories/{ => Old}/APIRepository.php  |   0
 .../{ => Old}/DatabaseRepository.php          |   0
 .../{ => Old}/HelperRepository.php            |   0
 .../{ => Old}/LocationRepository.php          |   0
 app/Repositories/{ => Old}/NodeRepository.php |   0
 .../{ => Old}/OptionRepository.php            |  94 ++++---
 app/Repositories/{ => Old}/PackRepository.php |   0
 .../{ => Old}/ServiceRepository.php           |   0
 .../{ => Old}/SubuserRepository.php           |   2 +-
 app/Repositories/{ => Old}/TaskRepository.php |   0
 .../{ => Old}/VariableRepository.php          |   0
 .../old_ServerRepository.php}                 |   2 +-
 .../old_UserRepository.php}                   |   2 +-
 app/Repositories/Repository.php               | 229 ++++++++++++++++++
 config/app.php                                |   1 +
 ..._06_10_152951_add_external_id_to_users.php |  32 +++
 .../pterodactyl/admin/users/view.blade.php    |   1 +
 routes/admin.php                              |  22 +-
 39 files changed, 1083 insertions(+), 166 deletions(-)
 create mode 100644 app/Contracts/Criteria/CriteriaInterface.php
 create mode 100644 app/Contracts/Repositories/RepositoryInterface.php
 create mode 100644 app/Contracts/Repositories/SearchableRepositoryInterface.php
 create mode 100644 app/Contracts/Repositories/UserInterface.php
 create mode 100644 app/Exceptions/PterodactylException.php
 create mode 100644 app/Exceptions/Repository/RepositoryException.php
 create mode 100644 app/Http/Requests/Admin/AdminFormRequest.php
 create mode 100644 app/Http/Requests/Admin/Service/EditOptionScript.php
 create mode 100644 app/Http/Requests/Admin/UserFormRequest.php
 create mode 100644 app/Providers/RepositoryServiceProvider.php
 create mode 100644 app/Repositories/Eloquent/UserRepository.php
 rename app/Repositories/{ => Old}/APIRepository.php (100%)
 rename app/Repositories/{ => Old}/DatabaseRepository.php (100%)
 rename app/Repositories/{ => Old}/HelperRepository.php (100%)
 rename app/Repositories/{ => Old}/LocationRepository.php (100%)
 rename app/Repositories/{ => Old}/NodeRepository.php (100%)
 rename app/Repositories/{ => Old}/OptionRepository.php (79%)
 rename app/Repositories/{ => Old}/PackRepository.php (100%)
 rename app/Repositories/{ => Old}/ServiceRepository.php (100%)
 rename app/Repositories/{ => Old}/SubuserRepository.php (99%)
 rename app/Repositories/{ => Old}/TaskRepository.php (100%)
 rename app/Repositories/{ => Old}/VariableRepository.php (100%)
 rename app/Repositories/{ServerRepository.php => Old/old_ServerRepository.php} (99%)
 rename app/Repositories/{UserRepository.php => Old/old_UserRepository.php} (99%)
 create mode 100644 app/Repositories/Repository.php
 create mode 100644 database/migrations/2017_06_10_152951_add_external_id_to_users.php

diff --git a/app/Console/Commands/MakeUser.php b/app/Console/Commands/MakeUser.php
index 05803dd3..81038cb4 100644
--- a/app/Console/Commands/MakeUser.php
+++ b/app/Console/Commands/MakeUser.php
@@ -25,7 +25,7 @@
 namespace Pterodactyl\Console\Commands;
 
 use Illuminate\Console\Command;
-use Pterodactyl\Repositories\UserRepository;
+use Pterodactyl\Repositories\oldUserRepository;
 
 class MakeUser extends Command
 {
@@ -80,7 +80,7 @@ class MakeUser extends Command
         $data['root_admin'] = is_null($this->option('admin')) ? $this->confirm('Is this user a root administrator?') : $this->option('admin');
 
         try {
-            $user = new UserRepository;
+            $user = new oldUserRepository;
             $user->create($data);
 
             return $this->info('User successfully created.');
diff --git a/app/Contracts/Criteria/CriteriaInterface.php b/app/Contracts/Criteria/CriteriaInterface.php
new file mode 100644
index 00000000..dba7a688
--- /dev/null
+++ b/app/Contracts/Criteria/CriteriaInterface.php
@@ -0,0 +1,39 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Contracts\Criteria;
+
+use Pterodactyl\Repositories\Repository;
+
+interface CriteriaInterface
+{
+    /**
+     * Apply selected criteria to a repository call.
+     *
+     * @param  \Illuminate\Database\Eloquent\Model            $model
+     * @param  \Pterodactyl\Repositories\Repository  $repository
+     * @return mixed
+     */
+    public function apply($model, Repository $repository);
+}
diff --git a/app/Contracts/Repositories/RepositoryInterface.php b/app/Contracts/Repositories/RepositoryInterface.php
new file mode 100644
index 00000000..16771e70
--- /dev/null
+++ b/app/Contracts/Repositories/RepositoryInterface.php
@@ -0,0 +1,164 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Contracts\Repositories;
+
+use Illuminate\Container\Container;
+
+interface RepositoryInterface
+{
+    /**
+     * RepositoryInterface constructor.
+     *
+     * @param \Illuminate\Container\Container $container
+     */
+    public function __construct(Container $container);
+
+    /**
+     * Define the model class to be loaded.
+     *
+     * @return string
+     */
+    public function model();
+
+    /**
+     * Returns the raw model class.
+     *
+     * @return \Illuminate\Database\Eloquent\Model
+     */
+    public function getModel();
+
+    /**
+     * Make the model instance.
+     *
+     * @return \Illuminate\Database\Eloquent\Model
+     * @throws \Pterodactyl\Exceptions\Repository\RepositoryException
+     */
+    public function makeModel();
+
+    /**
+     * Return all of the currently defined rules.
+     *
+     * @return array
+     */
+    public function getRules();
+
+    /**
+     * Return the rules to apply when updating a model.
+     *
+     * @return array
+     */
+    public function getUpdateRules();
+
+    /**
+     * Return the rules to apply when creating a model.
+     *
+     * @return array
+     */
+    public function getCreateRules();
+
+    /**
+     * Add relations to a model for retrieval.
+     *
+     * @param  array  $params
+     * @return $this
+     */
+    public function with(...$params);
+
+    /**
+     * Add count of related items to model when retrieving.
+     *
+     * @param  array  $params
+     * @return $this
+     */
+    public function withCount(...$params);
+
+    /**
+     * Get all records from the database.
+     *
+     * @param  array $columns
+     * @return mixed
+     */
+    public function all(array $columns = ['*']);
+
+    /**
+     * Return a paginated result set.
+     *
+     * @param  int   $limit
+     * @param  array $columns
+     * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
+     */
+    public function paginate($limit = 15, array $columns = ['*']);
+
+    /**
+     * Create a new record on the model.
+     *
+     * @param  array  $data
+     * @return \Illuminate\Database\Eloquent\Model
+     */
+    public function create(array $data);
+
+    /**
+     * Update the model.
+     *
+     * @param         $attributes
+     * @param  array  $data
+     * @return int
+     */
+    public function update($attributes, array $data);
+
+    /**
+     * Delete a model from the database. Handles soft deletion.
+     *
+     * @param  int    $id
+     * @return mixed
+     */
+    public function delete($id);
+
+    /**
+     * Destroy the model from the database. Ignores soft deletion.
+     *
+     * @param  int  $id
+     * @return mixed
+     */
+    public function destroy($id);
+
+    /**
+     * Find a given model by ID or IDs.
+     *
+     * @param  int|array  $id
+     * @param  array      $columns
+     * @return \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Collection
+     */
+    public function find($id, array $columns = ['*']);
+
+    /**
+     * Finds the first record matching a passed array of values.
+     *
+     * @param  array  $attributes
+     * @param  array  $columns
+     * @return \Illuminate\Database\Eloquent\Model
+     */
+    public function findBy(array $attributes, array $columns = ['*']);
+}
diff --git a/app/Contracts/Repositories/SearchableRepositoryInterface.php b/app/Contracts/Repositories/SearchableRepositoryInterface.php
new file mode 100644
index 00000000..6a6b4537
--- /dev/null
+++ b/app/Contracts/Repositories/SearchableRepositoryInterface.php
@@ -0,0 +1,36 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Contracts\Repositories;
+
+interface SearchableRepositoryInterface extends RepositoryInterface
+{
+    /**
+     * Pass parameters to search trait on model.
+     *
+     * @param  string  $term
+     * @return mixed
+     */
+    public function search($term);
+}
diff --git a/app/Contracts/Repositories/UserInterface.php b/app/Contracts/Repositories/UserInterface.php
new file mode 100644
index 00000000..a7bf4964
--- /dev/null
+++ b/app/Contracts/Repositories/UserInterface.php
@@ -0,0 +1,30 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Contracts\Repositories;
+
+interface UserInterface extends RepositoryInterface, SearchableRepositoryInterface
+{
+    //
+}
diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php
index f3f8fd71..530ad40c 100644
--- a/app/Exceptions/DisplayException.php
+++ b/app/Exceptions/DisplayException.php
@@ -26,7 +26,7 @@ namespace Pterodactyl\Exceptions;
 
 use Log;
 
-class DisplayException extends \Exception
+class DisplayException extends PterodactylException
 {
     /**
      * Exception constructor.
diff --git a/app/Exceptions/DisplayValidationException.php b/app/Exceptions/DisplayValidationException.php
index 3d8a4fda..ae97318a 100644
--- a/app/Exceptions/DisplayValidationException.php
+++ b/app/Exceptions/DisplayValidationException.php
@@ -24,7 +24,7 @@
 
 namespace Pterodactyl\Exceptions;
 
-class DisplayValidationException extends \Exception
+class DisplayValidationException extends PterodactylException
 {
     //
 }
diff --git a/app/Exceptions/PterodactylException.php b/app/Exceptions/PterodactylException.php
new file mode 100644
index 00000000..4ec48d66
--- /dev/null
+++ b/app/Exceptions/PterodactylException.php
@@ -0,0 +1,30 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Exceptions;
+
+class PterodactylException extends \Exception
+{
+    //
+}
diff --git a/app/Exceptions/Repository/RepositoryException.php b/app/Exceptions/Repository/RepositoryException.php
new file mode 100644
index 00000000..b55b6304
--- /dev/null
+++ b/app/Exceptions/Repository/RepositoryException.php
@@ -0,0 +1,30 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Exceptions\Repository;
+
+class RepositoryException extends \Exception
+{
+    //
+}
diff --git a/app/Http/Controllers/API/Admin/UserController.php b/app/Http/Controllers/API/Admin/UserController.php
index c94fe809..b524d1ee 100644
--- a/app/Http/Controllers/API/Admin/UserController.php
+++ b/app/Http/Controllers/API/Admin/UserController.php
@@ -30,7 +30,7 @@ use Illuminate\Http\Request;
 use Pterodactyl\Models\User;
 use Pterodactyl\Exceptions\DisplayException;
 use Pterodactyl\Http\Controllers\Controller;
-use Pterodactyl\Repositories\UserRepository;
+use Pterodactyl\Repositories\oldUserRepository;
 use Pterodactyl\Transformers\Admin\UserTransformer;
 use Pterodactyl\Exceptions\DisplayValidationException;
 use League\Fractal\Pagination\IlluminatePaginatorAdapter;
@@ -91,7 +91,7 @@ class UserController extends Controller
     {
         $this->authorize('user-create', $request->apiKey());
 
-        $repo = new UserRepository;
+        $repo = new oldUserRepository;
         try {
             $user = $repo->create($request->only([
                 'custom_id', 'email', 'password', 'name_first',
@@ -128,7 +128,7 @@ class UserController extends Controller
     {
         $this->authorize('user-edit', $request->apiKey());
 
-        $repo = new UserRepository;
+        $repo = new oldUserRepository;
         try {
             $user = $repo->update($user, $request->intersect([
                 'email', 'password', 'name_first',
@@ -165,7 +165,7 @@ class UserController extends Controller
     {
         $this->authorize('user-delete', $request->apiKey());
 
-        $repo = new UserRepository;
+        $repo = new oldUserRepository;
         try {
             $repo->delete($id);
 
diff --git a/app/Http/Controllers/Admin/OptionController.php b/app/Http/Controllers/Admin/OptionController.php
index cdcf2f3b..afabcfd2 100644
--- a/app/Http/Controllers/Admin/OptionController.php
+++ b/app/Http/Controllers/Admin/OptionController.php
@@ -26,8 +26,10 @@ namespace Pterodactyl\Http\Controllers\Admin;
 
 use Log;
 use Alert;
+use Route;
 use Javascript;
 use Illuminate\Http\Request;
+use Pterodactyl\Http\Requests\Admin\Service\EditOptionScript;
 use Pterodactyl\Models\Service;
 use Pterodactyl\Models\ServiceOption;
 use Pterodactyl\Exceptions\DisplayException;
@@ -39,6 +41,21 @@ use Pterodactyl\Http\Requests\Admin\Service\StoreOptionVariable;
 
 class OptionController extends Controller
 {
+    /**
+     * Store the repository instance.
+     *
+     * @var \Pterodactyl\Repositories\OptionRepository
+     */
+    protected $repository;
+
+    /**
+     * OptionController constructor.
+     */
+    public function __construct()
+    {
+        $this->repository = new OptionRepository(Route::current()->parameter('option'));
+    }
+
     /**
      * Handles request to view page for adding new option.
      *
@@ -227,24 +244,17 @@ class OptionController extends Controller
     }
 
     /**
-     * Handles POST when updating scripts for a service option.
+     * Handles POST when updating script for a service option.
      *
-     * @param  Request $request
-     * @param  int     $id
-     * @return \Illuminate\Response\RedirectResponse
+     * @param \Pterodactyl\Http\Requests\Admin\Service\EditOptionScript $request
+     * @return \Illuminate\Http\RedirectResponse
      */
-    public function updateScripts(Request $request, $id)
+    public function updateScripts(EditOptionScript $request)
     {
-        $repo = new OptionRepository;
-
         try {
-            $repo->scripts($id, $request->only([
-                'script_install', 'script_entry',
-                'script_container', 'copy_script_from',
-            ]));
+            $this->repository->scripts($request->normalize());
+
             Alert::success('Successfully updated option scripts to be run when servers are installed.')->flash();
-        } catch (DisplayValidationException $ex) {
-            return redirect()->route('admin.services.option.scripts', $id)->withErrors(json_decode($ex->getMessage()));
         } catch (DisplayException $ex) {
             Alert::danger($ex->getMessage())->flash();
         } catch (\Exception $ex) {
diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php
index 0e943eb4..1b963228 100644
--- a/app/Http/Controllers/Admin/UserController.php
+++ b/app/Http/Controllers/Admin/UserController.php
@@ -25,17 +25,31 @@
 
 namespace Pterodactyl\Http\Controllers\Admin;
 
-use Log;
 use Alert;
 use Illuminate\Http\Request;
-use Pterodactyl\Models\User;
+use Pterodactyl\Contracts\Repositories\UserInterface;
 use Pterodactyl\Exceptions\DisplayException;
+use Pterodactyl\Http\Requests\Admin\UserFormRequest;
+use Pterodactyl\Models\User;
 use Pterodactyl\Http\Controllers\Controller;
-use Pterodactyl\Repositories\UserRepository;
-use Pterodactyl\Exceptions\DisplayValidationException;
 
 class UserController extends Controller
 {
+    /**
+     * @var \Pterodactyl\Repositories\Eloquent\UserRepository
+     */
+    protected $repository;
+
+    /**
+     * UserController constructor.
+     *
+     * @param \Pterodactyl\Contracts\Repositories\UserInterface $repository
+     */
+    public function __construct(UserInterface $repository)
+    {
+        $this->repository = $repository;
+    }
+
     /**
      * Display user index page.
      *
@@ -44,7 +58,7 @@ class UserController extends Controller
      */
     public function index(Request $request)
     {
-        $users = User::withCount('servers', 'subuserOf');
+        $users = $this->repository->withCount('servers', 'subuserOf');
 
         if (! is_null($request->input('query'))) {
             $users->search($request->input('query'));
@@ -58,10 +72,9 @@ class UserController extends Controller
     /**
      * Display new user page.
      *
-     * @param  \Illuminate\Http\Request  $request
      * @return \Illuminate\View\View
      */
-    public function create(Request $request)
+    public function create()
     {
         return view('admin.users.new');
     }
@@ -69,96 +82,61 @@ class UserController extends Controller
     /**
      * Display user view page.
      *
-     * @param  \Illuminate\Http\Request  $request
-     * @param  int                       $id
+     * @param  \Pterodactyl\Models\User  $user
      * @return \Illuminate\View\View
      */
-    public function view(Request $request, $id)
+    public function view(User $user)
     {
         return view('admin.users.view', [
-            'user' => User::with('servers.node')->findOrFail($id),
+            'user' => $user,
         ]);
     }
 
     /**
-     * Delete a user.
+     * Delete a user from the system.
      *
-     * @param  \Illuminate\Http\Request  $request
-     * @param  int                       $id
+     * @param  \Pterodactyl\Models\User  $user
      * @return \Illuminate\Http\RedirectResponse
      */
-    public function delete(Request $request, $id)
+    public function delete(User $user)
     {
         try {
-            $repo = new UserRepository;
-            $repo->delete($id);
-            Alert::success('Successfully deleted user from system.')->flash();
+            $this->repository->delete($user->id);
 
             return redirect()->route('admin.users');
         } catch (DisplayException $ex) {
             Alert::danger($ex->getMessage())->flash();
-        } catch (\Exception $ex) {
-            Log::error($ex);
-            Alert::danger('An exception was encountered while attempting to delete this user.')->flash();
         }
 
-        return redirect()->route('admin.users.view', $id);
+        return redirect()->route('admin.users.view', $user->id);
     }
 
     /**
      * Create a user.
      *
-     * @param  \Illuminate\Http\Request  $request
+     * @param  \Pterodactyl\Http\Requests\Admin\UserFormRequest  $request
      * @return \Illuminate\Http\RedirectResponse
      */
-    public function store(Request $request)
+    public function store(UserFormRequest $request)
     {
-        try {
-            $user = new UserRepository;
-            $userid = $user->create($request->only([
-                'email', 'password', 'name_first',
-                'name_last', 'username', 'root_admin',
-            ]));
-            Alert::success('Account has been successfully created.')->flash();
+        $user = $this->repository->create($request->normalize());
+        Alert::success('Account has been successfully created.')->flash();
 
-            return redirect()->route('admin.users.view', $userid);
-        } catch (DisplayValidationException $ex) {
-            return redirect()->route('admin.users.new')->withErrors(json_decode($ex->getMessage()))->withInput();
-        } catch (\Exception $ex) {
-            Log::error($ex);
-            Alert::danger('An error occured while attempting to add a new user.')->flash();
-
-            return redirect()->route('admin.users.new');
-        }
+        return redirect()->route('admin.users.view', $user->id);
     }
 
     /**
-     * Update a user.
+     * Update a user on the system.
      *
-     * @param  \Illuminate\Http\Request  $request
-     * @param  int                       $id
+     * @param  \Pterodactyl\Http\Requests\Admin\UserFormRequest  $request
+     * @param  \Pterodactyl\Models\User                          $user
      * @return \Illuminate\Http\RedirectResponse
      */
-    public function update(Request $request, $id)
+    public function update(UserFormRequest $request, User $user)
     {
-        try {
-            $repo = new UserRepository;
-            $user = $repo->update($id, array_merge(
-                $request->only('root_admin'),
-                $request->intersect([
-                    'email', 'password', 'name_first',
-                    'name_last', 'username',
-                ])
-            ));
-            Alert::success('User account was successfully updated.')->flash();
-        } catch (DisplayValidationException $ex) {
-            return redirect()->route('admin.users.view', $id)->withErrors(json_decode($ex->getMessage()));
-        } catch (\Exception $ex) {
-            Log::error($ex);
-            Alert::danger('An error occured while attempting to update this user.')->flash();
-        }
+        $this->repository->update($user->id, $request->normalize());
 
-        return redirect()->route('admin.users.view', $id);
+        return redirect()->route('admin.users.view', $user->id);
     }
 
     /**
@@ -169,12 +147,12 @@ class UserController extends Controller
      */
     public function json(Request $request)
     {
-        return User::select('id', 'email', 'username', 'name_first', 'name_last')
-            ->search($request->input('q'))
-            ->get()->transform(function ($item) {
-                $item->md5 = md5(strtolower($item->email));
+        return $this->repository->search($request->input('q'))->all([
+            'id', 'email', 'username', 'name_first', 'name_last',
+        ])->transform(function ($item) {
+            $item->md5 = md5(strtolower($item->email));
 
-                return $item;
-            });
+            return $item;
+        });
     }
 }
diff --git a/app/Http/Controllers/Base/AccountController.php b/app/Http/Controllers/Base/AccountController.php
index 10c33e38..7163045a 100644
--- a/app/Http/Controllers/Base/AccountController.php
+++ b/app/Http/Controllers/Base/AccountController.php
@@ -30,7 +30,7 @@ use Alert;
 use Illuminate\Http\Request;
 use Pterodactyl\Models\User;
 use Pterodactyl\Http\Controllers\Controller;
-use Pterodactyl\Repositories\UserRepository;
+use Pterodactyl\Repositories\oldUserRepository;
 use Pterodactyl\Exceptions\DisplayValidationException;
 
 class AccountController extends Controller
@@ -90,7 +90,7 @@ class AccountController extends Controller
         }
 
         try {
-            $repo = new UserRepository;
+            $repo = new oldUserRepository;
             $repo->update($request->user()->id, $data);
             Alert::success('Your account details were successfully updated.')->flash();
         } catch (DisplayValidationException $ex) {
diff --git a/app/Http/Requests/Admin/AdminFormRequest.php b/app/Http/Requests/Admin/AdminFormRequest.php
new file mode 100644
index 00000000..e3e0be37
--- /dev/null
+++ b/app/Http/Requests/Admin/AdminFormRequest.php
@@ -0,0 +1,59 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Http\Requests\Admin;
+
+use Pterodactyl\Models\User;
+use Illuminate\Foundation\Http\FormRequest;
+
+abstract class AdminFormRequest extends FormRequest
+{
+    /**
+     * Determine if the user is an admin and has permission to access this
+     * form controller in the first place.
+     *
+     * @return bool
+     */
+    public function authorize()
+    {
+        if (! $this->user() instanceof User) {
+            return false;
+        }
+
+        return $this->user()->isRootAdmin();
+    }
+
+    /**
+     * Return only the fields that we are interested in from the request.
+     * This will include empty fields as a null value.
+     *
+     * @return array
+     */
+    public function normalize()
+    {
+        return $this->only(
+            array_keys($this->rules())
+        );
+    }
+}
diff --git a/app/Http/Requests/Admin/Service/EditOptionScript.php b/app/Http/Requests/Admin/Service/EditOptionScript.php
new file mode 100644
index 00000000..52cfff4e
--- /dev/null
+++ b/app/Http/Requests/Admin/Service/EditOptionScript.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Http\Requests\Admin\Service;
+
+use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
+
+class EditOptionScript extends AdminFormRequest
+{
+    /**
+     * Return the rules to be used when validating the sent data in the request.
+     *
+     * @return array
+     */
+    public function rules()
+    {
+        return [
+            'script_install' => 'sometimes|nullable|string',
+            'script_is_privileged' => 'sometimes|required|boolean',
+            'script_entry' => 'sometimes|required|string',
+            'script_container' => 'sometimes|required|string',
+            'copy_script_from' => 'sometimes|nullable|numeric',
+        ];
+    }
+}
diff --git a/app/Http/Requests/Admin/Service/StoreOptionVariable.php b/app/Http/Requests/Admin/Service/StoreOptionVariable.php
index cb37e6a1..869b2efc 100644
--- a/app/Http/Requests/Admin/Service/StoreOptionVariable.php
+++ b/app/Http/Requests/Admin/Service/StoreOptionVariable.php
@@ -24,25 +24,10 @@
 
 namespace Pterodactyl\Http\Requests\Admin\Service;
 
-use Pterodactyl\Models\User;
-use Illuminate\Foundation\Http\FormRequest;
+use Pterodactyl\Http\Requests\Admin\AdminFormRequest;
 
-class StoreOptionVariable extends FormRequest
+class StoreOptionVariable extends AdminFormRequest
 {
-    /**
-     * Determine if user is allowed to access this request.
-     *
-     * @return bool
-     */
-    public function authorize()
-    {
-        if (! $this->user() instanceof User) {
-            return false;
-        }
-
-        return $this->user()->isRootAdmin();
-    }
-
     /**
      * Set the rules to be used for data passed to the request.
      *
@@ -59,17 +44,4 @@ class StoreOptionVariable extends FormRequest
             'options' => 'sometimes|required|array',
         ];
     }
-
-    /**
-     * Return only the fields that we are interested in from the request.
-     * This will include empty fields as a null value.
-     *
-     * @return array
-     */
-    public function normalize()
-    {
-        return $this->only(
-            array_keys($this->rules())
-        );
-    }
 }
diff --git a/app/Http/Requests/Admin/UserFormRequest.php b/app/Http/Requests/Admin/UserFormRequest.php
new file mode 100644
index 00000000..10d55977
--- /dev/null
+++ b/app/Http/Requests/Admin/UserFormRequest.php
@@ -0,0 +1,79 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Http\Requests\Admin;
+
+use Pterodactyl\Models\User;
+use Illuminate\Support\Facades\Hash;
+use Pterodactyl\Contracts\Repositories\UserInterface;
+
+class UserFormRequest extends AdminFormRequest
+{
+    /**
+     * {@inheritdoc}
+     */
+    public function repository()
+    {
+        return UserInterface::class;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function rules()
+    {
+        if ($this->method() === 'PATCH') {
+            return [
+                'email' => 'sometimes|required|email|unique:users,email,' . $this->user->id,
+                'username' => 'sometimes|required|alpha_dash|between:1,255|unique:users,username, ' . $this->user->id . '|' . User::USERNAME_RULES,
+                'name_first' => 'sometimes|required|string|between:1,255',
+                'name_last' => 'sometimes|required|string|between:1,255',
+                'password' => 'sometimes|nullable|' . User::PASSWORD_RULES,
+                'root_admin' => 'sometimes|required|boolean',
+                'language' => 'sometimes|required|string|min:1|max:5',
+                'use_totp' => 'sometimes|required|boolean',
+                'totp_secret' => 'sometimes|required|size:16',
+            ];
+        }
+
+        return [
+            'email' => 'required|email|unique:users,email,' . $this->user->id,
+            'username' => 'required|alpha_dash|between:1,255|unique:users,username,' . $this->user->id . '|' . User::USERNAME_RULES,
+            'name_first' => 'required|string|between:1,255',
+            'name_last' => 'required|string|between:1,255',
+            'password' => 'sometimes|nullable|' . User::PASSWORD_RULES,
+            'root_admin' => 'required|boolean',
+            'external_id' => 'sometimes|nullable|numeric|unique:users,external_id',
+        ];
+    }
+
+    public function normalize()
+    {
+        if ($this->has('password')) {
+            $this->merge(['password' => Hash::make($this->input('password'))]);
+        }
+
+        return parent::normalize();
+    }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 12504b6b..a4f06f4b 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -117,6 +117,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
      *
      * @param  int  $token
      * @return bool
+     * @deprecated
      */
     public function toggleTotp($token)
     {
@@ -136,9 +137,11 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
      *      - at least one lowercase character
      *      - at least one number.
      *
-     * @param  string  $password
-     * @param  string  $regex
+     * @param  string $password
+     * @param  string $regex
      * @return void
+     * @throws \Pterodactyl\Exceptions\DisplayException
+     * @deprecated
      */
     public function setPassword($password, $regex = '((?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,})')
     {
@@ -165,6 +168,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
      * Return true or false depending on wether the user is root admin or not.
      *
      * @return bool
+     * @deprecated
      */
     public function isRootAdmin()
     {
@@ -256,6 +260,16 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac
         return $query;
     }
 
+    /**
+     * Store the username as a lowecase string.
+     *
+     * @param  string  $value
+     */
+    public function setUsernameAttribute($value)
+    {
+        $this->attributes['username'] = strtolower($value);
+    }
+
     /**
      * Returns all permissions that a user has.
      *
diff --git a/app/Observers/UserObserver.php b/app/Observers/UserObserver.php
index e7b07851..f6e16026 100644
--- a/app/Observers/UserObserver.php
+++ b/app/Observers/UserObserver.php
@@ -30,9 +30,17 @@ use Carbon;
 use Pterodactyl\Events;
 use Pterodactyl\Models\User;
 use Pterodactyl\Notifications\AccountCreated;
+use Pterodactyl\Services\UuidService;
 
 class UserObserver
 {
+    protected $uuid;
+
+    public function __construct(UuidService $uuid)
+    {
+        $this->uuid = $uuid;
+    }
+
     /**
      * Listen to the User creating event.
      *
@@ -41,6 +49,8 @@ class UserObserver
      */
     public function creating(User $user)
     {
+        $user->uuid = $this->uuid->generate();
+
         event(new Events\User\Creating($user));
     }
 
@@ -52,8 +62,7 @@ class UserObserver
      */
     public function created(User $user)
     {
-        event(new Events\User\Created($user));
-
+        dd($user);
         if ($user->password === 'unset') {
             $token = hash_hmac('sha256', str_random(40), config('app.key'));
             DB::table('password_resets')->insert([
@@ -68,6 +77,8 @@ class UserObserver
             'username' => $user->username,
             'token' => (isset($token)) ? $token : null,
         ]));
+
+        event(new Events\User\Created($user));
     }
 
     /**
diff --git a/app/Providers/RepositoryServiceProvider.php b/app/Providers/RepositoryServiceProvider.php
new file mode 100644
index 00000000..745e6d05
--- /dev/null
+++ b/app/Providers/RepositoryServiceProvider.php
@@ -0,0 +1,40 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Providers;
+
+use Illuminate\Support\ServiceProvider;
+use Pterodactyl\Contracts\Repositories\UserInterface;
+use Pterodactyl\Repositories\Eloquent\UserRepository;
+
+class RepositoryServiceProvider extends ServiceProvider
+{
+    /**
+     * Register the repositories.
+     */
+    public function register()
+    {
+        $this->app->bind(UserInterface::class, UserRepository::class);
+    }
+}
diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php
new file mode 100644
index 00000000..2ca9c521
--- /dev/null
+++ b/app/Repositories/Eloquent/UserRepository.php
@@ -0,0 +1,78 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Repositories\Eloquent;
+
+use Pterodactyl\Models\User;
+use Illuminate\Contracts\Auth\Guard;
+use Pterodactyl\Repositories\Repository;
+use Pterodactyl\Exceptions\DisplayException;
+use Pterodactyl\Contracts\Repositories\UserInterface;
+
+class UserRepository extends Repository implements UserInterface
+{
+    /**
+     * Dependencies to automatically inject into the repository.
+     *
+     * @var array
+     */
+    protected $inject = [
+        'guard' => Guard::class,
+    ];
+
+    /**
+     * Return the model to be used for the repository.
+     *
+     * @return string
+     */
+    public function model()
+    {
+        return User::class;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function search($term)
+    {
+        $this->model->search($term);
+
+        return $this;
+    }
+
+    public function delete($id)
+    {
+        $user = $this->model->withCount('servers')->find($id);
+
+        if ($this->guard->user() && $this->guard->user()->id === $user->id) {
+            throw new DisplayException('You cannot delete your own account.');
+        }
+
+        if ($user->server_count > 0) {
+            throw new DisplayException('Cannot delete an account that has active servers attached to it.');
+        }
+
+        return $user->delete();
+    }
+}
diff --git a/app/Repositories/APIRepository.php b/app/Repositories/Old/APIRepository.php
similarity index 100%
rename from app/Repositories/APIRepository.php
rename to app/Repositories/Old/APIRepository.php
diff --git a/app/Repositories/DatabaseRepository.php b/app/Repositories/Old/DatabaseRepository.php
similarity index 100%
rename from app/Repositories/DatabaseRepository.php
rename to app/Repositories/Old/DatabaseRepository.php
diff --git a/app/Repositories/HelperRepository.php b/app/Repositories/Old/HelperRepository.php
similarity index 100%
rename from app/Repositories/HelperRepository.php
rename to app/Repositories/Old/HelperRepository.php
diff --git a/app/Repositories/LocationRepository.php b/app/Repositories/Old/LocationRepository.php
similarity index 100%
rename from app/Repositories/LocationRepository.php
rename to app/Repositories/Old/LocationRepository.php
diff --git a/app/Repositories/NodeRepository.php b/app/Repositories/Old/NodeRepository.php
similarity index 100%
rename from app/Repositories/NodeRepository.php
rename to app/Repositories/Old/NodeRepository.php
diff --git a/app/Repositories/OptionRepository.php b/app/Repositories/Old/OptionRepository.php
similarity index 79%
rename from app/Repositories/OptionRepository.php
rename to app/Repositories/Old/OptionRepository.php
index 1a0ce450..6e99583c 100644
--- a/app/Repositories/OptionRepository.php
+++ b/app/Repositories/Old/OptionRepository.php
@@ -26,12 +26,67 @@ namespace Pterodactyl\Repositories;
 
 use DB;
 use Validator;
+use InvalidArgumentException;
 use Pterodactyl\Models\ServiceOption;
 use Pterodactyl\Exceptions\DisplayException;
 use Pterodactyl\Exceptions\DisplayValidationException;
 
 class OptionRepository
 {
+    /**
+     * Store the requested service option.
+     *
+     * @var \Pterodactyl\Models\ServiceOption
+     */
+    protected $model;
+
+    /**
+     * OptionRepository constructor.
+     *
+     * @param  null|int|\Pterodactyl\Models\ServiceOption  $option
+     */
+    public function __construct($option = null)
+    {
+        if (is_null($option)) {
+            return;
+        }
+
+        if ($option instanceof ServiceOption) {
+            $this->model = $option;
+        } else {
+            if (! is_numeric($option)) {
+                throw new InvalidArgumentException(
+                    sprintf('Variable passed to constructor must be integer or instance of \Pterodactyl\Models\ServiceOption.')
+                );
+            }
+
+            $this->model = ServiceOption::findOrFail($option);
+        }
+    }
+
+    /**
+     * Return the eloquent model for the given repository.
+     *
+     * @return null|\Pterodactyl\Models\ServiceOption
+     */
+    public function getModel()
+    {
+        return $this->model;
+    }
+
+    /**
+     * Update the currently assigned model by re-initalizing the class.
+     *
+     * @param  null|int|\Pterodactyl\Models\ServiceOption $option
+     * @return $this
+     */
+    public function setModel($option)
+    {
+        self::__construct($option);
+
+        return $this;
+    }
+
     /**
      * Creates a new service option on the system.
      *
@@ -67,7 +122,7 @@ class OptionRepository
             }
         }
 
-        return ServiceOption::create($data);
+        return $this->setModel(ServiceOption::create($data))->getModel();
     }
 
     /**
@@ -76,13 +131,15 @@ class OptionRepository
      * @param  int  $id
      * @return void
      *
+     * @throws \Exception
      * @throws \Pterodactyl\Exceptions\DisplayException
+     * @throws \Throwable
      */
     public function delete($id)
     {
-        $option = ServiceOption::with('variables')->withCount('servers')->findOrFail($id);
+        $this->model->load('variables', 'servers');
 
-        if ($option->servers_count > 0) {
+        if ($this->model->servers->count() > 0) {
             throw new DisplayException('You cannot delete a service option that has servers associated with it.');
         }
 
@@ -158,32 +215,19 @@ class OptionRepository
     /**
      * Updates a service option's scripts in the database.
      *
-     * @param  int    $id
      * @param  array  $data
-     * @return \Pterodactyl\Models\ServiceOption
      *
      * @throws \Pterodactyl\Exceptions\DisplayException
-     * @throws \Pterodactyl\Exceptions\DisplayValidationException
      */
-    public function scripts($id, array $data)
+    public function scripts(array $data)
     {
-        $option = ServiceOption::findOrFail($id);
-
         $data['script_install'] = empty($data['script_install']) ? null : $data['script_install'];
 
-        $validator = Validator::make($data, [
-            'script_install' => 'sometimes|nullable|string',
-            'script_is_privileged' => 'sometimes|required|boolean',
-            'script_entry' => 'sometimes|required|string',
-            'script_container' => 'sometimes|required|string',
-            'copy_script_from' => 'sometimes|nullable|numeric',
-        ]);
-
         if (isset($data['copy_script_from']) && ! empty($data['copy_script_from'])) {
-            $select = ServiceOption::whereNull('copy_script_from')->where([
-                ['id', $data['copy_script_from']],
-                ['service_id', $option->service_id],
-            ])->first();
+            $select = ServiceOption::whereNull('copy_script_from')
+                ->where('id', $data['copy_script_from'])
+                ->where('service_id', $this->model->service_id)
+                ->first();
 
             if (! $select) {
                 throw new DisplayException('The service option selected to copy a script from either does not exist, or is copying from a higher level.');
@@ -192,12 +236,6 @@ class OptionRepository
             $data['copy_script_from'] = null;
         }
 
-        if ($validator->fails()) {
-            throw new DisplayValidationException(json_encode($validator->errors()));
-        }
-
-        $option->fill($data)->save();
-
-        return $option;
+        $this->model->fill($data)->save();
     }
 }
diff --git a/app/Repositories/PackRepository.php b/app/Repositories/Old/PackRepository.php
similarity index 100%
rename from app/Repositories/PackRepository.php
rename to app/Repositories/Old/PackRepository.php
diff --git a/app/Repositories/ServiceRepository.php b/app/Repositories/Old/ServiceRepository.php
similarity index 100%
rename from app/Repositories/ServiceRepository.php
rename to app/Repositories/Old/ServiceRepository.php
diff --git a/app/Repositories/SubuserRepository.php b/app/Repositories/Old/SubuserRepository.php
similarity index 99%
rename from app/Repositories/SubuserRepository.php
rename to app/Repositories/Old/SubuserRepository.php
index 7a2aef8c..b7d73655 100644
--- a/app/Repositories/SubuserRepository.php
+++ b/app/Repositories/Old/SubuserRepository.php
@@ -79,7 +79,7 @@ class SubuserRepository
             $user = User::where('email', $data['email'])->first();
             if (! $user) {
                 try {
-                    $repo = new UserRepository;
+                    $repo = new oldUserRepository;
                     $user = $repo->create([
                         'email' => $data['email'],
                         'username' => str_random(8),
diff --git a/app/Repositories/TaskRepository.php b/app/Repositories/Old/TaskRepository.php
similarity index 100%
rename from app/Repositories/TaskRepository.php
rename to app/Repositories/Old/TaskRepository.php
diff --git a/app/Repositories/VariableRepository.php b/app/Repositories/Old/VariableRepository.php
similarity index 100%
rename from app/Repositories/VariableRepository.php
rename to app/Repositories/Old/VariableRepository.php
diff --git a/app/Repositories/ServerRepository.php b/app/Repositories/Old/old_ServerRepository.php
similarity index 99%
rename from app/Repositories/ServerRepository.php
rename to app/Repositories/Old/old_ServerRepository.php
index c13613bf..e7547404 100644
--- a/app/Repositories/ServerRepository.php
+++ b/app/Repositories/Old/old_ServerRepository.php
@@ -43,7 +43,7 @@ use Pterodactyl\Services\DeploymentService;
 use Pterodactyl\Exceptions\DisplayException;
 use Pterodactyl\Exceptions\DisplayValidationException;
 
-class ServerRepository
+class old_ServerRepository
 {
     /**
      * An array of daemon permission to assign to this server.
diff --git a/app/Repositories/UserRepository.php b/app/Repositories/Old/old_UserRepository.php
similarity index 99%
rename from app/Repositories/UserRepository.php
rename to app/Repositories/Old/old_UserRepository.php
index 05a1a53c..06538a4d 100644
--- a/app/Repositories/UserRepository.php
+++ b/app/Repositories/Old/old_UserRepository.php
@@ -35,7 +35,7 @@ use Pterodactyl\Services\UuidService;
 use Pterodactyl\Exceptions\DisplayException;
 use Pterodactyl\Exceptions\DisplayValidationException;
 
-class UserRepository
+class old_UserRepository
 {
     /**
      * Creates a user on the panel. Returns the created user's ID.
diff --git a/app/Repositories/Repository.php b/app/Repositories/Repository.php
new file mode 100644
index 00000000..37e2c421
--- /dev/null
+++ b/app/Repositories/Repository.php
@@ -0,0 +1,229 @@
+<?php
+/*
+ * Pterodactyl - Panel
+ * Copyright (c) 2015 - 2017 Dane Everitt <dane@daneeveritt.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+namespace Pterodactyl\Repositories;
+
+use Illuminate\Container\Container;
+use Illuminate\Database\Eloquent\Model;
+use Pterodactyl\Contracts\Repositories\RepositoryInterface;
+use Pterodactyl\Exceptions\Repository\RepositoryException;
+
+abstract class Repository implements RepositoryInterface
+{
+    const RULE_UPDATED = 'updated';
+    const RULE_CREATED = 'created';
+
+    /**
+     * @var \Illuminate\Container\Container
+     */
+    protected $container;
+
+    /**
+     * Array of classes to inject automatically into the container.
+     *
+     * @var array
+     */
+    protected $inject = [];
+
+    /**
+     * @var \Illuminate\Database\Eloquent\Model
+     */
+    protected $model;
+
+    /**
+     * Array of validation rules that can be accessed from this repository.
+     *
+     * @var array
+     */
+    protected $rules = [];
+
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct(Container $container)
+    {
+        $this->container = $container;
+
+        foreach ($this->inject as $key => $value) {
+            if (isset($this->{$key})) {
+                throw new \Exception('Cannot override a defined object in this class.');
+            }
+
+            $this->{$key} = $this->container->make($value);
+        }
+
+        $this->makeModel();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    abstract public function model();
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getModel()
+    {
+        return $this->model;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function makeModel()
+    {
+        $model = $this->container->make($this->model());
+
+        if (! $model instanceof Model) {
+            throw new RepositoryException(
+                "Class {$this->model()} must be an instance of \\Illuminate\\Database\\Eloquent\\Model"
+            );
+        }
+
+        return $this->model = $model->newQuery();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRules()
+    {
+        return $this->rules;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getUpdateRules()
+    {
+        if (array_key_exists(self::RULE_UPDATED, $this->rules)) {
+            return $this->rules[self::RULE_UPDATED];
+        }
+
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getCreateRules()
+    {
+        if (array_key_exists(self::RULE_CREATED, $this->rules)) {
+            return $this->rules[self::RULE_CREATED];
+        }
+
+        return [];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function with(...$params)
+    {
+        $this->model = $this->model->with($params);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function withCount(...$params)
+    {
+        $this->model = $this->model->withCount($params);
+
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function all(array $columns = ['*'])
+    {
+        return $this->model->get($columns);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function paginate($limit = 15, array $columns = ['*'])
+    {
+        return $this->model->paginate($limit, $columns);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function create(array $data)
+    {
+        return $this->model->create($data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function update($attributes, array $data)
+    {
+        // If only a number is passed, we assume it is an ID
+        // for the specific model at hand.
+        if (is_numeric($attributes)) {
+            $attributes = [['id', '=', $attributes]];
+        }
+
+        return $this->model->where($attributes)->get()->each->update($data);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function delete($id)
+    {
+        return $this->model->find($id)->delete();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function destroy($id)
+    {
+        return $this->model->find($id)->forceDelete();
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function find($id, array $columns = ['*'])
+    {
+        return $this->model->find($id, $columns);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function findBy(array $attributes, array $columns = ['*'])
+    {
+        return $this->model->where($attributes)->first($columns);
+    }
+}
diff --git a/config/app.php b/config/app.php
index 3c3fb772..0b6dbeeb 100644
--- a/config/app.php
+++ b/config/app.php
@@ -163,6 +163,7 @@ return [
         Pterodactyl\Providers\AppServiceProvider::class,
         Pterodactyl\Providers\AuthServiceProvider::class,
         Pterodactyl\Providers\EventServiceProvider::class,
+        Pterodactyl\Providers\RepositoryServiceProvider::class,
         Pterodactyl\Providers\RouteServiceProvider::class,
         Pterodactyl\Providers\MacroServiceProvider::class,
         Pterodactyl\Providers\PhraseAppTranslationProvider::class,
diff --git a/database/migrations/2017_06_10_152951_add_external_id_to_users.php b/database/migrations/2017_06_10_152951_add_external_id_to_users.php
new file mode 100644
index 00000000..696f10f4
--- /dev/null
+++ b/database/migrations/2017_06_10_152951_add_external_id_to_users.php
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Support\Facades\Schema;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Database\Migrations\Migration;
+
+class AddExternalIdToUsers extends Migration
+{
+    /**
+     * Run the migrations.
+     *
+     * @return void
+     */
+    public function up()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->unsignedInteger('external_id')->after('id')->nullable()->unique();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     *
+     * @return void
+     */
+    public function down()
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->dropColumn('external_id');
+        });
+    }
+}
diff --git a/resources/themes/pterodactyl/admin/users/view.blade.php b/resources/themes/pterodactyl/admin/users/view.blade.php
index 39461e46..3e23bb26 100644
--- a/resources/themes/pterodactyl/admin/users/view.blade.php
+++ b/resources/themes/pterodactyl/admin/users/view.blade.php
@@ -68,6 +68,7 @@
                 </div>
                 <div class="box-footer">
                     {!! csrf_field() !!}
+                    {!! method_field('PATCH') !!}
                     <input type="submit" value="Update User" class="btn btn-primary btn-sm">
                 </div>
             </div>
diff --git a/routes/admin.php b/routes/admin.php
index 7b6c459e..2fe4a0b2 100644
--- a/routes/admin.php
+++ b/routes/admin.php
@@ -81,12 +81,12 @@ Route::group(['prefix' => 'users'], function () {
     Route::get('/', 'UserController@index')->name('admin.users');
     Route::get('/accounts.json', 'UserController@json')->name('admin.users.json');
     Route::get('/new', 'UserController@create')->name('admin.users.new');
-    Route::get('/view/{id}', 'UserController@view')->name('admin.users.view');
+    Route::get('/view/{user}', 'UserController@view')->name('admin.users.view');
 
     Route::post('/new', 'UserController@store');
-    Route::post('/view/{id}', 'UserController@update');
+    Route::patch('/view/{user}', 'UserController@update');
 
-    Route::delete('/view/{id}', 'UserController@delete');
+    Route::delete('/view/{user}', 'UserController@delete');
 });
 
 /*
@@ -168,17 +168,17 @@ Route::group(['prefix' => 'services'], function () {
     Route::get('/view/{id}', 'ServiceController@view')->name('admin.services.view');
     Route::get('/view/{id}/functions', 'ServiceController@viewFunctions')->name('admin.services.view.functions');
     Route::get('/option/new', 'OptionController@create')->name('admin.services.option.new');
-    Route::get('/option/{id}', 'OptionController@viewConfiguration')->name('admin.services.option.view');
-    Route::get('/option/{id}/variables', 'OptionController@viewVariables')->name('admin.services.option.variables');
-    Route::get('/option/{id}/scripts', 'OptionController@viewScripts')->name('admin.services.option.scripts');
+    Route::get('/option/{option}', 'OptionController@viewConfiguration')->name('admin.services.option.view');
+    Route::get('/option/{option}/variables', 'OptionController@viewVariables')->name('admin.services.option.variables');
+    Route::get('/option/{option}/scripts', 'OptionController@viewScripts')->name('admin.services.option.scripts');
 
     Route::post('/new', 'ServiceController@store');
-    Route::post('/view/{id}', 'ServiceController@edit');
+    Route::post('/view/{option}', 'ServiceController@edit');
     Route::post('/option/new', 'OptionController@store');
-    Route::post('/option/{id}', 'OptionController@editConfiguration');
-    Route::post('/option/{id}/scripts', 'OptionController@updateScripts');
-    Route::post('/option/{id}/variables', 'OptionController@createVariable');
-    Route::post('/option/{id}/variables/{variable}', 'OptionController@editVariable')->name('admin.services.option.variables.edit');
+    Route::post('/option/{option}', 'OptionController@editConfiguration');
+    Route::post('/option/{option}/scripts', 'OptionController@updateScripts');
+    Route::post('/option/{option}/variables', 'OptionController@createVariable');
+    Route::post('/option/{option}/variables/{variable}', 'OptionController@editVariable')->name('admin.services.option.variables.edit');
 
     Route::delete('/view/{id}', 'ServiceController@delete');
 });