Skip to content

Commit

Permalink
Merge branch 'develop' into tests/onboarding-goldens
Browse files Browse the repository at this point in the history
  • Loading branch information
JvnSlv committed Feb 3, 2025
2 parents a70eb83 + de81384 commit eecf104
Show file tree
Hide file tree
Showing 39 changed files with 508 additions and 80 deletions.
1 change: 1 addition & 0 deletions packages/rx_bloc_cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [5.5.0]
* Added change email feature in the generated project
* Added change phone number feature in the generated project

## [5.4.1]
* Fix static analysis issue with multiline if statement brackets after linter update
Expand Down
110 changes: 95 additions & 15 deletions packages/rx_bloc_cli/example/docs/onboarding_api_contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Success (200): UserWithAuthTokenModel
}
```

Error (400):
Bad Request (400):

```json
{
Expand Down Expand Up @@ -68,7 +68,7 @@ Success (200): UserModel
}
```

Error (400):
Bad Request (400):

```json
{
Expand Down Expand Up @@ -108,7 +108,7 @@ Success (200): UserModel
}
```

Error (400):
Bad Request (400):

```json
{
Expand All @@ -133,7 +133,7 @@ Error (400):

**Response**

Success (200):
Success (200): UserModel

```json
{
Expand All @@ -148,7 +148,7 @@ Success (200):
}
```

Error (400):
Bad Request (400):

```json
{
Expand All @@ -173,7 +173,7 @@ Error (400):

**Response**

Success (200):
Success (200): UserModel

```json
{
Expand All @@ -188,7 +188,7 @@ Success (200):
}
```

Error (400):
Bad Request (400):

```json
{
Expand Down Expand Up @@ -233,17 +233,17 @@ Error (400):
## Resend SMS code
**Endpoint**: `POST /api/register/phone/resend`
**Headers**: `Authorization: Bearer <token>`
**Description**: Resends a SMS code to the set user's phone number.
**Description**: Resends a OTP code to the set user's phone number.

**Response**

Success (200)

Error (400):
Bad Request (400):

```json
{
"error": "Unable to send SMS code."
"error": "Unable to send OTP code."
}
```

Expand All @@ -255,7 +255,7 @@ Error (400):

**Response**

Success (200):
Success (200): Map<String, List<CountryCodeModel>>

```json
{
Expand Down Expand Up @@ -289,7 +289,7 @@ Success (200):
}
```

Success (200):
Success (200): UserModel
```json
{
"id": "12345",
Expand Down Expand Up @@ -332,7 +332,7 @@ Unprocessable Entity (422):
}
```

Success (200):
Success (200): UserModel
```json
{
"id": "12345",
Expand Down Expand Up @@ -361,7 +361,7 @@ Bad Request (400):
**Description**: Resends the confirmation link to the user.


Success (200):
Success (200): UserModel
```json
{
"id": "12345",
Expand All @@ -375,9 +375,89 @@ Success (200):
}
```

Error (400):
Bad Request (400):
```json
{
"error": "Invalid or expired user."
}
```

---

## Initiate Phone Number Change/Resend Confirmation Code

**Endpoint:** `PATCH /api/users/me`
**Headers**: `Authorization: Bearer <token>`
**Description**: Starts the process of changing the phone number by sending an SMS code to the new phone number. If the phone number is already in use, the endpoint returns a 409 Conflict response. If the phone number is invalid, the endpoint returns a 422 Unprocessable Entity response.

**Request Body:**
```json
{
"phoneNumber": "+1234567890"
}
```

**Response:**

Success (200): UserModel
```json
{
"id": "12345",
"email": "user@example.com",
"phoneNumber": "+359883125874",
"role": "User",
"confirmedCredentials": {
"email": true,
"phone": true
}
}
```

Conflict (409):
```json
{
"error": "This phone number is already in use."
}
```

Unprocessable Entity (422):
```json
{
"error": "Invalid phone number format."
}
```

## Verify New Phone Number
**Endpoint:** `POST /api/users/me/phone/confirm`
**Headers**: `Authorization: Bearer <token>`
**Description**: Confirms the phone number change by validating the OTP code. Only after this step the new phone number is saved to the user's profile.

**Request:**
```json
{
"code": "123456"
}
```

**Response:**

Success (200): UserModel
```json
{
"id": "12345",
"email": "user@example.com",
"phoneNumber": "+1234567890",
"role": "User",
"confirmedCredentials": {
"email": true,
"phone": true
}
}
```

Bad Request (400):
```json
{
"error": "Invalid or expired code."
}
```
2 changes: 1 addition & 1 deletion packages/rx_bloc_cli/lib/src/rx_bloc_cli_constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const rxBlocCliPackageVersion = '5.5.0';
const kAndroidCompileSDKVersion = 34;

/// Generated project's Android Target SDK version
const kAndroidTargetSDKVersion = 34;
const kAndroidTargetSDKVersion = 35;

/// Generated project's Android Min SDK version
const kAndroidMinSDKVersion = 22;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class RegistrationController extends ApiController {
router.addRequest(
RequestType.POST,
'/api/register/phone/confirm',
_confirmSmsCodeHandler,
_confirmPhoneNumberHandler,
);

router.addRequest(
Expand Down Expand Up @@ -103,7 +103,7 @@ class RegistrationController extends ApiController {
);
}

Future<Response> _confirmSmsCodeHandler(Request request) async {
Future<Response> _confirmPhoneNumberHandler(Request request) async {
final params = await request.bodyFromFormData();
final smsCode = params['smsCode'] as String?;

Expand All @@ -112,17 +112,14 @@ class RegistrationController extends ApiController {

if (smsCode == null || smsCode.length > 4 || smsCode == '1234') {
return responseBuilder.buildErrorResponse(
BadRequestException('Invalid or expired SMS code.'),
BadRequestException('Invalid or expired code.'),
);
}

final userId =
_authenticationService.getUserIdFromAuthHeader(request.headers);
_usersService.updateUser(
userId,
role: UserRole.user,
confirmedCredentials: ConfirmedCredentialsModel(email: true, phone: true),
);
final updateSuccess = _usersService.confirmPhoneNumber(userId);
if (updateSuccess) _usersService.updateUser(userId, role: UserRole.user);

return responseBuilder.buildOK(
data: _getUserJson(userId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ class UsersController extends ApiController {
router.addRequest(
RequestType.PATCH,
'/api/users/me',
_sendSmsCodeHandler,
_upsertPhoneNumberHandler,
);

router.addRequest(
Expand All @@ -59,6 +59,9 @@ class UsersController extends ApiController {
);
}

Map<String, dynamic> _getUserJson(String userId) =>
_usersService.getUserById(userId)!.toJson();

/// Resend Email Verification
Future<Response> _resendEmailVerificationHandler(Request request) async {
final userId =
Expand Down Expand Up @@ -139,10 +142,7 @@ class UsersController extends ApiController {
);
}

Map<String, dynamic> _getUserJson(String userId) =>
_usersService.getUserById(userId)!.toJson();

Future<Response> _sendSmsCodeHandler(Request request) async {
Future<Response> _upsertPhoneNumberHandler(Request request) async {
final params = await request.bodyFromFormData();
final phoneNumber = params['phoneNumber'] as String?;

Expand All @@ -158,12 +158,24 @@ class UsersController extends ApiController {
);
}

if (_usersService.isPhoneInUse(phoneNumber)) {
return responseBuilder.buildErrorResponse(
RequestConflictException('This phone number is already in use.'),
);
}

final userId =
_authenticationService.getUserIdFromAuthHeader(request.headers);
_usersService.updateUser(
userId,
phoneNumber: phoneNumber,
);
var user = _usersService.getUserById(userId);
if (user == null) throw NotFoundException('User not found.');

if (user.phoneNumber == null) {
_usersService.updateUser(
userId,
phoneNumber: phoneNumber,
);
}
_usersService.addUnconfirmedPhoneNumber(userId, phoneNumber);

return responseBuilder.buildOK(
data: _getUserJson(userId),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:{{project_name}}/base/models/user_role.dart';

class UsersRepository {
final List<UserModel> _registeredUsers = [];
final Map<String, String> _unconfirmedPhoneNumbers = {};

List<UserModel> getUsers() => _registeredUsers;

Expand All @@ -21,7 +22,30 @@ class UsersRepository {

bool isEmailInUse(String email) =>
_registeredUsers.any((user) => user.email == email);
void updateUser(

bool isPhoneInUse(String phoneNumber) =>
_registeredUsers.any((user) => user.phoneNumber == phoneNumber);

void addUnconfirmedPhoneNumber(String userId, String phoneNumber) {
_unconfirmedPhoneNumbers[userId] = phoneNumber;
}

bool confirmPhoneNumber(String userId) {
final user = getUserById(userId);
final phoneNumber = _unconfirmedPhoneNumbers[userId];
if (phoneNumber == null || user == null) return false;

updateUser(
userId,
phoneNumber: phoneNumber,
confirmedCredentials: user.confirmedCredentials.copyWith(phone: true),
);
_unconfirmedPhoneNumbers.remove(userId);

return true;
}

UserModel? updateUser(
String userId, {
String? email,
String? phoneNumber,
Expand All @@ -33,10 +57,11 @@ class UsersRepository {
final user = _registeredUsers[userIndex];
_registeredUsers[userIndex] = user.copyWith(
email: email ?? user.email,
phoneNumber: phoneNumber,
phoneNumber: phoneNumber ?? user.phoneNumber,
role: role ?? user.role,
confirmedCredentials: confirmedCredentials ?? user.confirmedCredentials,
);
return _registeredUsers[userIndex];
}

void deleteUser(String id, UserRole role) => _registeredUsers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ class UsersService {

bool isEmailInUse(String email) => _usersRepository.isEmailInUse(email);

bool isPhoneInUse(String phoneNumber) =>
_usersRepository.isPhoneInUse(phoneNumber);

void addUnconfirmedPhoneNumber(String userId, String phoneNumber) =>
_usersRepository.addUnconfirmedPhoneNumber(userId, phoneNumber);

bool confirmPhoneNumber(String userId) =>
_usersRepository.confirmPhoneNumber(userId);

void updateUser(
String userId, {
String? email,
Expand Down
Loading

0 comments on commit eecf104

Please sign in to comment.