Skip to content

Commit

Permalink
chore: add readme
Browse files Browse the repository at this point in the history
  • Loading branch information
kapiaszczyk committed Apr 24, 2024
1 parent f0ecfb5 commit b3d62c2
Showing 1 changed file with 223 additions and 0 deletions.
223 changes: 223 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
<!-- Introduction -->

# Job Board API

Prototype of backend service for a job board platform. The service provides endpoints for managing job offers with
related information, mainly company data.

<!-- ABOUT -->

## About

The service utilizes Spring Data JPA for retrieving data and Spring Web for presenting it over endpoints. The data is
cached thanks to Caffeine. The API is **globally rate limited** using bucket4j library, requests are authorized using **
JWT** with help of Spring Security and **OAuth 2.0 Resource Server**. API documentation is automatically generated
using **openAPI** and can be accessed via the Swagger UI.

## Docker image

**You can find the docker image of the whole application [here]().**

## Features

- Retrieval of job offers with various levels of detail and filtering options
- Global rate limiting
- Caching layer
- Authorization and authentication using JWT

### Data retrieval

Data stored in a PostgreSQL can be retrieved using various endpoints. All input is validated using Hibernate validator,
and the data is returned in a JSON format. The data can be filtered, sorted and paginated. Most frequently used queries
are cached using Caffeine, which is invalidated after a certain period of time.

### Rate Limiting

Rate limiting is implemented using bucket4j. The limit is globally applied to all requests and refuses handling requests
over the limit. The limit can be set through environment variables.

### Security and authentication

User can register and log in using OAuth 2.0 Resource Server, that is running alongside the application. The user is
authenticated using JWT tokens. Unauthorized users can only view data, while authenticated users have different
privileges based on their role. Passwords stored in the database are hashed. The tokens are enctypted and signed using
RSA keys.

### Filters

Two filters are implement in the application - one for rate limiting and one for logging request and response details.

### Spring Actuator

Information about the application can be accessed via Spring Actuator. Access to these endpoints is restricted to
administrator and superuser accounts.

### Logging

Basic logging of errors and events is implemented using Logback via the slf4j facade. The logger logs human-readable
statements to the console and JSON statements to a file. The file is rolled over daily or when it reaches a certain
size. Archived logs are kept for a month and then deleted.
**Sensitive data is encrypted and never stored explicitly**.

### Spring AOP

A single aspect is used to log the execution time of all service methods dealing with data.

## Running and accessing the application

The application can be run using included `docker-compose.yaml`. It will be available under the `localhost:8080`
address. For running the application securely, default credentials and keys should be changed. Full documentation is
created by OpenApi and available under the `/swagger-ui.html` or `/v3/api-docs`.

The following endpoints will be available:

| **Endpoint** | **Method** | **Description** |
| --- | --- | --- |
| `/api/v1/technologies/{id}` | *PUT* | Update a technology |
| `/api/v1/job-offers/{id}` | *GET* | Get a job offer by id |
| `/api/v1/job-offers/{id}` | *PUT* | Update a job offer |
| `/api/v1/job-offers/{id}` | *DELETE* | Delete a job offer |
| `/api/v1/companies/{id}` | *PUT* | Update a company |
| `/api/v1/companies/{id}` | *DELETE* | Delete a company |
| `/api/v1/technologies` | *GET* | Get all technologies |
| `/api/v1/technologies` | *POST* | Add a technology |
| `/api/v1/job-offers` | *GET* | Get all job offers |
| `/api/v1/job-offers` | *POST* | Add a new job offer |
| `/api/v1/companies` | *GET* | Get company by id |
| `/api/v1/companies` | *POST* | Add a new company |
| `/api/v1/auth/user` | *POST* | Get user details |
| `/api/v1/auth/register` | *POST* | Register a new user |
| `/api/v1/auth/login` | *POST* | Login as a user |
| `/api/v1/job-offers/detailed` | *GET* | Get all job offers projected to detailed view |
| `/api/v1/job-offers/detailed/{id}` | *GET* | Get a job offer by id projected to detailed view |
| `/api/v1/job-offers/criteria` | *GET* | Get job offers filtered by criteria |
| `/api/v1/job-offers/criteria-paged` | *GET* | Get job offers filtered by criteria, paged and sorted |
| `/api/v1/job-offers/criteria-detailed` | *GET* | Get job offers filtered by criteria projected to detailed view |
| `/api/v1/job-offers/criteria-detailed-paged` | *GET* | Get job offers filtered by criteria, paged and sorted, projected to detailed view |
| `/api/v1/job-offers/basic` | *GET* | Get all job offers projected to basic view |
| `/api/v1/job-offers/basic/{id}` | *GET* | Get a job offer by id projected to basic view |
| `/api/v1/addresses` | *GET* | Get all addresses |
| `/api/v1/addresses/companies/{companyName}` | *GET* | Get all addresses by company name |

*This table was generated from openAPI docs
using [this script](https://github.com/kapiaszczyk/python-scripts/blob/main/extract_openapi_paths.py)*

### Environment variables

The following properties can be set from `docker-compose.yaml`:

| **Environment variable** | **Default value** | **Corresponding property name** | **Description** |
| --- | --- | --- | --- |
| `JDBC_DATABASE_URL` | jdbc:postgresql://localhost:5432/ | `spring.datasource.url` | Database url with hostname and port |
| `JDBC_DATABASE_NAME` | jobboard | `spring.datasource.url` | Name of the database |
| `JDBC_DATABASE_USERNAME` | api | `spring.datasource.username` | Database user username |
| `JDBC_DATABASE_PASSWORD` | apipassword | `spring.datasource.password` | Database user password |
| `RATE_LIMIT_BUCKET_CAPACITY` | 100 | `rate.limit.bucket.capacity` | Global bucket capacity |
| `RATE_LIMIT_REFILL_PERIOD_SECONDS` | 60 | `rate.limit.refill.period.seconds` | Refill period for the global bucket |
| `RATE_LIMIT_REFILL_TOKENS` | 100 | `rate.limit.refill.tokens` | Tokens to refill |
| `CACHE_INITIAL_CAPACITY` | 100 | `cache.initial.capacity` | Cache initial capacity |
| `CACHE_MAXIMUM_SIZE` | 500 | `cache.maximum.size` | Maximum size of the cache |
| `CACHE_EXPIRE_AFTER_WRITE_MINUTES` | 1 | `cache.expire.after.write.minutes` | Expiration time of the cache |

*This table was generated from `application.properties` docs
using [this script](https://github.com/kapiaszczyk/python-scripts/blob/main/properties_env_var_extractor.py)*

## Reliability

Integration tests were written using Mockito and Testcontainers and the application was end-to-end tested using Postman.

## Future improvements

The application is a working prototype that is standalone and not suitable for real-world use. As of the current
version, the following improvements could be made:

- limiting creation of new accounts to a specific group of users
- more detailed tests covering edge cases
- optimization of some of the operations (mainly inserting and updating data)
- caching improvements
- JWT token refreshing
- and many more

## Security considerations

Since anyone can create any type of account, the application is not suitable for real-world use.

<!-- STACK -->

## Built With

[![bucket4j][bucket4j]][bucket4j-url]
[![PostgreSQL][PostgreSQL]][PostgreSQL-url]
[![Caffeine][Caffeine]][Caffeine]
[![Springdoc][springdoc]][Springdoc-url]
[![Spring Actuator][Spring Actuator]][Spring Actuator-url]
[![Spring AOP][Spring AOP]][Spring AOP-url]
[![Spring Data JPA][Spring Data JPA]][Spring Data JPA-url]
[![Spring Security][Spring Security]][Spring Security-url]
[![Spring Web][Spring Web]][Spring Web-url]
[![Maven][Maven]][Maven-url]
[![Postman][Postman]][Postman-url]
[![Docker][Docker]][Docker-url]
[![Swagger][Swagger]][Swagger-url]

<!-- MARKDOWN LINKS & IMAGES -->

[bucket4j]: https://img.shields.io/badge/bucket4j-orange

[bucket4j-url]: https://github.com/bucket4j/bucket4j

[PostgreSQL]: https://img.shields.io/badge/PostgreSQL-blue

[PostgreSQL-url]: https://www.postgresql.org/

[Caffeine]: https://img.shields.io/badge/Caffeine-pink

[Caffeine-url]: https://github.com/ben-manes/caffeine

[Springdoc]: https://img.shields.io/badge/Springdoc-green

[Springdoc-url]: https://springdoc.org/

[Spring Actuator]: https://img.shields.io/badge/Spring_Boot_Actuator-green

[Spring Actuator-url]: https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html

[Spring AOP]: https://img.shields.io/badge/Spring_Boot_AOP-green

[Spring AOP-url]: https://docs.spring.io/spring-framework/reference/core/aop.html

[Spring Data JPA]: https://img.shields.io/badge/Spring_Boot_Data_JPA-green

[Spring Data JPA-url]: https://docs.spring.io/spring-data/jpa/reference/index.html

[Spring Security]: https://img.shields.io/badge/Spring_Boot_Security-green

[Spring Security-url]: https://docs.spring.io/spring-security/reference/index.html

[Spring Web]: https://img.shields.io/badge/Spring_Boot_Web-green

[Spring Web-url]: https://docs.spring.io/spring-framework/reference/web.html

[Mockito]: https://img.shields.io/badge/Mockito-green

[Mockito-url]: https://site.mockito.org/

[Testcontainers]: https://img.shields.io/badge/Testcontainers-blue

[Testcontainers-url]: https://testcontainers.com/

[Docker]: https://img.shields.io/badge/Docker-blue

[Docker-url]: https://www.docker.com/

[Maven]: https://img.shields.io/badge/Maven-blue

[Maven-url]: https://maven.apache.org/

[Postman]: https://img.shields.io/badge/Postman-orange

[Postman-url]: https://www.postman.com/

[Swagger]: https://img.shields.io/badge/Swagger-green

[Swagger-url]: https://swagger.io/

0 comments on commit b3d62c2

Please sign in to comment.