User API Tests

<?php

class UserCest
{
    public function _before(ApiTester $I)
    {
    }

    public function registerUser(ApiTester $I)
    {
        $userClass = \App\Entity\User::class;

        $registerUser = [
            'username' => 'codeception_username',
            'email' => 'codeception_email@jonnydevine.com',
            'password' => 'codeception',
            'name' => 'Codeception Name'
        ];

        $I->dontSeeInRepository($userClass, $registerUser);

        $I->haveHttpHeader('Content-Type', 'application/json');
        $I->sendPOST('/api/v1/user', $registerUser);

        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $expectedResponse = [
            'username' => $registerUser['username']
        ];
        $I->seeResponseContainsJson($expectedResponse);
    }

    public function registerExistingUser(ApiTester $I)
    {
        $userClass = \App\Entity\User::class;

        $registerUser = [
            'username' => 'codeception_username',
            'email' => 'codeception_email@jonnydevine.com',
            'name' => 'Codeception Name'
        ];

        $I->seeInRepository($userClass, $registerUser);

        $I->haveHttpHeader('Content-Type', 'application/json');
        $I->sendPOST('/api/v1/user', $registerUser);

        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseIsJson();

        $I->seeResponseContains("USERNAME_IS_ALREADY_IN_USE");
        $I->seeResponseContains("EMAIL_IS_ALREADY_IN_USE");
    }

    public function registerButAlreadyLoggedIn(ApiTester $I)
    {
        $userClass = \App\Entity\User::class;
        $clientClass = \App\Entity\Client::class;

        $username = 'user2';
        $password = 'password';

        $I->seeInRepository($userClass, [
            'username' => $username,
            'enabled' => true
        ]);

        $I->seeInRepository($clientClass, [
            'randomId' => '5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s'
        ]);

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('/oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        $registerUser = [
            'username' => 'codeception_username',
            'email' => 'codeception_email@jonnydevine.com',
            'name' => 'Codeception Name'
        ];

        $I->sendPOST('/api/v1/user', $registerUser);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseIsJson();

        $I->seeResponseContains("Already Logged In");
    }

    public function registerInvalidEmail(ApiTester $I)
    {
        $userClass = \App\Entity\User::class;

        $registerUser = [
            'username' => 'codeception_username_2',
            'email' => 'codeception_email',
            'password' => 'codeception_2',
            'name' => 'Codeception Name 2'
        ];

        $I->dontSeeInRepository($userClass, $registerUser);

        $I->haveHttpHeader('Content-Type', 'application/json');
        $I->sendPOST('/api/v1/user', $registerUser);

        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseIsJson();

        $I->seeResponseContains("Not an email address");
    }

    public function meLoggedInDisabled(ApiTester $I)
    {
        $username = 'user4';
        $password = 'password';

        $I->seeInRepository(\App\Entity\User::class, [
            'username' => $username,
            'enabled' => false
        ]);

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $resposne = json_decode($I->grabResponse(), true);
        $accessToken = $resposne['access_token'];
        $I->amBearerAuthenticated($accessToken);
        $I->sendGet('/api/v1/user/me');
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::UNAUTHORIZED);
        $I->seeResponseIsJson();
        $expectedResponse = [
            'error' => 'access_denied',
            'error_description' => 'User account is disabled.'
        ];
        $I->seeResponseContainsJson($expectedResponse);
    }

    public function meLoggedInEnabled(ApiTester $I)
    {
        $username = 'user19';
        $password = 'password';

        $I->seeInRepository(\App\Entity\User::class, [
            'username' => $username,
            'enabled' => true
        ]);

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];

        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->haveHttpHeader('Authorization', 'Bearer ' . $accessToken);
        $I->sendGet('api/v1/user/me?disableLastLogin=' . true);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
    }

    public function meLoggedOut(ApiTester $I)
    {
        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => 'xxxxxxxx',
            'password' => 'xxxxxxxx'
        ];

        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseIsJson();
        $expectedResponse = [
            'error' => 'invalid_grant',
            'error_description' => 'Invalid username and password combination'
        ];
        $I->seeResponseContainsJson($expectedResponse);

        $I->sendGET('/api/v1/user/me');
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::UNAUTHORIZED);
        $I->seeResponseIsJson();
        $expectedResponse = [
            'error' => 'access_denied',
            'error_description' => 'OAuth2 authentication required'
        ];
        $I->seeResponseContainsJson($expectedResponse);
    }

    public function confirmInvalidUsernameQueryParam(ApiTester $I)
    {
        $username = '';
        $confirmationToken = '123';
        $I->sendGet('api/v1/user/confirm?username='.$username.'&confirmation_token='.$confirmationToken);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::NOT_FOUND);
        $I->seeResponseIsJson();
        $I->seeResponseContains('Username not found');
    }

    public function confirmInvalidConfirmationTokenQueryParam(ApiTester $I)
    {
        $username = 'user1';
        $confirmationToken = '';
        $I->sendGet('api/v1/user/confirm?username='.$username.'&confirmation_token='.$confirmationToken);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::NOT_FOUND);
        $I->seeResponseIsJson();
        $I->seeResponseContains('Confirmation Token not found');
    }

    public function confirmUserNotInDatabase(ApiTester $I)
    {
        $username = 'xxxxx';
        $confirmationToken = 'xxxx';

        $I->dontSeeInRepository(\App\Entity\User::class, [
            'username' => $username
        ]);

        $I->sendGet('api/v1/user/confirm?username='.$username.'&confirmation_token='.$confirmationToken);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::NOT_FOUND);
        $I->seeResponseIsJson();
        $I->seeResponseContains('User not found');
    }

    public function confirmIsUserAlreadyActivated(ApiTester $I)
    {
        $username = 'user1';
        $confirmationToken = 'xxxx';

        $I->seeInRepository(\App\Entity\User::class, [
            'username'  => $username
        ]);

        $I->sendGet('api/v1/user/confirm?username='.$username.'&confirmation_token='.$confirmationToken);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('Already confirmed');
    }

    public function confirmInvalidConfirmationToken(ApiTester $I)
    {
        $userClass = \App\Entity\User::class;

        $username = 'user4';
        $confirmationToken = 'xxxx';

        $I->seeInRepository($userClass, [
            'username'  => $username
        ]);

        $I->sendGet('api/v1/user/confirm?username='.$username.'&confirmation_token='.$confirmationToken);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseIsJson();
        $I->seeResponseContains('Confirmation Token cant be found');
    }

    public function confirmAlreadyLoggedIn(ApiTester $I)
    {
        $userClass = \App\Entity\User::class;

        $username = 'user1';
        $password = 'password';
        $confirmationToken = '4c3fb568d51feb12a0038033890efb5367585af3a';

        $I->seeInRepository($userClass, [
            'username'  => $username
        ]);

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];

        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        $I->sendGet('api/v1/user/confirm?username='.$username.'&confirmation_token='.$confirmationToken);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseContains('Already confirmed');
    }

    public function confirmSuccessfully(ApiTester $I)
    {
        $userClass = App\Entity\User::class;

        $username = 'user5';
        $confirmationToken = '5c3fb568d51feb12a0038033890efb5367585af3a';

        $data = [
            'username' => $username,
            'confirmationToken' => $confirmationToken,
            'activatedAt' => null,
            'enabled' => false
        ];
        $I->seeInRepository($userClass, $data);

        $I->sendGet('api/v1/user/confirm?username='
            .$username.'&confirmation_token='.$confirmationToken.'&disableActivation=true');
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseContains('Successfully confirmed');
    }

    public function resendNoEmail(ApiTester $I)
    {
        $email = null;

        $I->sendGet('api/v1/user/resend');
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::NOT_FOUND);
        $I->seeResponseContains('Email not entered');
    }

    public function resendNonExistingUser(ApiTester $I)
    {
        $email = 'xxxxxxxx';

        $I->sendGet('api/v1/user/resend?email='.$email);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::NOT_FOUND);
        $I->seeResponseContains('User not found');
    }

    public function resendValidEmailNotActivated(ApiTester $I)
    {
        $email = 'user6@email.com';

        $I->sendGet('api/v1/user/resend?email='.$email);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseContains('We have resent a new confirmation email');
    }

    public function resendValidEmailActivated(ApiTester $I)
    {
        $email = 'user1@email.com';

        $I->sendGet('api/v1/user/resend?email='.$email);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseContains('Already confirmed');
    }

    public function resetPasswordInvalidResetKeyParam(ApiTester $I)
    {
        $I->sendPOST('api/v1/user/reset-password', []);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('Reset key not found');
    }

    public function resetPasswordInvalidUsernameParam(ApiTester $I)
    {
        $data = [
            "reset_key" => "xxxxxxxxxxx"
        ];

        $I->sendPOST('api/v1/user/reset-password', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('Username not found');
    }

    public function resetPasswordInvalidPasswordParam(ApiTester $I)
    {
        $data = [
            "reset_key" => "xxxxxxxxxxx",
            "username" => "xxxxxxxxxx"
        ];

        $I->sendPOST('api/v1/user/reset-password', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('Password not found');
    }

    public function resetPasswordUserDoesNotExist(ApiTester $I)
    {
        $data = [
            "reset_key" => "xxxxxxxxxxx",
            "username" => "xxxxxxxxxx",
            "password" => "xxxxxxxxxx"
        ];

        $I->sendPOST('api/v1/user/reset-password', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::FORBIDDEN);
        $I->seeResponseContains('User does not exist');
    }

    public function resetPasswordInvalidResetKey(ApiTester $I)
    {
        $data = [
            "reset_key" => "xxxxxxxxxxx",
            "username" => "user4",
            "password" => "xxxxxxxxxx"
        ];

        $I->sendPOST('api/v1/user/reset-password', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::FORBIDDEN);
        $I->seeResponseContains('Reset key does not exist');
    }

    public function resetPasswordIsRequestExpired(ApiTester $I)
    {
        $data = [
            "reset_key" => "4c3fb568d51feb12a0038033890efb5367585af3a",
            "username" => "user4",
            "password" => "xxxxxxxxxx"
        ];

        $I->sendPOST('api/v1/user/reset-password', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::FORBIDDEN);
        $I->seeResponseContains('Reset key expired');
    }

    public function resetPasswordSuccessfully(ApiTester $I)
    {
        $data = [
            "reset_key" => "5c3fb568d51feb12a0038033890efb5367585af3a",
            "username" => "user5",
            "password" => "xxxxxxxxxx"
        ];

        $I->sendPOST('api/v1/user/reset-password', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseContains('New password set');
    }

    public function forgotUsernameNoEmailParam(ApiTester $I)
    {
        $I->sendPost('api/v1/user/forgot-username', []);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('Email not found');
    }

    public function forgotUsernameUserNotFound(ApiTester $I)
    {
        $data = [
            'email' => 'xxxxxx'
        ];

        $I->sendPost('api/v1/user/forgot-username', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('User not found');
    }

    public function forgotUsernameSuccessful(ApiTester $I)
    {
        $data = [
            'email' => 'user1@email.com'
        ];

        $I->sendPost('api/v1/user/forgot-username', json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseContains('Email has been sent');
    }

    public function getUserInvalidUsername(ApiTester $I)
    {
        $username = 'xxxxxxxxxx';

        $I->sendGET('api/v1/user/' . $username);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::NOT_FOUND);
        $I->seeResponseContains('User cannot be found');
    }

    public function getUserSuccessful(ApiTester $I)
    {
        $username = 'user1';

        $I->sendGET('api/v1/user/' . $username);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);

        $expectedResponse = [
            "name" => "John Smith",
            "username" => "user1",
            "avatar" => "http://localhost:8000/uploads/avatar/0d91cca62a1a31a612b2a6366c7ef56b3e468ce8.jpg",
            "roles" => [
                "ROLE_ADMIN",
                "ROLE_USER"
            ],
        ];

        $I->seeResponseContainsJson($expectedResponse);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $user = $userService->getUserByUsername($username);
        $I->assertNotNull($user->getCreatedAt());
    }

    public function editUserNotLoggedIn(ApiTester $I)
    {
        $userId = 1;
        $data = [];

        $I->sendPATCH('api/v1/user/' . $userId, $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::UNAUTHORIZED);
        $I->seeResponseIsJson();
        $expectedResponse = [
            'error' => 'access_denied',
            'error_description' => 'OAuth2 authentication required'
        ];
        $I->seeResponseContainsJson($expectedResponse);
    }

    public function editUserLoggedInButInvalidUser(ApiTester $I)
    {
        $userId = 999999;
        $username = 'user2';
        $password = 'password';

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        $data = [];
        $I->sendPATCH('api/v1/user/' . $userId, json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::FORBIDDEN);
        $I->seeResponseContains('User does not exist');
    }

    public function editUserButDifferentUser(ApiTester $I)
    {
        $username = 'user2';
        $password = 'password';

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $userId = $userService->getUserByUsername("user1")->getId();

        $data = [];
        $I->sendPATCH('api/v1/user/' . $userId, json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::FORBIDDEN);
        $I->seeResponseContains('You are not allowed to edit a different user');
    }

    public function editUserButDifferentUsername(ApiTester $I)
    {
        $username = 'user2';
        $password = 'password';

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $existingUser = $userService->getUserByUsername($username);

        $data = [
            'name' => $existingUser->getName(),
            'username' => 'user1'
        ];
        $I->sendPATCH('api/v1/user/' . $existingUser->getId(), json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('USERNAME_IS_ALREADY_IN_USE');
    }

    public function editUserSuccessfully(ApiTester $I)
    {
        $username = 'user2';
        $password = 'password';

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $existingUser = $userService->getUserByUsername($username);

        $data = [
            'name' => $existingUser->getName(),
            'username' => 'user99999'
        ];
        $I->sendPATCH('api/v1/user/' . $existingUser->getId(), json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $expectedResponse = [
            "name" => "Sarah McCarthy",
            "username" => "user99999"
        ];
        $I->seeResponseContainsJson($expectedResponse);

        $data = [
            'name' => $existingUser->getName(),
            'username' => $username
        ];
        $I->sendPatch('api/v1/user/' . $existingUser->getId(), json_encode($data));
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $expectedResponse = [
            "name" => "Sarah McCarthy",
            "username" => "user2"
        ];
        $I->seeResponseContainsJson($expectedResponse);
    }

    public function changePasswordNotLoggedIn(ApiTester $I)
    {
        $username = "user1";
        $data = [];

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $user = $userService->getUserByUsername($username);

        $I->sendPOST('api/v1/user/' . $user->getId() . '/change-password', $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::UNAUTHORIZED);

        $expectedResponse = [
            'error' => 'access_denied',
            'error_description' => 'OAuth2 authentication required'
        ];
        $I->seeResponseContainsJson($expectedResponse);
    }

    public function changePasswordNotLoggedInUser(ApiTester $I)
    {
        $username = 'user3';
        $password = 'password';

        $otherUsername = 'user4';

        $data = [];

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $otherUser = $userService->getUserByUsername($otherUsername);

        $I->assertEquals($otherUsername, $otherUser->getUsername());

        $I->sendPOST('api/v1/user/' . $otherUser->getId() . '/change-password', $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::FORBIDDEN);
        $I->seeResponseContains('You cannot change password of someone else');
    }

    public function changePasswordNewPasswordMustBeBetween6And40Chars(ApiTester $I)
    {
        $username = 'user1';
        $currentPassword = 'xxxxx';
        $newPassword = 'xxxxx';
        $confirmPassword = 'xxxxx';

        $data = [
            'currentPassword' => $currentPassword,
            'newPassword' => $newPassword,
            'confirmPassword' => $confirmPassword
        ];

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => 'password'
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $user = $userService->getUserByUsername($username);

        $I->sendPOST('api/v1/user/' . $user->getId() . '/change-password', $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('New Password must be between 6 and 40 characters');
    }

    public function changePasswordWrongCurrentPassword(ApiTester $I)
    {
        $username = 'user1';
        $currentPassword = 'xxxxxxxx';
        $newPassword = 'xxxxxxxx';
        $confirmPassword = 'xxxxxxxx';

        $data = [
            'currentPassword' => $currentPassword,
            'newPassword' => $newPassword,
            'confirmPassword' => $confirmPassword
        ];

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => 'password'
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $user = $userService->getUserByUsername($username);

        $I->sendPOST('api/v1/user/' . $user->getId() . '/change-password', $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('Password does not match the one in your account');
    }

    public function changePasswordSuccessfully(ApiTester $I)
    {
        $username = 'user19';
        $currentPassword = 'password';
        $newPassword = 'newpassword';
        $confirmPassword = 'newpassword';

        $data = [
            'currentPassword' => $currentPassword,
            'newPassword' => $newPassword,
            'confirmPassword' => $confirmPassword
        ];

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => 'password'
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        /** @var \App\Service\UserService $userService */
        $userService = $I->grabService('App\Service\UserService');
        $user = $userService->getUserByUsername($username);

        $I->sendPOST('api/v1/user/' . $user->getId() . '/change-password', $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);

        $expectedResponse = [
            "name" => "Kathleen Sims",
            "username" => "user19"
        ];
        $I->seeResponseContainsJson($expectedResponse);

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $newPassword
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');
    }

    public function forgotPasswordNoUser(ApiTester $I)
    {
        $data = [
            'username' => 'xxxxxxxxxxxx'
        ];

        $I->sendPOST('api/v1/user/forgot-password', $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('Username cannot be found.');
    }

    public function forgotPasswordSuccessful(ApiTester $I)
    {
        $data = [
            'username' => 'user3'
        ];

        $I->sendPOST('api/v1/user/forgot-password', $data);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseContains('An email has been sent');
    }

    public function listUsersOrderByLastLogin(ApiTester $I)
    {
        $sort = \App\Enum\UserOrderBy::LAST_LOGIN . '-' . \App\Enum\Order::DESC;
        $I->sendGET('api/v1/user?sort=' . $sort);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $items = json_decode($I->grabResponse(), true)['items'];

        for ($i = 0; $i <= 2; $i++) {
            $item = $items[$i];
            if ($i == 0) {
                $I->assertEquals('user2', $item['username']);
            } else if ($i == 1) {
                $I->assertEquals('user3', $item['username']);
            } else if ($i == 2) {
                $I->assertEquals('user1', $item['username']);
            }
        }
    }

    public function listUsersAsAdminOrderByLastLogin(ApiTester $I)
    {
        $username = 'user1';
        $password = 'password';

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        $sort = \App\Enum\UserOrderBy::LAST_LOGIN . '-' . \App\Enum\Order::DESC;
        $I->sendGET('api/v1/user?sort=' . $sort);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $items = json_decode($I->grabResponse(), true)['items'];

        for ($i = 0; $i <= 4; $i++) {
            $item = $items[$i];
            if ($i == 0) {
                $I->assertEquals('user5', $item['username']);
            } else if ($i == 1) {
                $I->assertEquals('user2', $item['username']);
            } else if ($i == 2) {
                $I->assertEquals('user4', $item['username']);
            } else if ($i == 3) {
                $I->assertEquals('user3', $item['username']);
            } else if ($i == 4) {
                $I->assertEquals('user1', $item['username']);
            }
        }
    }

    public function listUsersAsAdminEnabledFalseOrderByLastLogin(ApiTester $I)
    {
        $username = 'user1';
        $password = 'password';

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        $sort = \App\Enum\UserOrderBy::LAST_LOGIN . '-' . \App\Enum\Order::DESC;
        $enabled = 'false';
        $I->sendGET('api/v1/user?sort=' . $sort . '&enabled=' . $enabled);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $items = json_decode($I->grabResponse(), true)['items'];

        for ($i = 0; $i <= 2; $i++) {
            $item = $items[$i];
            if ($i == 0) {
                $I->assertEquals('user2', $item['username']);
            } else if ($i == 1) {
                $I->assertEquals('user3', $item['username']);
            } else if ($i == 2) {
                $I->assertEquals('user1', $item['username']);
            }
        }
    }

    public function listUsersAsGuestOrderByEnabled(ApiTester $I)
    {
        $sort = \App\Enum\UserOrderBy::ENABLED . '-' . \App\Enum\Order::DESC;
        $enabled = 'false';
        $I->sendGET('api/v1/user?sort=' . $sort . '&enabled=' . $enabled);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::BAD_REQUEST);
        $I->seeResponseContains('Only admin can sort by enabled');
    }

    public function listUsersAsAdminEnabledFalseOrderByEnabled(ApiTester $I)
    {
        $username = 'user1';
        $password = 'password';

        $logInDetails = [
            'grant_type' => 'password',
            'client_id' => '1_5w8zrdasdafr4tregd454cw0c0kswcgs0oks40s',
            'client_secret' => 'sdgggskokererg4232404gc4csdgfdsgf8s8ck5s',
            'username' => $username,
            'password' => $password
        ];
        $I->sendPOST('oauth/v2/token', $logInDetails);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $I->seeResponseIsJson();
        $I->seeResponseContains('access_token');
        $I->seeResponseContains('expires_in');
        $I->seeResponseContains('token_type');
        $I->seeResponseContains('scope');
        $I->seeResponseContains('refresh_token');

        $response = json_decode($I->grabResponse(), true);
        $accessToken = $response['access_token'];
        $I->amBearerAuthenticated($accessToken);

        $sort = \App\Enum\UserOrderBy::ENABLED . '-' . \App\Enum\Order::DESC;
        $enabled = 'false';
        $I->sendGET('api/v1/user?sort=' . $sort . '&enabled=' . $enabled);
        $I->seeResponseCodeIs(\Codeception\Util\HttpCode::OK);
        $items = json_decode($I->grabResponse(), true)['items'];

        for ($i = 0; $i <= 2; $i++) {
            $item = $items[$i];
            if ($i == 0) {
                $I->assertEquals('user1', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 1) {
                $I->assertEquals('user2', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 2) {
                $I->assertEquals('user3', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 3) {
                $I->assertEquals('user7', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 4) {
                $I->assertEquals('user8', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 5) {
                $I->assertEquals('user10', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 6) {
                $I->assertEquals('user11', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 7) {
                $I->assertEquals('user12', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 8) {
                $I->assertEquals('user13', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 9) {
                $I->assertEquals('user14', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 10) {
                $I->assertEquals('user15', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 11) {
                $I->assertEquals('user16', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 12) {
                $I->assertEquals('user17', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 13) {
                $I->assertEquals('user18', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 14) {
                $I->assertEquals('user19', $item['username']);
                $I->assertEquals(true, $item['enabled']);
            } else if ($i == 15) {
                $I->assertEquals('user4', $item['username']);
                $I->assertEquals(false, $item['enabled']);
            }
        }
    }


}
 

How to parse an Object that has no keys in Java

I have the following query:

public List<Object> findNearbyGangs(double lat, double lng, double distance) {
    Query query = this.entityManager.createNativeQuery("SELECT id, (6371 * acos (cos(radians(:latitude)) * cos(radians(latitude)) * cos(radians(longitude) - radians(:longitude))  + sin(radians(:latitude)) * sin(radians(latitude)))) AS distance FROM Gang g GROUP BY id HAVING distance < :distance ORDER BY distance")
        .setParameter("latitude", lat)
        .setParameter("longitude", lng)
        .setParameter("distance", distance);

    List<Object> objects = query.getResultList();
    return objects;
}

which objects contain:

[
    [
        3,
        0
    ],
    [
        321,
        0
    ],
    [
        353,
        1.3575295456440253
    ]
]

How do I parse this object so that id = 3 and distance = 0, id = 321 and distance = 0, and so on?

I asked on Stackoverflow but got no response so I ended up figuring it out myself:

public List<NearbyGang> getNearbyGangs(Double longitude, Double latitude, int radius) {
        List<Object> nearbyGangsFromDatabase = this.gangRepositoryImpl.findNearbyGangs(longitude, latitude, radius);
        List<NearbyGang> nearbyGangs = new ArrayList<NearbyGang>();
        for (Object object : nearbyGangsFromDatabase) {
            Class oClass = object.getClass();
            if (oClass.isArray()) {
                for (int i = 0; i < Array.getLength(object); i++) {
                    BigInteger gangId = (BigInteger) Array.get(object, 0);
                    Gang gang = this.getById(gangId.longValue()).get();

                    Double distance = (Double) Array.get(object, 1);

                    NearbyGang nearbyGang = new NearbyGang(gang, distance);
                    nearbyGangs.add(nearbyGang);
                }
            }
        }
        return nearbyGangs;
    }

Improvements welcomed.

 

Refactoring Multi-Dimensional Array PHP to Object Oriented PHP

One of my favourite things to do is refactor legacy code. I get to see the thinking process that went into solving the problem. It’s like standing on the shoulders of giants.

private function convertActivityToArray(array $activity)
    {
        $activityArray = array();

        $previousGroupNumber = null;
        /** @var Activity $activityItem */
        foreach ($activity as $activityItem) {
            $type = $activityItem->getType();
            $groupNumber = $activityItem->getGroupNumber();
            $user = $activityItem->getUser();
            $name = $user->getName();
            $avatar = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/avatar/' . $user->getAvatar();
            $data = $activityItem->getData();
            $created = $activityItem->getCreatedAt();

            $activityArray[$groupNumber]['type'] = $type;
            $activityArray[$groupNumber]['created'] = $created;

            if ($type == ActivityType::Like) {
                if ($groupNumber != $previousGroupNumber) {
                    $data['documentaryThumbnail'] = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/posters/' . $data['documentaryThumbnail'];
                    $activityArray[$groupNumber]['parent']['data'] = $data;
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $data['documentaryThumbnail'] = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/posters/' . $data['documentaryThumbnail'];
                    $child['data'] = $data;
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            } else if ($type == ActivityType::Comment) {
                $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                $activityArray[$groupNumber]['parent']['data'] = $data;
            } else if ($type == ActivityType::Joined) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;#
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            } else if ($type == ActivityType::Added) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityArray[$groupNumber]['parent']['data'] = $data;
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $child['data'] = $data;
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            }

            $previousGroupNumber = $groupNumber;
        }

        return $activityArray;
    }

First we refactor $data.

 $data = $activityItem->getData();

$data is an array stored in the database like this:

Joined Data:

a:2:{s:6:"userId";s:1:"1";s:8:"username";s:8:"mbwagner";}

Watchlisted Data

a:5:{s:13:"documentaryId";i:728;s:16:"documentaryTitle";s:24:"The U.S. vs. John Lennon";s:18:"documentaryExcerpt";s:160:"In retrospect, it seems absurd that the United States government felt so threatened by the presence of John Lennon that they tried to have him deported. But tha";s:20:"documentaryThumbnail";s:24:"cover/usvsjohnlennon.jpg";s:15:"documentarySlug";s:22:"the-u-s-vs-john-lennon";}

The problem with this way is the data is hardcoded into the database, what happens if the data changes? So I decide to populate data on the fly.

Replace:

 $data = $activityItem->getData();

with:

$dataStrategyContext = new DataStrategyContext(
                $type,
                $this->request,
                $this->documentaryService,
                $this->commentService);
$data = $dataStrategyContext->createData($activityItem);

Here we use the Strategy Pattern to populate data.

class DataStrategyContext
{
    /**
     * @var StrategyInterface|null
     */
    private $strategy = null;

    public function __construct(
        string $type,
        Request $request,
        DocumentaryService $documentaryService,
        CommentService $commentService)
    {
        switch ($type) {
            case ActivityType::WATCHLIST:
                $this->strategy = new StrategyWatchlist(
                    $request,
                    $documentaryService
                );
            break;
            case ActivityType::COMMENT:
                $this->strategy = new StrategyComment(
                    $request,
                    $commentService
                );
            break;
            case ActivityType::JOINED:
                $this->strategy = new StrategyJoined();
            break;
            case ActivityType::ADDED:
                $this->strategy = new StrategyAdded(
                    $documentaryService
                );
            break;
        }
    }

    /**
     * @param Activity $activityEntity
     * @return mixed
     */
    public function createData(Activity $activityEntity)
    {
        return $this->strategy->createData($activityEntity);
    }
}

StrategyWatchlist would look like the following:

class StrategyWatchlist implements StrategyInterface
{
    /**
     * @var Request
     */
    private $request;

    /**
     * @var DocumentaryService
     */
    private $documentaryService;

    /**
     * @param Request $request
     * @param DocumentaryService $documentaryService
     */
    public function __construct(
        Request $request,
        DocumentaryService $documentaryService)
    {
        $this->request = $request;
        $this->documentaryService = $documentaryService;
    }

    /**
     * @param Activity $activityEntity
     * @return Data
     */
    public function createData(Activity $activityEntity)
    {
        $documentaryId = $activityEntity->getObjectId();
        $documentary = $this->documentaryService->getDocumentaryById($documentaryId);

        $watchlistData = new WatchlistData();
        $watchlistData->setDocumentaryId($documentary->getId());
        $watchlistData->setDocumentaryTitle($documentary->getTitle());
        $watchlistData->setDocumentarySlug($documentary->getSlug());
        $watchlistData->setDocumentarySummary($documentary->getSummary());
        $poster = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/posters/' . $documentary->getPoster();
        $watchlistData->setDocumentaryPoster($poster);

        return $watchlistData;
    }
}

The next thing is to refactor parent and children from this:

if ($type == ActivityType::Like) {
                if ($groupNumber != $previousGroupNumber) {
                    $data['documentaryThumbnail'] = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/posters/' . $data['documentaryThumbnail'];
                    $activityArray[$groupNumber]['parent']['data'] = $data;
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $data['documentaryThumbnail'] = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/posters/' . $data['documentaryThumbnail'];
                    $child['data'] = $data;
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            } else if ($type == ActivityType::Comment) {
                $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                $activityArray[$groupNumber]['parent']['data'] = $data;
            } else if ($type == ActivityType::Joined) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;#
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            } else if ($type == ActivityType::Added) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityArray[$groupNumber]['parent']['data'] = $data;
                    $activityArray[$groupNumber]['parent']['user']['name'] = $name;
                    $activityArray[$groupNumber]['parent']['user']['avatar'] = $avatar;
                } else {
                    $child['data'] = $data;
                    $child['user']['name'] = $name;
                    $child['user']['avatar'] = $avatar;
                    $activityArray[$groupNumber]['child'][] = $child;
                }
            }

            $previousGroupNumber = $groupNumber;
        }

After refactoring and creating ActivityParent and ActivityChild it should look like this:

if ($type == ActivityType::Like) {
	if ($groupNumber != $previousGroupNumber) {
		$activityParent = new ActivityParent();
		$activityParent->setName($name);
		$activityParent->setUsername($username);
		$activityParent->setAvatar($avatar);
		$activityParent->setData($data);

		$activityArray[$groupNumber]['parent'] = $activityParent->toArray();
	} else {
		$activityChild = new ActivityChild();
		$activityChild->setData($data);
		$activityChild->setUsername($username);
		$activityChild->setName($name);
		$activityChild->setAvatar($avatar);

		$activityArray[$groupNumber]['child'][] = $activityChild->toArray();
	}
} else if ($type == ActivityType::Comment) {
	$activityParent = new ActivityParent();
		$activityParent->setName($name);
		$activityParent->setUsername($username);
		$activityParent->setAvatar($avatar);
		$activityParent->setData($data);
} else if ($type == ActivityType::Joined) {
	if ($groupNumber != $previousGroupNumber) {
		$activityParent = new ActivityParent();
		$activityParent->setName($name);
		$activityParent->setUsername($username);
		$activityParent->setAvatar($avatar);
		$activityParent->setData($data);
	} else {
		$activityChild = new ActivityChild();
		$activityChild->setData($data);
		$activityChild->setUsername($username);
		$activityChild->setName($name);
		$activityChild->setAvatar($avatar);

		$activityArray[$groupNumber]['child'][] = $activityChild->toArray();
	}
} else if ($type == ActivityType::Added) {
	if ($groupNumber != $previousGroupNumber) {
		$activityParent = new ActivityParent();
		$activityParent->setName($name);
		$activityParent->setUsername($username);
		$activityParent->setAvatar($avatar);
		$activityParent->setData($data);
	} else {
		$activityChild = new ActivityChild();
		$activityChild->setData($data);
		$activityChild->setUsername($username);
		$activityChild->setName($name);
		$activityChild->setAvatar($avatar);

		$activityArray[$groupNumber]['child'][] = $activityChild->toArray();
	}
}

$previousGroupNumber = $groupNumber;
}

Notice there are duplicate times when we create ActivityParent and ActivityChild. Only certain types have children:

class ActivityType
{
    const LIKE = "like";
    const COMMENT = "comment";
    const FOLLOW = "follow";
    const JOINED = "joined";
    const ADDED = "added";

    /**
     * @return array
     */
    public static function getAllTypes()
    {
        return [
            self::LIKE,
            self::ADDED,
            self::JOINED,
            self::COMMENT,
            self::FOLLOW
        ];
    }

    /**
     * @return array
     */
    public static function getTypesWithChildren()
    {
        return [
            self::LIKE,
            self::JOINED,
            self::ADDED
        ];
    }

    /**
     * @param string $type
     * @return bool
     */
    public static function hasChildren(string $type)
    {
        return in_array($type, self::getTypesWithChildren());
    }
}

Then we update the convertActivityToArray() function to this:


            $hasChildren = ActivityType::hasChildren($type);
            if ($hasChildren) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityParent = new ActivityParent();
                    $activityParent->setName($name);
                    $activityParent->setUsername($username);
                    $activityParent->setAvatar($avatar);
                    $activityParent->setData($data);

                    $activityArray[$groupNumber]['parent'] = $activityParent->toArray();

                } else {
                    $activityChild = new ActivityChild();
                    $activityChild->setData($data);
                    $activityChild->setUsername($username);
                    $activityChild->setName($name);
                    $activityChild->setAvatar($avatar);

                    $activityArray[$groupNumber]['child'][] = $activityChild->toArray();
                }
            } else {
                $activityParent = new ActivityParent();
                $activityParent->setName($name);
                $activityParent->setUsername($username);
                $activityParent->setAvatar($avatar);
                $activityParent->setData($data);

                $activityArray[$groupNumber]['parent'] = $activityParent->toArray();
            }

Notice ActivityParent and ActivityChild are doing the same thing so I refactored it to just ActivityObject. Plus I moved it above the IF statements.

$activityObject = new ActivityObject();
$activityObject->setName($name);
$activityObject->setUsername($username);
$activityObject->setAvatar($avatar);
$activityObject->setData($data);

$hasChildren = ActivityType::hasChildren($type);
if ($hasChildren) {
	if ($groupNumber != $previousGroupNumber) {
		$activityArray[$groupNumber]['parent'] = $activityObject->toArray();
	} else {
		$activityArray[$groupNumber]['child'][] = $activityObject->toArray();
	}
} else {
	$activityArray[$groupNumber]['parent'] = $activityObject->toArray();
}

Next we add ActivityItemOject to hold the array keys and values.

class ActivityItemObject
{
    /**
     * @var string
     */
    private $type;

    /**
     * @var \DateTime
     */
    private $created;

    /**
     * @var ActivityObject
     */
    private $parent;

    /**
     * @var ActivityObject[]
     */
    private $children;

    public function __construct()
    {
        $this->children = [];
    }
.....

    /**
     * @return array
     */
    public function toArray()
    {
        $children = [];
        foreach ($this->children as $child) {
            $children[] = $child->toArray();
        }

        $array = [
            'type' => $this->type,
            'created' => $this->created,
            'parent' => $this->parent->toArray(),
            'child' => $children
        ];

        return $array;
    }

And finally the completed code:


    /**
     * @param ArrayCollection|Activity[] $activity
     * @return array
     */
    private function convertActivityToArray(array $activity)
    {
        $activityMap = [];

        $previousGroupNumber = 0;
        /** @var Activity $activityEntity */
        foreach ($activity as $activityItem) {
            $groupNumber = $activityItem->getGroupNumber();

            if (array_key_exists($groupNumber, $activityMap) != null) {
                $activityItemObject = $activityMap[$groupNumber];
            } else {
                $activityItemObject = new ActivityItemObject();
            }

            $type = $activityItem->getType();
            $created = $activityItem->getCreatedAt();

            $activityItemObject->setType($type);
            $activityItemObject->setCreated($created);

            $dataStrategyContext = new DataStrategyContext(
                $type,
                $this->request,
                $this->documentaryService,
                $this->commentService);
            $data = $dataStrategyContext->createData($activityItem);

            $user = $activityItem->getUser();
            $name = $user->getName();
            $avatar = $this->request->getScheme() .'://' . $this->request->getHttpHost() . $this->request->getBasePath() . '/uploads/avatar/' . $user->getAvatar();
            $username = $user->getUsername();

            $activityObject = new ActivityObject();
            $activityObject->setName($name);
            $activityObject->setUsername($username);
            $activityObject->setAvatar($avatar);
            $activityObject->setData($data);

            $hasChildren = ActivityType::hasChildren($type);
            if ($hasChildren) {
                if ($groupNumber != $previousGroupNumber) {
                    $activityItemObject->setParent($activityObject);
                } else {
                    $activityItemObject->addChild($activityObject);
                }
            } else {
                $activityItemObject->setParent($activityObject);
            }

            $activityMap[$groupNumber] = $activityItemObject;

            $previousGroupNumber = $groupNumber;
        }

        $display = [];
        /** @var ActivityItemObject $value */
        foreach ($activityMap as $key => $value) {
            $display[$key] = $value->toArray();
        }

        return $display;
    }
 

Add New Documentary Series

Here’s the code for creating new documentary series.

import { YoutubeService } from './../../../../services/youtube.service';
import { OMDBService } from './../../../../services/omdb.service';
import { AngularEditorConfig } from '@kolkov/angular-editor';
import { FormBuilder, FormControl, Validators, FormArray, FormGroup } from '@angular/forms';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { CategoryService } from './../../../../services/category.service';
import { VideoSourceService } from './../../../../services/video-source.service';
import { HttpParams } from '@angular/common/http';
import { YearService } from './../../../../services/year.service';
import { UserService } from './../../../../services/user.service';
import { DocumentaryService } from './../../../../services/documentary.service';
import { Router, ActivatedRoute } from '@angular/router';
import { Episodic } from './../../../../models/episodic.model';
import { Documentary } from 'src/app/models/documentary.model';
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
import { Location } from "@angular/common";

@Component({
  selector: 'app-documentary-add-episodic',
  templateUrl: './documentary-add-episodic.component.html',
  styleUrls: ['./documentary-add-episodic.component.css']
})
export class DocumentaryAddEpisodicComponent implements OnInit {

  private documentary: Documentary;

  private form: FormGroup;
  private imdbForm: FormGroup;
  private youtubeForm: FormGroup;

  private submitted = false;
  private errors = null;

  private posterImgURL;
  private wideImgURL;
  private thumbnailImgURLDict = {};

  private page;
  private slug;
  private editMode = false;

  private me;

  private config: any;

  private seasonIndex;
  private episodeIndex;

  editorConfig: AngularEditorConfig = {
    editable: true,
    spellcheck: true,
    height: '25rem',
    minHeight: '5rem',
    placeholder: 'Enter text here...',
    translate: 'no',
    uploadUrl: 'v1/images', // if needed
  };

  private showPage = false;
  private hasToggledForm = false;
  private showForm = false;
  private showAddTitleButton = true;
  private showDocumentaries = true;

  private isFetchingYears = false;
  private isFetchingVideoSources = false;
  private isFetchingCategories = false;
  private isFetchingDocumentaries = false;

  private isFetchingDocumentariesFromIMDB = false;
  private isFetchingDocumentaryFromIMDB = false;
  private showSearchedDocumentariesFromIMDB = false;
  private showSearchedDocumentaryFromIMDB = false;

  private searchedDocumentariesFromIMDB;
  private searchedDocumentaryFromIMDB;

  private isFetchingVideosFromYoutube = false;
  private showSearchedVideosFromYoutube = false;
  private searchedVideosFromYoutube;

  private years;
  private videoSources;
  private categories;

  private myDocumentaries;

  private queryParamsSubscription;
  private routeParamsSubscription;
  private documentaryBySlugSubscription;
  private meSubscription;
  private videoSourcesSubscription;
  private categoriesSubscription;
  private getByImdbIdSubscription;
  private ombdSearchSubscription;
  private youtubeByIdSubscription;
  private youtubeSearchSubscription;

  private closeResult: string;

  private seasonNumber = 1;
  private episodeNumber = 1;

  private imdbType;

  constructor(
    private documentaryService: DocumentaryService,
    private userService: UserService,
    private yearService: YearService,
    private videoSourceService: VideoSourceService,
    private categoryService: CategoryService,
    private omdbService: OMDBService,
    private youtubeService: YoutubeService,
    private route: ActivatedRoute,
    private cd: ChangeDetectorRef,
    private router: Router,
    private location: Location,
    private modalService: NgbModal,
    private fb: FormBuilder
  ) { }

  ngOnInit() {
    this.initModel();

    this.queryParamsSubscription = this.route
      .queryParams
      .subscribe(params => {
        this.page = +params['page'] || 1;

        this.routeParamsSubscription = this.route
          .paramMap
          .subscribe(params => {
            this.slug = params['params']['slug'];
            this.editMode = this.slug != null;

            if (this.editMode) {
              this.documentaryBySlugSubscription = this.documentaryService
                .getDocumentaryBySlug(this.slug)
                .subscribe((result: any) => {
                  this.documentary = result;
                  this.toggleForm();
                  this.showPage = true;
                });
            } else {
              this.meSubscription = this.userService
                .getMe()
                .subscribe(me => {
                  this.me = me;

                  if (!this.hasToggledForm) {
                    this.fetchDocumentaries();
                    this.showPage = true;
                  }
                })
            }
          })
      })
  }

  initModel() {
    this.documentary = new Documentary();
    let episodic = new Episodic();
    this.documentary.episodic = episodic;
  }

  toggleForm() {
    this.showAddTitleButton = false;
    this.showDocumentaries = false;

    this.showForm = !this.showForm;

    this.initYears();
    this.initVideoSources();
    this.initCategories();
    this.initForm();
  }

  initForm(seasons = null) {
    let title = this.documentary.title;
    let category = this.documentary.category;
    let storyline = this.documentary.storyline;
    let summary = this.documentary.summary;
    // let videoSource = null;
    //if (this.documentary.standalone.videoSource) {
    //  videoSource = this.documentary.standalone.videoSource.id
    // }
    //let videoId = this.documentary.standalone.videoId;
    let year = this.documentary.year;
    let yearFrom = this.documentary.yearFrom;
    let yearTo = this.documentary.yearTo;
    //let length = this.documentary.length;
    let poster = this.documentary.poster;
    this.posterImgURL = this.documentary.poster;
    let wideImage = this.documentary.wideImage;
    this.wideImgURL = this.documentary.wideImage;
    let imdbId = this.documentary.imdbId;

    this.form = this.fb.group({
      'title': new FormControl(title, [Validators.required]),
      'category': new FormControl(category, [Validators.required]),
      'storyline': new FormControl(storyline, [Validators.required]),
      'summary': new FormControl(summary, [Validators.required]),
      'yearFrom': new FormControl(yearFrom, [Validators.required]),
      'yearTo': new FormControl(yearTo, [Validators.required]),
      'poster': new FormControl(poster, [Validators.required]),
      'wideImage': new FormControl(wideImage, [Validators.required]),
      'imdbId': new FormControl(imdbId),
      'seasons': this.fb.array([], Validators.required)
    });

    if (seasons != null) {
      seasons.forEach(season => {
        this.addSeason(season);
      });
    }

    console.log("this.thumbnailImgURLDict");
    console.log(this.thumbnailImgURLDict);
  }

  getEpisodeNumber(episode) {
    return episode.value.number;
  }

  getSeasonNumber(season) {
    return season.value.number;
  }

  addSeason(season = null) {
    if (season != null) {
      this.seasonNumber = season.number;
    }

    let control = <FormArray>this.form.controls.seasons;
    control.push(
      this.fb.group({
        'number': new FormControl(this.seasonNumber, [Validators.required]),
        'episodes': this.fb.array([], Validators.required)
      })
    );

    if (season != null) {
      let episodes = season.episodes;
      if (season != null && episodes != null) {
        episodes.forEach(episode => {
          let episodesControl = control.at(this.seasonNumber - 1).get('episodes');
          this.addEpisode(episodesControl, season, episode);
        })
      }
    }

    if (season == null) {
      this.seasonNumber++;
    }
  }

  deleteSeason(number) {
    var seasonsFormArray = this.form.get("seasons") as FormArray;

    let index = 0;
    seasonsFormArray.value.forEach(seasonArray => {
      if (number == seasonArray.number) {
        seasonsFormArray.removeAt(index);
        return;
      }
      index++;
    });
  }

  deleteEpisode(control, index) {
    control.removeAt(index);
  }

  insertEpisode(control, index) {
    let title;
    let storyline;
    let summary;
    let year;
    let length;
    let imdbId;
    let videoId;
    let videoSource = 2;
    let thumbnail;
    let episodeNumber;

    control.insert(index,
      this.fb.group({
        'number': new FormControl(episodeNumber, [Validators.required]),
        'title': new FormControl(title, [Validators.required]),
        'imdbId': new FormControl(imdbId),
        'storyline': new FormControl(storyline, [Validators.required]),
        'summary': new FormControl(summary, [Validators.required]),
        'length': new FormControl(length, [Validators.required]),
        'year': new FormControl(year, [Validators.required]),
        'videoSource': new FormControl(videoSource, [Validators.required]),
        'videoId': new FormControl(videoId, [Validators.required]),
        'thumbnail': new FormControl(thumbnail, [Validators.required]),
      }));
  }

  addEpisode(control, season, episode = null) {
    let title;
    let storyline;
    let summary;
    let year;
    let length;
    let imdbId;
    let videoId;
    let videoSource = 2;
    let thumbnail;
    let episodeNumber;

    if (episode != null) {
      episodeNumber = episode.number;
      title = episode.title;
      imdbId = episode.imdbId;
      thumbnail = episode.thumbnail;
      summary = episode.summary;
      storyline = episode.storyline;
      year = episode.year;
      videoId = episode.videoId;
      videoSource = 2;
      length = episode.length;

      if (this.thumbnailImgURLDict[season.number] == undefined) {
        this.thumbnailImgURLDict[season.number] = {};
      }
      if (this.thumbnailImgURLDict[season.number][episode.number] == undefined) {
        this.thumbnailImgURLDict[season.number][episode.number] = {};
      }
      this.thumbnailImgURLDict[season.number][episode.number] = thumbnail;
    }

    control.push(
      this.fb.group({
        'number': new FormControl(episodeNumber, [Validators.required]),
        'title': new FormControl(title, [Validators.required]),
        'imdbId': new FormControl(imdbId),
        'storyline': new FormControl(storyline, [Validators.required]),
        'summary': new FormControl(summary, [Validators.required]),
        'length': new FormControl(length, [Validators.required]),
        'year': new FormControl(year, [Validators.required]),
        'videoSource': new FormControl(videoSource, [Validators.required]),
        'videoId': new FormControl(videoId, [Validators.required]),
        'thumbnail': new FormControl(thumbnail, [Validators.required]),
      }));

  }

  get f() { return this.form.controls; }

  getThumbnailForSeasonAndEpsiode(seasonNumber: number, episodeNumber: number) {
    if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
      this.thumbnailImgURLDict[seasonNumber] = {};
    }
    return this.thumbnailImgURLDict[seasonNumber][episodeNumber];
  }

  onThumbnailChange(event, seasonIndex, episodeIndex) {
    console.log(event);
    let reader = new FileReader();

    if (event.target.files && event.target.files.length) {
      const [file] = event.target.files;
      reader.readAsDataURL(file);

      reader.onload = () => {
        var seasonsFormArray = this.form.get("seasons") as FormArray;
        var episodesFormArray = seasonsFormArray.at(seasonIndex).get("episodes") as FormArray;
        episodesFormArray.at(episodeIndex)['controls']['thumbnail'].patchValue(reader.result);

        let seasonNumber = seasonsFormArray.at(seasonIndex).value.number;
        let episodeNumber = episodesFormArray.at(episodeIndex).value.number;

        if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
          this.thumbnailImgURLDict[seasonNumber] = {};
        }
        this.thumbnailImgURLDict[seasonNumber][episodeNumber] = reader.result;
      }

      this.cd.markForCheck();

    };
  }

  openIMDBModal(content, imdbType) {
    console.log(imdbType);
    this.imdbType = imdbType;
    this.initIMDBFrom();
    console.log(content);
    this.modalService.open(content, { ariaLabelledBy: 'modal-omdb' }).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
    }, (reason) => {
      this.closeResult = `Dismissed ${reason}`;
    });
  }


  initIMDBFrom() {
    let title = this.form.value.title;

    this.imdbForm = new FormGroup({
      'title': new FormControl(title, [Validators.required])
    });
  }

  imdbView(imdbId) {
    console.log("imdbId");
    console.log(imdbId);
    this.isFetchingDocumentaryFromIMDB = true;
    this.showSearchedDocumentariesFromIMDB = false;
    this.showSearchedDocumentaryFromIMDB = true;

    let imdbType = this.imdbType;
    this.omdbService.getByImdbId(imdbId, imdbType)
      .subscribe((result: any) => {
        this.searchedDocumentaryFromIMDB = result;
        this.isFetchingDocumentaryFromIMDB = false;
        console.log("searchedDocumentaryFromIMDB");
        console.log(this.searchedDocumentaryFromIMDB);
      })
  }

  imdbSelect() {
    let selectedDocumentary = this.searchedDocumentaryFromIMDB;
    console.log("selectedDocumentary");
    console.log(selectedDocumentary);
    console.log("this.form");
    console.log(this.form);
    if (this.form.value.title == null) {
      this.documentary.title = selectedDocumentary.title;
    } else {
      this.documentary.title = this.form.value.title;
    }

    if (this.form.value.imdbId != selectedDocumentary.imdbId) {
      this.documentary.imdbId = selectedDocumentary.imdbId;
    }

    if (this.form.value.storyline == null) {
      this.documentary.storyline = selectedDocumentary.storyline;
    } else {
      this.documentary.storyline = this.form.value.storyline;
    }

    if (this.form.value.yearFrom == null) {
      this.documentary.yearFrom = selectedDocumentary.yearFrom;
    } else {
      this.documentary.yearFrom = this.form.value.yearFrom;
    }

    if (this.form.value.yearTo == null) {
      this.documentary.yearTo = selectedDocumentary.yearTo;
    } else {
      this.documentary.yearTo = this.form.value.yearTo;
    }

    if (this.form.value.poster == null) {
      this.documentary.poster = selectedDocumentary.poster;
      this.posterImgURL = selectedDocumentary.poster;
    } else {
      this.documentary.poster = this.form.value.poster;
      this.posterImgURL = this.form.value.poster;
    }

    let seasons = selectedDocumentary.seasons;
    this.initForm(seasons);

    this.modalService.dismissAll();
  }

  searchOMDB() {
    this.isFetchingDocumentariesFromIMDB = true;
    this.showSearchedDocumentaryFromIMDB = false;
    this.showSearchedDocumentariesFromIMDB = true;

    let titleOrId = this.imdbForm.value.title;
    let imdbType = this.imdbType;

    this.getByImdbIdSubscription = this.omdbService.getByImdbId(titleOrId, imdbType)
      .subscribe((result: any) => {
        result = [result];
        this.searchedDocumentariesFromIMDB = result;
        this.isFetchingDocumentariesFromIMDB = false;
      },
        (error) => {
          this.ombdSearchSubscription = this.omdbService.getSearchedDocumentaries(titleOrId, imdbType)
            .subscribe((result: any) => {
              this.searchedDocumentariesFromIMDB = result;
              this.isFetchingDocumentariesFromIMDB = false;
            });
        });
  }


  openYoutubeModal(content, seasonIndex: number, episodeIndex: number) {
    this.seasonIndex = seasonIndex;
    this.episodeIndex = episodeIndex;

    this.initYoutubeForm();

    this.modalService.open(content, { ariaLabelledBy: 'modal-youtube' }).result.then((result) => {
      this.closeResult = `Closed with: ${result}`;
    }, (reason) => {
      this.closeResult = `Dismissed ${reason}`;
    });
  }

  initYoutubeForm() {
    let seasonIndex = this.seasonIndex;
    let episodeIndex = this.episodeIndex;

    let seasonsFormArray = this.form.get("seasons") as FormArray;
    let episodesFormArray = seasonsFormArray.at(seasonIndex).get("episodes") as FormArray;
    let titleOrId = episodesFormArray.at(episodeIndex)['controls']['title'].value;

    this.youtubeForm = new FormGroup({
      'title': new FormControl(titleOrId, [Validators.required])
    });
  }

  searchYoutube() {
    this.isFetchingVideosFromYoutube = true;
    this.showSearchedVideosFromYoutube = true;

    let titleOrId = this.youtubeForm.value.title;

    this.youtubeByIdSubscription = this.youtubeService.getById(titleOrId)
      .subscribe((result: any) => {
        this.searchedVideosFromYoutube = result['items'];
        this.isFetchingVideosFromYoutube = false;
      }, (error) => {
        this.youtubeSearchSubscription = this.youtubeService.getSearchedDocumentaries(titleOrId)
          .subscribe((result: any) => {
            this.searchedVideosFromYoutube = result['items'];
            this.isFetchingVideosFromYoutube = false;
          });
      });
  }

  youtubeSelect(selectedVideo) {
    let seasonIndex = this.seasonIndex;
    let episodeIndex = this.episodeIndex;

    let seasonsFormArray = this.form.get("seasons") as FormArray;
    let episodesFormArray = seasonsFormArray.at(seasonIndex).get("episodes") as FormArray;

    let title = episodesFormArray.at(episodeIndex)['controls']['title'].value;
    if (title == null || !title.trim()) {
      let selectedTitle = selectedVideo.snippet.title;
      episodesFormArray.at(episodeIndex)['controls']['title'].patchValue(selectedTitle);
    }

    let videoId = episodesFormArray.at(episodeIndex)['controls']['videoId'].value;
    if (videoId == null || !videoId.trim()) {
      let selectedVideoId = selectedVideo.id.videoId;
      episodesFormArray.at(episodeIndex)['controls']['videoId'].patchValue(selectedVideoId);
    }

    let thumbnail = episodesFormArray.at(episodeIndex)['controls']['thumbnail'].value;
    if (thumbnail == null || !thumbnail.trim()) {
      let selectedThumbnail = selectedVideo.snippet.thumbnails.default.url;
      episodesFormArray.at(episodeIndex)['controls']['thumbnail'].patchValue(selectedThumbnail);

      let seasonNumber = seasonsFormArray.at(seasonIndex).value.number;
      let episodeNumber = episodesFormArray.at(episodeIndex).value.number;

      if (this.thumbnailImgURLDict[seasonNumber] == undefined) {
        this.thumbnailImgURLDict[seasonNumber] = {};
      }
      this.thumbnailImgURLDict[seasonNumber][episodeNumber] = selectedThumbnail;
    }

    this.episodeIndex = null;
    this.seasonIndex = null;
    this.modalService.dismissAll();
  }

  fetchDocumentaries() {
    if (this.editMode) {
      this.showDocumentaries = false;
      return;
    }

    this.isFetchingDocumentaries = true;

    let params = new HttpParams();

    params = params.append('page', this.page.toString());

    this.location.go(this.router.url.split("?")[0], params.toString());

    this.documentaryService.getMyEpisodicDocumentaries(params, this.me.username)
      .subscribe(result => {
        this.config = {
          itemsPerPage: 5,
          currentPage: this.page,
          totalItems: result['count_results']
        };
        this.myDocumentaries = result['items'];

        this.isFetchingDocumentaries = false;
        this.showDocumentaries = true;
        this.showAddTitleButton = true;
      })
  }

  pageChanged(event) {
    this.config.currentPage = event;
    this.page = event;
    this.fetchDocumentaries();
  }

  initYears() {
    this.isFetchingYears = true;

    this.years = this.yearService.getAllYearsForForm();

    this.isFetchingYears = false;
  }

  initVideoSources() {
    this.isFetchingVideoSources = true;

    let params: HttpParams;
    this.videoSourcesSubscription = this.videoSourceService.getAll(params)
      .subscribe(result => {
        this.videoSources = result;
        console.log("this.videoSources");
        console.log(this.videoSources);

        this.isFetchingVideoSources = false;
      });
  }

  initCategories() {
    this.isFetchingCategories = true;

    let params: HttpParams;
    this.categoriesSubscription = this.categoryService.getAll(params)
      .subscribe(result => {
        this.categories = result;

        this.isFetchingCategories = false;
      });
  }

  onSubmit() {
    this.errors = null;

    console.log(this.f);
    console.log(this.form.value);

    this.submitted = true;

    let formValue = this.form.value;

    if (formValue.seasons.length === 0) {
      this.errors = "You must add a season";
    }

    if (formValue.seasons[0].episodes.length === 0) {
      this.errors = "You must add an episode";
    }

    if (!this.form.valid) {
      return;
    }

    this.errors = null;

    if (this.editMode) {
      this.documentaryService.editEpisodicDocumentary(this.documentary.id, formValue)
        .subscribe((result: any) => {
          //this.reset();
          this.router.navigate(["/add/episodic"]);
        },
          (error) => {
            console.log(error);
            this.errors = error.error;
          });
    } else {
      this.documentaryService.createEpisodicDocumentary(formValue)
        .subscribe((result: any) => {
          //this.reset();
          this.router.navigate(["/add/episodic"]);
        },
          (error) => {
            console.log(error);
            this.errors = error.error;
          });
    }
  }

  ngOnDestroy() {
    this.queryParamsSubscription.unsubscribe();
    this.routeParamsSubscription.unsubscribe();
    if (this.documentaryBySlugSubscription != null) {
      this.documentaryBySlugSubscription.unsubscribe();
    }
    if (this.meSubscription != null) {
      this.meSubscription.unsubscribe();
    }
    if (this.getByImdbIdSubscription != null) {
      this.getByImdbIdSubscription.unsubscribe();
    }
    if (this.ombdSearchSubscription != null) {
      this.ombdSearchSubscription.unsubscribe();
    }
    this.meSubscription.unsubscribe();
  }
}
 

Admin Documentaries with Query Params

See also: The Criteria Pattern

import { Status } from './../../models/status.model';
import { CategoryService } from './../../services/category.service';
import { VideoSourceService } from './../../services/video-source.service';
import { VideoSource } from './../../models/video-source.model';
import { ActivatedRoute, Router } from '@angular/router';
import { DocumentaryService } from './../../services/documentary.service';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { HttpParams } from '@angular/common/http';
import { Documentary } from './../../models/documentary.model';
import { Location } from "@angular/common";
import { Category } from 'src/app/models/category.model';

@Component({
  selector: 'app-admin-documentaries',
  templateUrl: './admin-documentaries.component.html',
  styleUrls: ['./admin-documentaries.component.css']
})
export class AdminDocumentariesComponent implements OnInit, OnDestroy {
  
  private documentariesSubscription;
  private queryParamsSubscription;
  private videoSourcesSubscription;
  private categoriesSubscription;
  public documentaries: Array<Documentary>;
  public videoSources: Array<VideoSource>;
  public categories: Array<Category>;
  public statuses: Array<Status> = [
    { id: 'pending', name: 'Pending' },
    { id: 'publish', name: 'Published' }
  ];
  public featuredOptions = [
    { id: true },
    { id: false }
  ];
  config: any;
  private page;
  private videoSource;
  private previousVideoSource;
  private previousCategory;
  private category;
  private status;
  private previousStatus;
  private featured;
  private previousFeatured;

  constructor(
    private service: DocumentaryService,
    private videoSourceService: VideoSourceService,
    private categoryService: CategoryService,
    private route: ActivatedRoute,
    private location: Location,
    private router: Router) { }

  ngOnInit() {
    this.queryParamsSubscription = this.route
      .queryParams
      .subscribe(params => {
        this.page = +params['page'] || 1;
        this.videoSource = +params['videoSource'] || 'all';
        this.category = +params['category'] || 'all';
        this.status = params['status'] || 'all';
        this.featured = params['featured'] || 'all';
        this.fetchVideoSources();
        this.fetchCategories();
        this.fetchDocumentaries();
      })
  }

  fetchDocumentaries() {
    let params = new HttpParams();
    if (this.videoSource) {
      if (this.videoSource != 'all') {
        params = params.append('videoSource', this.videoSource.toString());
        if (this.videoSource != this.previousVideoSource) {
          this.page = 1;
        }
     }
      this.previousVideoSource = this.videoSource;
    }
    if (this.category) {
      if (this.category != 'all') {
        params = params.append('category', this.category.toString());
        if (this.category != this.previousCategory) {
          this.page = 1;
        }
      }
      this.previousCategory = this.category;
    }
    if (this.status) {
      if (this.status != 'all') {
        params = params.append('status', this.status.toString());
        if (this.status != this.previousStatus) {
          this.page = 1;
        }
      }
      this.previousStatus = this.status;
    }
    if (this.featured) {
      if (this.featured != 'all') {
        params = params.append('featured', this.featured.toString());
        if (this.featured != this.previousFeatured) {
          this.page = 1;
        }
      }
      this.previousFeatured = this.featured;
    }
    
    params = params.append('page', this.page.toString());
    
    this.location.go(this.router.url.split("?")[0], params.toString());

    this.documentariesSubscription = this.service.getAllDocumentaries(params)
      .subscribe(
          result => {
            this.config = {
              itemsPerPage: 12,
              currentPage: this.page,
              totalItems: result['count_results']
            };
            this.documentaries = result['items'];
            console.log(result);
          }
      );
  }

  fetchVideoSources() {
    this.videoSourcesSubscription = this.videoSourceService.getAllVideoSources()
      .subscribe(result => {
        this.videoSources = <any> result;
      });
  }

  fetchCategories() {
    this.categoriesSubscription = this.categoryService.getAllCategories()
      .subscribe(result => { 
        this.categories = <any> result;
      });
  }

  pageChanged(event) {
    console.log(event);
    this.config.currentPage = event;
    this.page = event;
    this.fetchDocumentaries();
  }

  onVideoSourceSelected(value: string) {
    this.videoSource = value;
    this.fetchDocumentaries();
  }

  onCategoriesSelected(value: string) {
    this.category = value;
    this.fetchDocumentaries();
  }

  onStatusSelected(value: string) {
    this.status = value;
    this.fetchDocumentaries();
  }

  onFeaturedSelected(value: string) {
    this.featured = value;
    this.fetchDocumentaries();
  }

  ngOnDestroy() {
    this.documentariesSubscription.unsubscribe();
    this.queryParamsSubscription.unsubscribe();
    this.videoSourcesSubscription.unsubscribe();
    this.categoriesSubscription.unsubscribe();
  }
}