Solution was provided using Python 3.11, Django, Django REST, Gunicorn and database is SQLite.
Testing was done using Django flavor of untitest module.
Solution comes included with Django Admin Site. Using admin site it is possible to register new users and generaly to browse database and make CRUD operations on provided tables.
I also provided the access to REST Browsable API.
API Authentication is provided using Django REST Knox library.
User of the REST API is supposed to login using his username and password in order to obtain token. Token is then used to authenticate API endpoints.
Knox provides one token per call to the login view - allowing each client to have its own token which is deleted on the server side when the client logs out. Knox also provides an optional setting to limit the amount of tokens generated per user.
Knox tokens are only stored in an encrypted form. Even if the database were somehow stolen, an attacker would not be able to log in with the stolen credentials.
I generated self signed certificate and key using OpenSSL library for Gunicorn's SSL transport.
Superuser password and Django secret key are also provided as textual files that can be modified.
All the secrets are in the secrets folder.
In real production these secrets would be used in CI/CD in encrypted form and services would use some secret management service. They would never be passed around in plain text like this
Development Server: http://127.0.0.1:8080
Production Server: https://127.0.0.1:443
Defined in urls.py and urls.py
- /admin/ - Django Admin Site
- /api/clicks/campaign/:id/ - Campaign (with provided id) clicks
- /api/browser/ - REST Browser
- /api/auth/login - Login endpoint
- /api/auth/logout - Logout endpoint
- /api/auth/logoutall - Invalidate all tokens endpoint
- POST /api/auth/login
Required headers
- Authorization: Basic username:password -
Credentials should be encoded using base64. Example in tests.py:
base64.b64encode(f'{USERNAME}:{PASSWORD}'.encode()).decode()
- GET /api/clicks/campaign/:id[int]/?after_date=:after_date[str]&before_date=:before_date[str]
Path Parameters
- id - campaign ID
Query Parameters
- after_date - After date string in the format "YYYY-MM-DD HH:MM:SS"
- before_date - Before date string in the format "YYYY-MM-DD HH:MM:SS"
Required headers
- Authorization: Token token_value
Example:
https://127.0.0.1/api/clicks/campaign/4510461/?after_date=2021-11-07+03:10:00&before_date=2021-11-07+03:30:00
Install virtual environment:
$ python -m virtualenv env
Activate virtual environment:
On macOS and Linux:
$ source env/bin/activate
On Windows:
$ .\env\Scripts\activate
Install dependencies:
$ pip install -r requirements.txt
This is the easiest way to run.
For non docker commands execute Prerequisites.
All the commands are defined in the Makefile.
-
bootstrap - Bootstraps Django. Includes: clean, migrate, create_superuser, import_data, collectstatic
-
clean - Deletes db.sqlite3 and generated src/static folder.
-
migrate - Applies migration
-
create_superuser - Creates Django super user (admin)
-
import_data - Imports data/click_log.csv into SQLite
-
collectstatic - Generates static files to serve with Gunicorn and built in dev server
-
run_tests - Runs tests
-
run_dev_server - Runs development server on http://127.0.0.1:8080
-
run_prod_server - Runs Gunicorn server on https://127.0.0.1:443
-
docker_build - Builds Docker image
-
docker_run_tests - Runs tests using generated Docker image
-
docker_run_dev_server - Runs development server on http://127.0.0.1:8080 inside Docker container
-
docker_run_prod_server - Runs Gunicorn server on https://127.0.0.1:443 inside Docker container
All the commands are run from the root of the project in the form:
$ make command
Examples:
$ make bootstrap
$ make run_tests
$ make docker_run_prod_server
You can run the application from the command line with manage.py.
Before running any command first execute Prerequisites.
From the root of the project:
Run migrations:
$ python src/manage.py migrate
Import data/click_log.csv into SQLite:
$ python src/manage.py import_clicks_from_csv --path data/click_log.csv
Create Django super user (admin):
$ python src/manage.py createsuperuser --email [email protected] --username admin
Generate static files to serve with Gunicorn and built in dev server:
$ python src/manage.py collectstatic
Run development server on port 8000:
$ python src/manage.py runserver
Run Gunicorn server on port 443, using SSL:
$ export DEBUG=False; export SECRET_KEY="${cat secrets/secret.key}"; cd src; \
python -m gunicorn core.wsgi \
-b :443 --keyfile ../secrets/key.pem --certfile ../secrets/cert.pem
It is also possible to run servers and tests using Docker.
Build the Docker image:
$ docker build -t reljicd/clicks_api --build-arg DJANGO_SUPERUSER_PASSWORD=very_secret_password -f docker/Dockerfile .
Run development server on http://127.0.0.1:8080 inside Docker container:
$ docker run -p 8000:8000 --rm reljicd/clicks_api manage.py runserver 0.0.0.0:8000
Run Gunicorn server on https://127.0.0.1:443 inside Docker container:
$ docker run -p 443:443 -e SECRET_KEY="${cat secrets/secret.key}" -e DEBUG=False --rm reljicd/clicks_api
It is possible to add additional admin user who can login to the admin site. Run the following command:
$ python src/manage.py createsuperuser
Enter your desired username and press enter.
Username: admin_username
You will then be prompted for your desired email address:
Email address: [email protected]
The final step is to enter your password. You will be asked to enter your password twice, the second time as a confirmation of the first.
Password: **********
Password (again): *********
Superuser created successfully.
Go to the web browser and visit http://127.0.0.1:8000/admin
(dev)
or https://127.0.0.1/admin
(prod)
Go to the web browser and visit http://127.0.0.1:8000/api/browser/
(dev)
or https://127.0.0.1/api/browser/
(prod)
Tests can be found in tests.py.
Before running tests using CLI or Make first execute Prerequisites.
Using manage.py:
$ python src/manage.py test clicks
Using make:
$ make run_tests
It is also possible to run tests using Docker. First build the image following instructions in Make or Docker
Using docker CLI:
$ docker run --rm reljicd/clicks_api manage.py test clicks
Using make:
$ make docker_run_tests