Project Structure
The main idea behind the project structure is the separation of concerns.
Main principles to follow;
- API controllers are only responsible for specifying processing steps. They should not contain or depend any implementation details about database or models.
- Database-related operations should be abstracted from the rest of the system. That way database changing costs could be minimal.
- Model related data structures should be separated.
With these principles in mind there will be 10 seperate projects;
Project | Contents | References |
---|---|---|
Core [Class Library] | Models, Exceptions, Interfaces | - |
Core.Tests [xUnit Test Project] | Tests for Core library | Core |
Persistence [Class Library] | Database-related operations for both api and external services | Domain |
Persistence.Tests [xUnit Test Project] | Tests for Persistence library | Persistence |
Infrastructure [Class Library] | External services (like notification, stmp, logging etc.) | Domain, Persistence |
Infrastructure.Tests [xUnit Test Project] | Tests for Infrastructure library | Infrastructure |
Application [Class Library] | Application logic | Domain, Persistence, Infrastructure |
Application.Tests [xUnit Test Project] | Tests for Application library | Application |
Api [Web Api Project] | Controllers (get request and return response) | Application |
Api.Tests [xUnit Test Project] | Tests for Api project | Api |
Versioning
Project will follow rolling release concept.
Response Codes
Code | Use Cases |
---|---|
200 OK | Response to a successful GET, PUT, PATCH or DELETE requests. |
201 Created | Response to a successful POST request which results in a creation. |
204 No Content | Response to a successful request which doesn't have body. |
304 Not Modified ❓ | Useful when HTTP caching is present. (Not yet planned.) |
400 Bad Request | Response to an unsuccessful request with bad data. |
401 Unauthorized | Response to an unsuccessful request with invalid authentication. |
403 Forbidden | Response to an unsuccessful request with valid authentication but with invalid authorization. |
404 Not Found | Response to an unsuccessful request made to unknown endpoint. |
405 Method Not Allowed | Response to an unsuccessful request which isn't allowed for authenticated user. |
410 Gone | Response to an unsuccessful request made to endpoint that no longer available. |
415 Unsupported Media Type | Response to an unsuccessful request with incorrect content type. |
429 Too Many Requests ❓ | Useful when the request rejected due to rate limiting. (Not yet planned.) |
Endpoint Naming Strategy
Endpoints will follow this main structure;
/resource/:id/:subResource/:subResourceID
For example;
Resource | POST | GET | PUT (replacing) | PATCH (partial update) | DELETE |
---|---|---|---|---|---|
/users | Creates new user. | Returns user list. | Method not allowed. (405) | Method not allowed. (405) | Method not allowed. (405) |
/books/1 | Method not allowed. (405) | Returns book info. | Replaces book info. | Updates book info. | Removes book. |
Filtering
For any filterable resource there will be filter query.
For example;
GET /users?is_public=true
Will return only users with public profile.
Paging
Default page is 1 and page size is 30 items.
Example;
GET /genres?page=2&pageSize=5
Will skip first 5 genres and return 2nd 5 genres.
Detailed endpoint list can be found in Endpoints section.
Authentication
When the user successfully logs in using their credentials, a JSON Web Token will be returned.
Then authenticated requests should feature Authorization header using the Bearer schema;
Authorization: Bearer <token>
Authorization
Main authorization model is role based using JWT claims until MVP.
After MVP more complex authorization system should be a way to go.
Database
PostgreSQL 12 will be main choice until MVP.
Endpoints
This section will be replaced eventually by API documentation.
Cemiyet and event related endpoints will be added prior to their update.
Update: Implemented endpoints: API Docs.
Work In Progress
Users
user_id: uuid
page: int
user_ids: uuid array
Method | Endpoint |
---|---|
POST | /register |
POST | /login |
POST | /logout |
POST | /users |
GET | /users |
GET | /users?page={{page}} |
GET | /users/{{user_id}} |
PUT | /users/{{user_id}} |
PATCH | /users/{{user_id}} |
DELETE | /users/{{user_id}} |
DELETE | /users?user_ids={{user_ids}} |
GET | /me |
PUT | /me |
PATCH | /me |
DELETE | /me |
UsersBooks
user_id: uuid
page: int
book_isbn: char(13)
book_isbns: char(13) array
Method | Endpoint |
---|---|
POST | /me/books |
GET | /me/books |
GET | /me/books?page={{page}} |
PUT | /me/books |
DELETE | /me/books/{{book_isbn}} |
DELETE | /me/books?book_isbns={{book_isbns}} |
POST | /users/{{user_id}}/books |
GET | /users/{{user_id}}/books |
GET | /users/{{user_id}}/books?page={{page}} |
PUT | /users/{{user_id}}/books |
DELETE | /users/{{user_id}}/books/{{book_isbn}} |
DELETE | /users/{{user_id}}/books?book_isbns={{book_isbns}} |
UsersBooksTags
user_id: uuid
page: int
book_id: char(13)
tag_ids: char(13) array
Method | Endpoint |
---|---|
POST | /me/books/{{book_id}}/tags |
GET | /me/books/{{book_id}}/tags |
PUT | /me/books/{{book_id}}/tags |
DELETE | /me/books/{{book_id}}/tags |
DELETE | /me/books/{{book_id}}/tags?tag_ids={{tag_ids}} |
POST | /users/{{user_id}}/books/{{book_id}}/tags |
GET | /users/{{user_id}}/books/{{book_id}}/tags |
PUT | /users/{{user_id}}/books/{{book_id}}/tags |
DELETE | /users/{{user_id}}/books/{{book_id}}/tags |
DELETE | /users/{{user_id}}/books/{{book_id}}/tags?tag_ids={{tag_ids}} |
UsersLists
user_id: uuid
page: int
list_id: uuid
list_ids: uuid array
Method | Endpoint |
---|---|
POST | /me/lists |
GET | /me/lists |
GET | /me/lists?page={{page}} |
GET | /me/lists/{{list_id}} |
PUT | /me/lists/{{list_id}} |
DELETE | /me/lists/{{list_id}} |
DELETE | /me/lists?list_ids={{list_ids}} |
POST | /users/{{user_id}}/lists |
GET | /users/{{user_id}}/lists |
GET | /users/{{user_id}}/lists?page={{page}} |
GET | /users/{{user_id}}/lists/{{list_id}} |
PUT | /users/{{user_id}}/lists/{{list_id}} |
DELETE | /users/{{user_id}}/lists/{{list_id}} |
DELETE | /users/{{user_id}}/lists?list_ids={{list_ids}} |
UsersListsBooks
user_id: uuid
page: int
list_id: uuid
book_isbn: char(13)
book_isbns: char(13) array
Method | Endpoint |
---|---|
POST | /me/lists/{{list_id}}/books |
GET | /me/lists/{{list_id}}/books |
GET | /me/lists/{{list_id}}/books?page={{page}} |
GET | /me/lists/{{list_id}}/books/{{book_isbn}} |
DELETE | /me/lists/{{list_id}}/books/{{book_isbn}} |
DELETE | /me/lists/{{list_id}}/books?book_isbns={{book_isbns}} |
POST | /users/{{user_id}}/lists/{{list_id}}/books |
GET | /users/{{user_id}}/lists/{{list_id}}/books |
GET | /users/{{user_id}}/lists/{{list_id}}/books?page={{page}} |
GET | /users/{{user_id}}/lists/{{list_id}}/books/{{book_isbn}} |
DELETE | /users/{{user_id}}/lists/{{list_id}}/books/{{book_isbn}} |
DELETE | /users/{{user_id}}/lists/{{list_id}}/books?book_isbns={{book_isbns}} |
UsersTags
user_id: uuid
page: int
tag_id: uuid
tag_ids: uuid array
Method | Endpoint |
---|---|
POST | /me/tags |
GET | /me/tags |
GET | /me/tags?page={{page}} |
GET | /me/tags/{{tag_id}} |
PUT | /me/tags/{{tag_id}} |
DELETE | /me/tags/{{tag_id}} |
DELETE | /me/tags?tag_ids={{tag_ids}} |
POST | /users/{{user_id}}/tags |
GET | /users/{{user_id}}/tags |
GET | /users/{{user_id}}/tags?page={{page}} |
GET | /users/{{user_id}}/tags/{{tag_id}} |
PUT | /users/{{user_id}}/tags/{{tag_id}} |
DELETE | /users/{{user_id}}/tags/{{tag_id}} |
DELETE | /users/{{user_id}}/tags?tag_ids={{tag_ids}} |
UsersTagsBooks
user_id: uuid
page: int
tag_id: uuid
book_id: char(13)
book_ids: char(13) array
Method | Endpoint |
---|---|
POST | /me/tags/{{tag_id}}/books |
GET | /me/tags/{{tag_id}}/books |
GET | /me/tags/{{tag_id}}/books?page={{page}} |
GET | /me/tags/{{tag_id}}/books/{{book_id}} |
DELETE | /me/tags/{{tag_id}}/books/{{book_id}} |
DELETE | /me/tags/{{tag_id}}/books?book_ids={{book_ids}} |
POST | /users/{{user_id}}/tags/{{tag_id}}/books |
GET | /users/{{user_id}}/tags/{{tag_id}}/books |
GET | /users/{{user_id}}/tags/{{tag_id}}/books?page={{page}} |
GET | /users/{{user_id}}/tags/{{tag_id}}/books/{{book_id}} |
DELETE | /users/{{user_id}}/tags/{{tag_id}}/books/{{book_id}} |
DELETE | /users/{{user_id}}/tags/{{tag_id}}/books?book_ids={{book_ids}} |
EditorsPublishers
publisher_id: uuid
editor_id: uuid (user id)
editor_ids: uuid array (user ids)
Method | Endpoint |
---|---|
POST | /publishers/{{publisher_id}}/editors |
GET | /publishers/{{publisher_id}}/editors |
DELETE | /publishers/{{publisher_id}}/editors/{{editor_id}} |
DELETE | /publishers/{{publisher_id}}/editors?editor_ids={{editor_ids}} |
EditorsAuthors
author_id : uuid
editor_id : uuid (user id)
editor_ids: uuid array (user ids)
Method | Endpoint |
---|---|
POST | /authors/{{author_id}}/editors |
GET | /authors/{{author_id}}/editors |
DELETE | /authors/{{author_id}}/editors/{{editor_id}} |
DELETE | /authors/{{author_id}}/editors?editor_ids={{editor_ids}} |
EditorsGenres
genre_id : uuid
editor_id : uuid (user id)
editor_ids: uuid array (user ids)
Method | Endpoint |
---|---|
POST | /genres/{{genre_id}}/editors |
GET | /genres/{{genre_id}}/editors |
DELETE | /genres/{{genre_id}}/editors/{{editor_id}} |
DELETE | /genres/{{genre_id}}/editors?editor_ids={{editor_ids}} |