It's a demo app to test the following technical stack:
- Java 17
- Spring Boot 3+
- Spring Web Flux
- Spring Data JDBC
- Reactor
- PostgreSQL
- Spock Framework
- Springdoc OpenAPI
- Spring GraphQL
- Spring Security with JWT auth
- Resilience4j
We must develop the user service, that allows us to manage basic operations with User and Customer domain objects, as well as customer registration and authentication (optional subtask).
The service will have the 2 main entities:
- User - represents the "internal application user", that is user for private administration app, and identity, that is used for authentication and contains the user type (ADMIN, MANAGER, CUSTOMER).
- Customer - user extension, the "application end customer" or "public application user", that includes additional data.
- id
- login
- name
- surname
- password
- type - ADMIN/MANAGER/CUSTOMER
- date of birth
- country of residence
- identity number
- passport number
- registration IP address (not exposed to customer)
- registration country (not exposed to customer)
Create a new user by supplied data, password must be stored in DB in encrypted form.
{
"login": "some login",
"name": "some name",
"surname": "some surname",
"email": "some email",
"password": "some password",
"type": "ADMIN"
}
{
"id": "some id",
"login": "some login"
}
All request data fields must be not empty and have some min/max length limits (on your choice). Email address must be of a valid form (use corresponding RegEx pattern).
Sample error responses:
HTTP/1.1 400 Bad Request
{
"login": "Login min length is 6 chars",
"email": "Email is not valid",
"name": "Name can not be empty",
"password": "Password min length is 8 chars",
"surname": "Surname can not be empty"
}
- Supported types are only ADMIN/MANAGER.
- Login must be unique.
- Email must be unique.
Sample error responses:
HTTP/1.1 403 Forbidden
{
"login": "Supplied login is already taken"
}
HTTP/1.1 403 Forbidden
{
"email": "Supplied email is already taken"
}
Return the User object data except password.
{
"id": 35,
"login": "some-login",
"name": "some name",
"surname": "some surname",
"email": "[email protected]",
"type": "ADMIN"
}
PUT /user/35
body:
{
"login": "updated login",
"name": "updated name",
"surname": "some surname",
"email": "[email protected]",
"type": "ADMIN"
}
{
"id": 35,
"login": "updated login",
"name": "updated name",
"surname": "updated surname",
"email": "[email protected]",
"type": "ADMIN"
}
Must be the same as for "create new user" endpoint.
Return a pageable list of users (user type is ADMIN/MANAGER) with specified offset and limit.
GET /users?offset=0&limit=2
[
{
"id": 26,
"login": "some login",
"name": "some name",
"surname": "some surname",
"email": "[email protected]",
"type": "ADMIN"
},
{
"id": 27,
"login": "some login2",
"name": "some name2",
"surname": "some surname2",
"email": "[email protected]",
"type": "MANAGER"
}
]
Return a pageable list of customers (user type is CUSTOMER) with specified offset and limit. All customer data must be returned, this endpoint will be used only in admin application.
GET /customers?offset=0&limit=2
[
{
"id": 1,
"countryOfResidence": "US",
"dateOfBirth": "09-12-1997",
"identityNumber": "101297-10111",
"passportNumber": "LV9384938498",
"registrationCountry": "LV",
"registrationIp": "88.22.33.44",
"userId": 26,
"login": "some login",
"email": "[email protected]",
"name": "some name",
"surname": "some surname"
},
{
"id": 2,
"countryOfResidence": "US",
"dateOfBirth": "03-12-1980",
"identityNumber": "041289-15717",
"passportNumber": "US3948938433",
"registrationCountry": "US",
"registrationIp": "92.33.45.122",
"userId": 27,
"login": "some login",
"email": "[email protected]",
"name": "some name",
"surname": "some surname"
}
]
Return the customer user data for the specified login. All data must be returned, this endpoint will be used only in admin application.
GET /customer/login
{
"id": 64,
"login": "login",
"email": "[email protected]",
"name": "some name",
"surname": "some surname",
"countryOfResidence": "US",
"dateOfBirth": "06-12-1982",
"identityNumber": "identity number",
"passportNumber": "passport number",
"registrationCountry": "XX",
"registrationIp": "127.0.0.1",
"userId": 320
}
All customer object responses must not contain "id" field.
Create a new user and related customer by supplied data.
The stored Customer domain object must include the following data:
- Registration IP address. It must be resolved using incoming HTTP request taking into account, that our service can be deployed behind a reverse proxy or load balancer. This field must not be exposed to the end public users of our public application and will be accessible only for admins and managers in our administration application.
- Registration country. It must be resolved via some public external GeoIP HTTP service, e.g. http://reallyfreegeoip.org. When implementing this external service call, you must think about application end users (system responsiveness) and reduce the response time of the parent REST endpoint, so the resolution can be done in async way. You also should think about the overall system stability and how to protect it from external service performance degradation and failures (system resiliency). This field also must not be exposed to the end public users.
POST /customer/registration
body:
{
"login": "some login",
"name": "some name",
"surname": "some surname",
"email": "[email protected]",
"password": "some password",
"dateOfBirth": "12-12-1981",
"countryOfResidence": "DE",
"identityNumber": "identity number",
"passportNumber": "passport number"
}
{
"login": "some login",
"email": "[email protected]",
"name": "some name",
"surname": "some surname",
"dateOfBirth": "12-12-1981",
"countryOfResidence": "UK",
"identityNumber": "identity number",
"passportNumber": "passport number"
}
All request data fields must be not empty and have some min/max length limits (on your choice). Email address must be of a valid form (use corresponding RegEx pattern).
- Login must be unique.
- Email must be unique.
Show only login, name, surname, email, date of birth and country of residence.
GET /customer/profile/some-login
{
"login": "some-login",
"name": "some name",
"surname": "some surname",
"email": "[email protected]",
"dateOfBirth": "09-12-1997",
"countryOfResidence": "US"
}
POST /auth
body:
{
"login": "some login",
"password": "some password"
}
{
"token": "JWT token"
}
2. PUT /customer/my/profile - update current (authenticated) customer's profile by id (requires JWT auth with user type CUSTOMER).
Return all data except id, password, IP and registration country.
PUT /customer/my/profile
Authorization:"Bearer ${JWT_TOKEN}"
body:
{
"name": "some name",
"surname": "some surname",
"email": "[email protected]",
"dateOfBirth": "11-11-1971",
"countryOfResidence": "UK",
"identityNumber": "identity number",
"passportNumber": "passport number"
}
{
"login": "somelogin16",
"name": "some name",
"surname": "some surname",
"email": "[email protected]",
"dateOfBirth": "11-11-1971",
"countryOfResidence": "DE",
"identityNumber": "identity number",
"passportNumber": "passport number"
}
3. GET /customer/my/profile - get current (authenticated) customer's profile (requires JWT auth with user type CUSTOMER).
Show all data except id, password, IP and registration country.
GET /customer/my/profile
Authorization:"Bearer ${JWT_TOKEN}"
{
"login": "some login",
"name": "some name",
"email": "[email protected]",
"surname": "some surname",
"countryOfResidence": "US",
"dateOfBirth": "12-12-1981",
"identityNumber": "identity number",
"passportNumber": "passport number"
}
Return the current authenticated user (ADMIN or MANAGER).
GET /me
Authorization:"Bearer ${JWT_TOKEN}"
{
"id": 26,
"login": "login",
"name": "some name",
"surname": "some surname",
"email": "[email protected]",
"type": "ADMIN"
}