Abidria API
This repo contains the backend code for Abidria project. This simple app aims to be a reference to discover interesting places and also to record our past experiences, sharing content with other people.
Application domain is composed by scenes
,
which are defined as something that happened
or can be done/seen in a located place.
A group of scenes
are defined as an experience
.
A person
can use api as anonymous guest.
Later, she can register specifying just username and email.
Posterior email confirmation is required to create content.
There is no password, so login will be implemented using email token system.
A person
can save
their favourite experiences
.
For the moment, the api is only consumed by abidria-android project.
API Endpoints
GET /experiences
Request:
You can specify mine
filter param to fetch only experiences you have created.
You can also specify saved
filter param to fetch only experiences you have saved.
Both params are set to false
by default, you can ignore them.
Response:
[
{
"id": "2",
"title": "Baboon",
"description": "Mystical place...",
"picture": {
"small_url": "https://experiences/8c29.small.jpg",
"medium_url": "https://experiences/8c29.medium.jpg",
"large_url": "https://experiences/8c29.large.jpg"
},
"author_id": "3",
"author_username": "usr.nam",
"is_mine": false,
"is_saved": false
},
{
"id": "3",
"title": "Magic Castle of Lost Swamps",
"description": "Don't even try to go there!",
"picture": null,
"author_id": "5",
"author_username": "da_usr",
"is_mine": false,
"is_saved": false
}
]
POST /experiences
Request(application/x-www-form-urlencoded):
{
"title": "My travel",
"description": "and other adventures",
}
Response:
201
{
"id": "8",
"title": "My travel",
"description": "and other adventures",
"picture": null,
"author_id": "8",
"author_username": "my.name",
"is_mine": true,
"is_saved": false
}
422
{
"error": {
"source": "title",
"code": "empty_attribute",
"message": "Title cannot be empty"
}
}
PATCH /experiences/<experience_id>
Request(application/x-www-form-urlencoded):
{
"title": "",
"description": "A new description",
}
It is also allowed to not define some fields (if defined blank value will be set to blank).
Response:
200
{
"id": "8",
"title": "MainSquare",
"description": "A new description",
"picture": null,
"author_id": "8",
"author_username": "my.name",
"is_mine": true,
"is_saved": false
}
404
{
"error": {
"source": "entity",
"code": "not_found",
"message": "Entity not found"
}
}
422
{
"error": {
"source": "title",
"code": "wrong_size",
"message": "Title must be between 1 and 30 chars"
}
}
POST /experiences/<experience_id>/save/
Endpoint to save experience as favourite.
Response:
201
DELETE /experiences/<experience_id>/save/
Endpoint to unsave experience as favourite.
Response:
204
POST /experiences/<experience_id>/picture/
Request(multipart/form-data):
Param name to send the file: picture
Response:
200
{
"id": "8",
"title": "My travel",
"description": "and other adventures",
"picture": {
"small_url": "https://scenes/37d6.small.jpeg",
"medium_url": "https://scenes/37d6.medium.jpeg",
"large_url": "https://scenes/37d6.large.jpeg"
},
"author_id": "8",
"author_username": "my.name",
"is_mine": true,
"is_saved": false
}
GET /scenes/?experience=<experience_id>
Response:
[
{
"id": "5",
"title": "Plaça Mundial",
"description": "World wide square!",
"picture": {
"small_url": "https://scenes/37d6.small.jpeg",
"medium_url": "https://scenes/37d6.medium.jpeg",
"large_url": "https://scenes/37d6.large.jpeg"
},
"latitude": 1.000000,
"longitude": 2.000000,
"experience_id": "5"
},
{
"id": "4",
"title": "I've been here",
"description": "",
"picture": null,
"latitude": 0.000000,
"longitude": 1.000000,
"experience_id": "5"
},
]
POST /scenes
Request(application/x-www-form-urlencoded):
{
"title": "Plaça Major",
"description": "The main square",
"latitude": 1.2,
"longitude": 0.3,
"experience_id": "3"
}
Response:
201
{
"id": "8",
"title": "Plaça Major",
"description": "The main square",
"picture": null,
"latitude": 1.2,
"longitude": 0.3,
"experience_id": "3"
}
422
{
"error": {
"source": "title",
"code": "empty_attribute",
"message": "Title cannot be empty"
}
}
PATCH /scenes/<scene_id>
Request(application/x-www-form-urlencoded):
{
"title": "",
"description": "A new description",
"latitude": -0.3,
"longitude": 0.56,
}
It is also allowed to not define some fields (if defined blank value will be set to blank).
Response:
200
{
"id": "8",
"title": "MainSquare",
"description": "A new description",
"picture": null,
"latitude": 1.2,
"longitude": 0.56,
"experience_id": "3"
}
404
{
"error": {
"source": "entity",
"code": "not_found",
"message": "Entity not found"
}
}
422
{
"error": {
"source": "title",
"code": "wrong_size",
"message": "Title must be between 1 and 30 chars"
}
}
POST /scenes/<scene_id>/picture/
Request(multipart/form-data):
Param name to send the file: picture
Response:
200
{
"id": "8",
"title": "Plaça Major",
"description": "The main square",
"picture": {
"small_url": "https://scenes/37d6.small.jpeg",
"medium_url": "https://scenes/37d6.medium.jpeg",
"large_url": "https://scenes/37d6.large.jpeg"
},
"latitude": 1.2,
"longitude": 0.3,
"experience_id": "3"
}
POST /people/
This endpoint is to create a person
instance.
This person
will be anonymous guest (until registration)
and has limited permissions (basically get information).
The response of this endpoint will be auth_token
credentials,
composed by access_token
and refresh_token
,
that have to be persisted on the client.
Request(application/x-www-form-urlencoded):
{
"client_secret_key": "XXXX",
}
Response:
201
{
"access_token": "A_T_12345",
"refresh_token": "R_T_67890",
}
PATCH /people/me
This endpoint is to register a guest person
.
Username and email is required.
person
status change to registered
but will not have full permissions until email confirmation
(an email is sent with confirmation token).
Request(application/x-www-form-urlencoded):
(http headers)
Authorization: Token ABXZ
(previous endpoint access_token
response)
{
"username": "user.name",
"email": "[email protected]"
}
Response:
200
{
"is_registered": true,
"username": "user.name",
"email": "[email protected]",
"is_email_confirmed": false
}
POST /people/me/email-confirmation
This endpoint is to confirm email and finish person
register.
On previous endpoint, an email is sent with a confirmation token.
That token has to be sent as parameter.
Request(application/x-www-form-urlencoded):
(http headers)
Authorization: Token ABXZ
{
"confirmation_token": "C_T_ABXZ",
}
Response:
200
{
"is_registered": true,
"username": "user.name",
"email": "[email protected]",
"is_email_confirmed": true
}
Documentation
This project has been developed using Django framework, with Postgres as database and S3 as storage service.
Code structure follows a Clean Architecture approach (explained in detail here), emphasizing on code readability, responsibility decoupling and unit testing.
Authentication part is a little bit custom (to better fit requirements and also with learning purposes).
It doesn't uses Django User
model nor django-rest-framework, everything is handmade
(everything but cryptography, obviously :) ) and framework untied.
Special things are described here:
- There is anonymous guest user status. That allow users to enter to the app without register but we can track and analyze them. That also helps making app more secure because calls are made from guest users but authenticated and we can also control the number of registrations.
- There is no password. Guest user can register just with username and email, which makes registration process easier. Login will be implemented using token validation via email.
- User is called person. Developer tends to treat a user like a model or a number,
person
naming aims to remember who is really behind the screen.
Setup
Follow these instructions to start working locally on the project:
- Download code cloning this repo:
git clone https://github.com/jordifierro/abidria-api.git
- Install postgres and run:
./abidria/setup/postgres.sh
to create user and database.
- Run postgres:
postgres -D /usr/local/var/postgres &
- Install python version specified on
runtime.txt
and run:
virtualenv -p `which python3.6` ../env
- Add this to the end of
../env/bin/activate
file:
source abidria/setup/envvars.sh
- Get into the environment:
source ../env/bin/activate
and install dependencies:
pip install -r requirements.txt
- Migrate database:
python manage.py migrate
- Create django admin super user:
python manage.py createsuperuser
- Finally, you should be able to run unit and integration tests:
pytest # python tests
python manage.py test # django tests
Once we have made the first time setup, we can start everything up running:
source abidria/setup/startup.sh