Skip to content

Commit 556103c

Browse files
authored
Merge pull request #1 from NONstop5/feature-1_auth_code_generation
Feature 1 auth code generation
2 parents fa2dd13 + a389641 commit 556103c

21 files changed

+807
-16
lines changed

.php-cs-fixer.dist.php

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
<?php
22

3-
$fileHeaderComment = <<<COMMENT
4-
This file is part of the Symfony package.
5-
6-
(c) Fabien Potencier <[email protected]>
7-
8-
For the full copyright and license information, please view the LICENSE
9-
file that was distributed with this source code.
10-
COMMENT;
11-
123
$finder = PhpCsFixer\Finder::create()
134
->in(__DIR__)
145
->exclude('config')
@@ -26,7 +17,6 @@
2617
->setRules([
2718
'@Symfony' => true,
2819
'@Symfony:risky' => true,
29-
'header_comment' => ['header' => $fileHeaderComment, 'separate' => 'both'],
3020
'linebreak_after_opening_tag' => true,
3121
'mb_str_functions' => true,
3222
'no_php4_constructor' => true,
@@ -38,6 +28,8 @@
3828
'strict_comparison' => true,
3929
'strict_param' => true,
4030
'blank_line_between_import_groups' => false,
31+
'concat_space' => ['spacing' => 'one'],
32+
'yoda_style' => false,
4133
])
4234
->setFinder($finder)
4335
->setCacheFile(__DIR__.'/var/.php-cs-fixer.cache')

Task.MD

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Техническое задание для прохождения собеседования на вакансию middle-разработчик (Symfony) Indigolab.
2+
3+
Реализовать API метод регистрации/авторизации пользователя по номеру телефона.
4+
5+
1. Запрос кода подтверждения.
6+
Метод POST
7+
8+
Логика:
9+
1. Сгенерировать 4-х значный код, сохранить в БД и вернуть в ответе.
10+
2. Реализовать контроль отправки - 1 СМС в минуту на номер телефона.
11+
3. Если код запарашивается повторно в течение минуты - вернуть сгенерированный ранее код. Иначе - сгенерировать новый код и вернуть его.
12+
4. Реализовать блокировку - если отправили 3 кода за последние 10-15 минут - блокировка на час. Вернуть соответствующий ответ.
13+
14+
Фактическая отправка СМС не требуется - вместо этого в составе ответа сервера вернуть поле, содержащее этот код.
15+
Текущий код подтверждения сохранять в базе или кэше.
16+
17+
2. Получение кода подтверждения.
18+
Метод POST
19+
20+
Логика:
21+
1. Код указан неверный - вернуть ошибку.
22+
2. Код указан верный:
23+
2.1 Если пользователя с таким номером телефона ещё нет в системе - создаём и возвращаем что-то типа "Вы успешно зарегистрировались" и его id
24+
2.2 Если пользователь с этим номером телефона уже есть - "Вы успешно авторизовались" и его id
25+
Для временного хранения номера телефона незарегистрированных пользователей может использоваться как кэш, так и БД. После успешной регистрации номер телефона должен быть привязан к пользователю (Вид связи - на Ваше усмотрение: отдельное поле пользователя, OneToOne, OneToMany(один пользователь - много телефонов))
26+
27+
Требования:
28+
1. PHP 8.4
29+
2. Symfony 7.2
30+
3. БД - PostgreSQL
31+
4. Реализация сущностей и репозиториев: предпочтительно реализация репозиториев, основанных на Doctrine DBAL и моделей с фабричными методами, т.к. абсолютное большинство сервисов компании используют протокол rpc, при котором использование ORM не представляется возможным. ORM также допускается.
32+
5. Получение/возвращение данных в формате json. Наименование полей - на Ваше усмотрение.
33+
6. Выполненное задание - ссылка на открытый git-репозиторий. Все переменные окружения - в .env.
34+
35+
Будет большим плюсом:
36+
1. Контейнеризация с использованием docker:
37+
1.1. Формат именования контейнеров: Префикс - "фамилия_имя-" (например, ivanov_ivan-php, ivanov_ivan-postgres и т.д.). Допускаются и составные префиксы, к примеру, ivanov_ivan-indigolab-php, ivanov_ivan-il_test-php и т.д.
38+
1.2. Настройка основного контейнера с кодом для работы с отладчиком xdebug. Желательно, xdebug.client_host, xdebug.idekey - в .env.
39+
2. Использование кэширония при помощи Redis для контроля отправки СМС.

config/packages/security.yaml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@ security:
44
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
55
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
66
providers:
7-
users_in_memory: { memory: null }
7+
# used to reload user from session & other features (e.g. switch_user)
8+
app_user_provider:
9+
entity:
10+
class: App\Entity\User
11+
property: phoneNumber
812
firewalls:
913
dev:
1014
pattern: ^/(_(profiler|wdt)|css|images|js)/
1115
security: false
1216
main:
1317
lazy: true
14-
provider: users_in_memory
18+
provider: app_user_provider
1519

1620
# activate different ways to authenticate
1721
# https://symfony.com/doc/current/security.html#the-firewall

config/routes.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ controllers:
33
path: ../src/Controller/
44
namespace: App\Controller
55
type: attribute
6+
prefix: '/api'

migrations/.gitignore

Whitespace-only changes.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Symfony package.
7+
*
8+
* (c) Fabien Potencier <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace DoctrineMigrations;
15+
16+
use Doctrine\DBAL\Schema\Schema;
17+
use Doctrine\Migrations\AbstractMigration;
18+
19+
/**
20+
* Auto-generated Migration: Please modify to your needs!
21+
*/
22+
final class Version20250323083449 extends AbstractMigration
23+
{
24+
public function getDescription(): string
25+
{
26+
return '';
27+
}
28+
29+
public function up(Schema $schema): void
30+
{
31+
// this up() migration is auto-generated, please modify it to your needs
32+
$this->addSql('CREATE TABLE IF NOT EXISTS messenger_messages (id BIGSERIAL NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY(id))');
33+
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)');
34+
$this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)');
35+
$this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)');
36+
$this->addSql('COMMENT ON COLUMN messenger_messages.created_at IS \'(DC2Type:datetime_immutable)\'');
37+
$this->addSql('COMMENT ON COLUMN messenger_messages.available_at IS \'(DC2Type:datetime_immutable)\'');
38+
$this->addSql('COMMENT ON COLUMN messenger_messages.delivered_at IS \'(DC2Type:datetime_immutable)\'');
39+
$this->addSql('CREATE OR REPLACE FUNCTION notify_messenger_messages() RETURNS TRIGGER AS $$
40+
BEGIN
41+
PERFORM pg_notify(\'messenger_messages\', NEW.queue_name::text);
42+
RETURN NEW;
43+
END;
44+
$$ LANGUAGE plpgsql;');
45+
$this->addSql('DROP TRIGGER IF EXISTS notify_trigger ON messenger_messages;');
46+
$this->addSql('CREATE TRIGGER notify_trigger AFTER INSERT OR UPDATE ON messenger_messages FOR EACH ROW EXECUTE PROCEDURE notify_messenger_messages();');
47+
}
48+
49+
public function down(Schema $schema): void
50+
{
51+
// this down() migration is auto-generated, please modify it to your needs
52+
$this->addSql('CREATE SCHEMA public');
53+
$this->addSql('DROP TABLE IF EXISTS messenger_messages');
54+
}
55+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Symfony package.
7+
*
8+
* (c) Fabien Potencier <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace DoctrineMigrations;
15+
16+
use Doctrine\DBAL\Schema\Schema;
17+
use Doctrine\Migrations\AbstractMigration;
18+
19+
final class Version20250323090258 extends AbstractMigration
20+
{
21+
public function getDescription(): string
22+
{
23+
return 'Create users table';
24+
}
25+
26+
public function up(Schema $schema): void
27+
{
28+
$this->addSql('
29+
CREATE TABLE IF NOT EXISTS users
30+
(
31+
id SERIAL PRIMARY KEY,
32+
name VARCHAR(255) DEFAULT NULL,
33+
phone_number VARCHAR(50) NOT NULL,
34+
roles JSON NOT NULL,
35+
updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL,
36+
created_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP
37+
);
38+
');
39+
40+
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_PHONE_NUMBER ON users (phone_number)');
41+
}
42+
43+
public function down(Schema $schema): void
44+
{
45+
$this->addSql('DROP TABLE IF EXISTS users');
46+
}
47+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace DoctrineMigrations;
6+
7+
use Doctrine\DBAL\Schema\Schema;
8+
use Doctrine\Migrations\AbstractMigration;
9+
10+
final class Version20250323103107 extends AbstractMigration
11+
{
12+
public function getDescription(): string
13+
{
14+
return 'Create phone_verification_code table';
15+
}
16+
17+
public function up(Schema $schema): void
18+
{
19+
$this->addSql('
20+
CREATE TABLE phone_verification_code
21+
(
22+
id SERIAL PRIMARY KEY,
23+
phone_number VARCHAR(50) NOT NULL,
24+
code VARCHAR(10) NOT NULL,
25+
attempts SMALLINT NOT NULL DEFAULT 0,
26+
is_used BOOLEAN NOT NULL DEFAULT FALSE,
27+
created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
28+
)
29+
');
30+
}
31+
32+
public function down(Schema $schema): void
33+
{
34+
$this->addSql('DROP TABLE IF EXISTS phone_verification_code');
35+
}
36+
}

src/Controller/.gitignore

Whitespace-only changes.

src/Controller/UserController.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
namespace App\Controller;
4+
5+
use App\Dto\Request\GetPhoneCodeDto;
6+
use App\Service\PhoneVerificationService;
7+
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
8+
use Symfony\Component\HttpFoundation\JsonResponse;
9+
use Symfony\Component\HttpKernel\Attribute\MapRequestPayload;
10+
use Symfony\Component\Routing\Attribute\Route;
11+
12+
final class UserController extends AbstractController
13+
{
14+
/**
15+
* @throws \Exception
16+
*/
17+
#[Route(
18+
'/user/request-code',
19+
name: 'user_request_code',
20+
methods: ['POST'])
21+
]
22+
public function requestCode(
23+
#[MapRequestPayload] GetPhoneCodeDto $requestDto,
24+
PhoneVerificationService $verificationService,
25+
): JsonResponse {
26+
$phoneCodeDto = $verificationService->getPhoneCode($requestDto);
27+
28+
return $this->json($phoneCodeDto);
29+
}
30+
31+
// #[Route('/verify-code', methods: ['POST'])]
32+
// public function verifyCode(Request $request): JsonResponse
33+
// {
34+
// $phoneNumber = $request->request->get('phone_number');
35+
// $code = $request->request->get('code');
36+
//
37+
// try {
38+
// $user = $this->verificationService->verifyCode($phoneNumber, $code);
39+
// return $this->json(['success' => true, 'user_id' => $user->getId()]);
40+
// } catch (\Exception $e) {
41+
// return $this->json(['error' => $e->getMessage()], 400);
42+
// }
43+
// }
44+
}

0 commit comments

Comments
 (0)