digitalbiblesociety/dbp

View on GitHub
app/Http/Controllers/User/UsersController.php

Summary

Maintainability
B
5 hrs
Test Coverage
<?php

namespace App\Http\Controllers\User;

use App\Http\Controllers\APIController;
use App\Mail\ProjectVerificationEmail;

use App\Models\User\Project;
use App\Models\User\ProjectOauthProvider;
use App\Models\User\ProjectMember;
use App\Models\User\Account;
use App\Models\User\Role;
use App\Models\User\User;
use App\Models\User\Key;

use App\Transformers\Serializers\DataArraySerializer;
use App\Transformers\UserTransformer;
use Illuminate\Http\Request;

use Socialite;
use Image;
use Mail;
use Validator;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;

use League\Fractal\Pagination\IlluminatePaginatorAdapter;

class UsersController extends APIController
{

    /**
     * Returns an index of all users within the system
     *
     * @OA\Get(
     *     path="/users",
     *     tags={"Users"},
     *     summary="",
     *     description="",
     *     operationId="v4_user.index",
     *     @OA\Parameter(ref="#/components/parameters/version_number"),
     *     @OA\Parameter(ref="#/components/parameters/key"),
     *     @OA\Parameter(ref="#/components/parameters/pretty"),
     *     @OA\Parameter(ref="#/components/parameters/format"),
     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\MediaType(mediaType="application/json", @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="application/xml",  @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="text/x-yaml",      @OA\Schema(ref="#/components/schemas/v4_user_index"))
     *     )
     * )
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|mixed
     */
    public function index()
    {
        if (!$this->api) {
            return view('dashboard.users.index');
        }
        $limit = checkParam('limit') ?? 100;
        $project_id = checkParam('project_id');

        $users = \DB::table('users')->join('project_members', function ($join) use ($project_id) {
            $join->on('users.id', 'project_members.user_id')->where('project_members.project_id', $project_id);
        })->select(['id','name','email'])->paginate($limit);

        $userCollection = $users->getCollection();
        $userPagination = new IlluminatePaginatorAdapter($users);
        return $this->reply(fractal($userCollection, UserTransformer::class, $this->serializer)->paginateWith($userPagination));
    }

    /**
     *
     * @OA\Get(
     *     path="/users/{id}",
     *     tags={"Users"},
     *     summary="Returns a single user",
     *     description="",
     *     operationId="v4_user.show",
     *     @OA\Parameter(name="id", in="path", description="The user ID for which to retrieve info.", required=true, @OA\Schema(ref="#/components/schemas/User/properties/id")),
     *     @OA\Parameter(ref="#/components/parameters/version_number"),
     *     @OA\Parameter(ref="#/components/parameters/key"),
     *     @OA\Parameter(ref="#/components/parameters/pretty"),
     *     @OA\Parameter(ref="#/components/parameters/format"),
     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\MediaType(mediaType="application/json", @OA\Schema(ref="#/components/schemas/v4_user_show")),
     *         @OA\MediaType(mediaType="application/xml",  @OA\Schema(ref="#/components/schemas/v4_user_show")),
     *         @OA\MediaType(mediaType="text/x-yaml",      @OA\Schema(ref="#/components/schemas/v4_user_show"))
     *     )
     * )
     *
     * @param $id
     *
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View|mixed
     */
    public function show($id)
    {
        $available_projects = $this->availableProjects();

        $user = User::with('accounts', 'organizations', 'profile')
            ->whereHas('projectMembers', function ($query) use ($available_projects) {
                    $query->whereIn('project_id', $available_projects);
            })->where('id', $id)->first();
        if (!$user) {
            return $this->replyWithError(trans('api.users_errors_404', ['param' => $id]));
        }

        if (!$this->api) {
            return view('dashboard.users.show', compact('user'));
        }
        return $this->reply(fractal($user, UserTransformer::class));
    }

    public function edit($id)
    {
        $authorized_user = $this->unauthorizedToAlterUsers();
        if (!$authorized_user) {
            return $this->setStatusCode(401)->replyWithError(trans('auth.not_logged_in'));
        }
        $user = User::with('organizations.currentTranslation')->where('id', $id)->first();

        return view('dashboard.users.edit', compact('user'));
    }

    public function create()
    {
        $project = Project::where('name', 'Digital Bible Platform')->first();
        return view('auth.register', compact('project'));
    }

    /**
     *
     * @OA\Post(
     *     path="/users/login",
     *     tags={"Users"},
     *     summary="Login a user",
     *     description="",
     *     operationId="v4_user.login",
     *     @OA\Parameter(ref="#/components/parameters/version_number"),
     *     @OA\Parameter(ref="#/components/parameters/key"),
     *     @OA\Parameter(ref="#/components/parameters/pretty"),
     *     @OA\Parameter(ref="#/components/parameters/format"),
     *     @OA\RequestBody(required=true, description="Either the `email` & `password` or the `social_provider_user_id` & `social_provider_id` are required for user Login", @OA\MediaType(mediaType="application/json",
     *          @OA\Schema(
     *              @OA\Property(property="email",                     ref="#/components/schemas/User/properties/email"),
     *              @OA\Property(property="password",                  ref="#/components/schemas/User/properties/password"),
     *              @OA\Property(property="project_id",                ref="#/components/schemas/Project/properties/id"),
     *              @OA\Property(property="social_provider_user_id",   ref="#/components/schemas/Account/properties/provider_user_id"),
     *              @OA\Property(property="social_provider_id",        ref="#/components/schemas/Account/properties/provider_id"),
     *          )
     *     )),
     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\MediaType(mediaType="application/json", @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="application/xml",  @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="text/x-yaml",      @OA\Schema(ref="#/components/schemas/v4_user_index"))
     *     )
     * )
     *
     * @param Request $request
     *
     * @return mixed
     */
    public function login(Request $request)
    {
        if (!$this->api && $request->method() !== 'POST') {
            return view('auth.login');
        }

        $user = User::with('accounts')->where('email', $request->email)->first();
        if (!$user) {
            return $this->setStatusCode(404)->replyWithError(trans('api.users_errors_404_email'));
        }

        $oldPassword = \Hash::check(md5($request->password), $user->password);
        $newPassword = \Hash::check($request->password, $user->password);

        if ($oldPassword || $newPassword) {
            // Associate user with Project
            $project_id = checkParam('project_id');
            if ($project_id) {
                $connection_exists = ProjectMember::where(['user_id' => $user->id, 'project_id' => $project_id])->exists();
                if (!$connection_exists) {
                    $role = Role::where('slug', 'user')->first();
                    ProjectMember::create([
                        'user_id'    => $user->id,
                        'project_id' => $project_id,
                        'role_id'    => $role->id ?? 'user'
                    ]);
                }
            }

            if ($this->api) {
                return $user;
            }

            Auth::login($user, true);
            return redirect()->to('dashboard');
        }

        return $this->setStatusCode(401)->replyWithError(trans('auth.failed'));
    }

    /**
     *
     * @OA\Post(
     *     path="/users",
     *     tags={"Users"},
     *     summary="Create a new user",
     *     description="",
     *     operationId="v4_user.store",
     *     @OA\Parameter(ref="#/components/parameters/version_number"),
     *     @OA\Parameter(ref="#/components/parameters/key"),
     *     @OA\Parameter(ref="#/components/parameters/pretty"),
     *     @OA\Parameter(ref="#/components/parameters/format"),
     *     @OA\RequestBody(required=true, description="Information supplied for user creation", @OA\MediaType(mediaType="application/json",
     *          @OA\Schema(
     *              @OA\Property(property="email",                   ref="#/components/schemas/User/properties/email"),
     *              @OA\Property(property="name",                    ref="#/components/schemas/User/properties/name"),
     *              @OA\Property(property="password",                ref="#/components/schemas/User/properties/password"),
     *              @OA\Property(property="project_id",              ref="#/components/schemas/ProjectMember/properties/project_id"),
     *              @OA\Property(property="subscribed",              ref="#/components/schemas/ProjectMember/properties/subscribed"),
     *              @OA\Property(property="social_provider_id",      ref="#/components/schemas/Account/properties/provider_id"),
     *              @OA\Property(property="social_provider_user_id", ref="#/components/schemas/Account/properties/provider_user_id"),
     *          )
     *     )),
     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\MediaType(mediaType="application/json", @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="application/xml",  @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="text/x-yaml",      @OA\Schema(ref="#/components/schemas/v4_user_index"))
     *     )
     * )
     *
     * @param Request $request
     *
     * @return mixed
     */
    public function store(Request $request)
    {
        $invalid = $this->validateUser();
        if ($invalid) {
            return $invalid;
        }

        $user = User::create([
            'email'         => $request->email,
            'name'          => $request->name,
            'first_name'    => $request->first_name,
            'last_name'     => $request->last_name,
            'token'         => unique_random('dbp_users.users', 'token'),
            'activated'     => 0,
            'notes'         => $request->notes,
            'password'      => \Hash::make($request->password),
        ]);
        if ($request->project_id) {
            $user_role = Role::where('slug', 'user')->first();
            if (!$user_role) {
                return $this->setStatusCode(404)->replyWithError('The Roles table has not been populated');
            }
            $user->projectMembers()->create([
                'project_id' => $request->project_id,
                'role_id'    => $user_role->id,
                'subscribed' => $request->subscribed ?? 0,
            ]);
        }
        if ($request->social_provider_id) {
            $user->accounts()->create([
                'provider_id'      => $request->social_provider_id,
                'provider_user_id' => $request->social_provider_user_id,
            ]);
        }

        if(!$this->api) {
            Auth::login($user, true);
            return redirect()->to('home');
        }

        return $this->setStatusCode(200)->reply(fractal($user, new UserTransformer())->addMeta(['success' => 'User created']));
    }

    /**
     *
     * @OA\Put(
     *     path="/users/{id}",
     *     tags={"Users"},
     *     summary="Update an existing user",
     *     description="",
     *     operationId="v4_user.update",
     *     @OA\Parameter(ref="#/components/parameters/version_number"),
     *     @OA\Parameter(ref="#/components/parameters/key"),
     *     @OA\Parameter(ref="#/components/parameters/pretty"),
     *     @OA\Parameter(ref="#/components/parameters/format"),
     *     @OA\Parameter(name="id", in="path", description="The user ID for which to retrieve info.", required=true, @OA\Schema(ref="#/components/schemas/User/properties/id")),
     *     @OA\RequestBody(required=true, description="Information supplied for updating an existing user", @OA\MediaType(mediaType="application/json",
     *          @OA\Schema(
     *              @OA\Property(property="nickname",                ref="#/components/schemas/User/properties/nickname"),
     *              @OA\Property(property="avatar",                  ref="#/components/schemas/User/properties/avatar"),
     *              @OA\Property(property="email",                   ref="#/components/schemas/User/properties/email"),
     *              @OA\Property(property="name",                    ref="#/components/schemas/User/properties/name"),
     *              @OA\Property(property="password",                ref="#/components/schemas/User/properties/password"),
     *              @OA\Property(property="project_id",              ref="#/components/schemas/ProjectMember/properties/project_id"),
     *              @OA\Property(property="subscribed",              ref="#/components/schemas/ProjectMember/properties/subscribed"),
     *              @OA\Property(property="social_provider_id",      ref="#/components/schemas/Account/properties/provider_id"),
     *              @OA\Property(property="social_provider_user_id", ref="#/components/schemas/Account/properties/provider_user_id"),
     *          )
     *     )),
     *     @OA\Response(
     *         response=200,
     *         description="successful operation",
     *         @OA\MediaType(mediaType="application/json", @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="application/xml",  @OA\Schema(ref="#/components/schemas/v4_user_index")),
     *         @OA\MediaType(mediaType="text/x-yaml",      @OA\Schema(ref="#/components/schemas/v4_user_index"))
     *     )
     * )
     *
     * @param Request $request
     * @param         $id
     *
     * @return mixed
     */
    public function update(Request $request, $id)
    {
        if ((int) $request->v === 1) {
            return redirect('http://api.dbp.test/login?reply=json');
        }
        // Validate Request
        $invalidRequest = $this->validateUser();
        if ($invalidRequest) {
            return $invalidRequest;
        }

        // Validate User
        $unauthorized_user = $this->unauthorizedToAlterUsers();
        if ($unauthorized_user) {
            return $unauthorized_user;
        }

        // Retrieve User
        $user = User::with('projects')->where('email', request()->email)->first();
        if (!$user) {
            return $this->setStatusCode(404)->replyWithError(trans('api.users_errors_404_email', ['email' => request()->email], $GLOBALS['i18n_iso']));
        }

        // If the request does not originate from an admin
            $user_projects = $user->projects->pluck('id');
            $developer_projects = $this->user->projectMembers->whereIn('role_id', [2,3,4])->pluck('project_id');

            if (!$developer_projects->contains(request()->project_id)) {
                return $this->setStatusCode(401)->replyWithError(trans('api.projects_developer_not_a_member', [], $GLOBALS['i18n_iso']));
            }

            if ($developer_projects->intersect($user_projects)->count() === 0) {
                $project = Project::where('id', request()->project_id)->first();
                if (!$project) {
                    return $this->setStatusCode(404)->replyWithError(trans('api.projects_404'));
                }
                $connection = $user->projectMembers()->create([
                    'user_id'       => $user->id,
                    'project_id'    => $project->id,
                    'role_id'       => 'user',
                    'token'         => unique_random(config('database.connections.dbp_users.database').'.project_members', 'token'),
                    'subscribed'    => false
                ]);

                Mail::to($user->email)->send(new ProjectVerificationEmail($connection, $project));
                return $this->setStatusCode(401)->replyWithError(trans('api.projects_users_needs_to_connect', [], $GLOBALS['i18n_iso']));
        }

        // Fetch Data
        $user->fill($request->except(['v','key','pretty','project_id']))->save();

        if ($this->api) {
            return $this->reply(['success' => 'User updated', 'user' => $user]);
        }
        return view('dashboard.users.show', $id);
    }

    public function destroy($id)
    {
        $project_id = checkParam('project_id');
        $connection = ProjectMember::where('user_id', $id)->where('project_id', $project_id)->first();
        if (!$connection) {
            return $this->setStatusCode(404)->replyWithError('User/Project connection not found');
        }
        $connection->delete();

        return $this->reply('User Project connection successfully removed');
    }

    public function verify($token)
    {
        $user = User::where('email_token', $token)->first();
        if (!$user) {
            return $this->setStatusCode(404)->replyWithError(trans('api.users_errors_404', ['param' => $token]));
        }
        $user->verified = 1;
        $user->save();
        \Auth::login($user);
        $this->guard()->login($user);
        return redirect()->route('home');
    }

    /**
     * @return bool
     */
    private function unauthorizedToAlterUsers()
    {
        if ($this->user === null) {
            return $this->setStatusCode(401)->replyWithError(trans('api.auth_key_validation_failed'));
        }
        $developer = $this->user->projectMembers->where('slug', 'developer')->first();
        if ($developer !== null) {
            return $this->setStatusCode(401)->replyWithError(trans('api.auth_user_validation_failed'));
        }
        return false;
    }


    private function availableProjects()
    {
        $role = Role::where('slug', 'developer')->first();
        if (!$role) {
            return $this->setStatusCode(404)->replyWithError('The Roles table has not been populated');
        }
        $user = $this->user;
        if (!$user) {
            $user = Key::whereKey($this->key)->first()->user;
        }
        $userWithProjects = $user->load(['projectMembers' => function ($query) use ($role) {
            $query->where('role_id', $role->id);
        }]);

        return $userWithProjects->projectMembers->pluck('project_id')->toArray();
    }

    /**
     * Return Validate User
     * If the user is invalid return the errors, otherwise return false.
     *
     * @return Validator|bool
     */
    private function validateUser()
    {
        $validator = Validator::make(request()->all(), [
            'email'                   => (request()->method() === 'POST') ? 'required|unique:dbp_users.users,email' : 'required|exists:dbp_users.users,email',
            'project_id'              => 'required|exists:dbp_users.projects,id',
            'social_provider_id'      => 'required_with:social_provider_user_id',
            'social_provider_user_id' => 'required_with:social_provider_id',
            'name'                    => 'string|max:191',
            'first_name'              => 'string|max:64',
            'last_name'               => 'string|max:64',
            'remember_token'          => 'max:100',
            'verified'                => 'boolean'
        ]);

        if ($validator->fails()) {
            return $this->replyWithError($validator->errors());
        }
        return false;
    }
}