List of the features that have been implemented so far:
- User accounts
- Registration
- Account deletion
- Account activation via email
- Logging in
- Password reset via email
- Account types with diffrent permissions (users, admins and the owner)
- Booking appointments:
- Booking through the appointments page by first selecting a service and then followed by selecting a date
- Quick booking from the home page
- Administrative features
- Business summary and overview
- Appointments management
- Services management
- Users administration
- Work hours management
- Business statistics
- Administrative settings
- Settings
- Updating user data (name, surname and gender)
- Account security
- Password change
- Active sessions management
- Log out of every session seperately
- Log out everywhere
- Show location on a map based on IP address
- Two-Factor Authentication
- Notification settings
- Appointment reminders
- New functionality added
- Themes
- Internalization
- Translations
- Polish
- English
- Translations
-
First you need to install the dependencies:
pip install -r requirements.txt
-
Set required environment variables
API_VERSION='1.0.0 Stable' API_TITLE='<API name>' # See https://docs.python.org/3/library/logging.html#logging-levels for available logging levels LOG_LEVEL='<DEBUG/INFO...>' # Pytz timezone (see https://gist.github.com/heyalexej/8bf688fd67d7199be4a1682b3eec7568 for list of all timezones) COMPANY_TIMEZONE: str # Used mostly for email messages COMPANY_NAME='' BASE_URL='/api' # Used for account activation and password reset links FRONTEND_URL='<frontend URL>' DATABASE_USERNAME='' DATABASE_PASSWORD='' DATABASE_HOSTNAME='' DATABASE_PORT='' DATABASE_NAME='' # See https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/?h=secret#handle-jwt-tokens for more info API_SECRET='<cryptographically secure secret key used for generating JWT tokens>' ALGORITHM='<e.g. HS256>' ACCESS_TOKEN_EXPIRE_MINUTES='<e.g. 60>' PASSWORD_RESET_TOKEN_EXPIRE_MINUTES='<e.g. 10>' MAIL_VERIFICATION_COOLDOWN_MINUTES='<e.g. 5>' PASSWORD_RESET_COOLDOWN_MINUTES='<e.g. 15>' # Email service configuration used for sending account activation, password reset, appointment reminder and administrative messages MAIL_USERNAME='' MAIL_PASSWORD='' MAIL_FROM='no-reply@example.com' MAIL_PORT=587 MAIL_SERVER='' MAIL_STARTTLS=true MAIL_SSL_TLS=false USE_CREDENTIALS=true VALIDATE_CERTS=true MAIL_FROM_NAME='' # Access token obtained from https://ipinfo.io/ used for displaying info about ip addresses associated with sessions IPINFO_ACCESS_TOKEN='' # Time for which users won't be asked again to enter their passwords when performing critical operations SUDO_MODE_TIME_HOURS='<e.g. 2>' # Service durations are a multiple of this number # Thus it determines the shortest possible time a service can take # Setting it too high would probably mean a lot of wasted time between appointments # Setting it too low would make it harder for admins to reserve slots APPOINTMENT_SLOT_TIME_MINUTES='<e.g. 30>' # Determines the latest possible date users can book their appointments MAX_FUTURE_APPOINTMENT_DAYS='<e.g. 30>' # Path to JSON credentials file obtained from https://firebase.google.com/ # Used for sending notifications via FCM (see https://firebase.google.com/docs/cloud-messaging for more info) FIREBASE_SERVICE_ACCOUNT_CREDENTIALS_PATH="<path>.json"
-
Initialize dynamic resources
Working hours are stored in a JSON file in project's directory
Path to the file has to be as follows: <PROJECT_ROOT>/dynamic_resources/weekplan.json
File structure:
weekplan.json
[
// Each object represents a single day
{
"work_hours": {
"start_hour": 9,
"start_minute": 0,
"end_hour": 17,
"end_minute": 30
},
"breaks": [
// Each object represents a single break
{
"start_hour": 13,
"start_minute": 30,
// Time has to be a multiple of APPOINTMENT_SLOT_TIME_MINUTES
"time_minutes": 30
}
]
},
// Workaround to create a day off by setting the end time to the same value as start time
{
"work_hours": {
"start_hour": 9,
"start_minute": 0,
"end_hour": 9,
"end_minute": 0
},
"breaks": []
},
...
]
- Create a database and tables
Create tables using the following command (database has to be already created):
alembic upgrade head
- Run the API using Uvicorn via the provided startup script
python3 run_uvicorn.py
You can use Gunicorn as a production server.
If you are using multiple workers you need to configure your server to call the init_app() function on startup.
Example for Gunicorn:
gunicorn.conf.py
from init_app import init_app
def on_starting(_server):
init_app()
Otherwise, you can call this function in another file (e.g. main.py
)
Example startup command for Gunicorn:
gunicorn --access-logfile ./gunicorn-access.log --error-logfile ./gunicorn-error.log --workers 4 --worker-class uvicorn.workers.UvicornWorker src.main:app
If your server is using systemd as a service manager you can also create a custom unit file to make managing the API's state easier
Example systemd unit file:
[Unit]
Description = <UNIT_NAME>
After = network.target
[Service]
User = ubuntu
Group = ubuntu
WorkingDirectory = <PROJECT_ROOT>
Environment = "PATH=<PROJECT_ROOT>/venv/bin"
EnvironmentFile = <PROJECT_ROOT>/.env
ExecStart = <PROJECT_ROOT>/venv/bin/gunicorn --access-logfile ./gunicorn-access.log --error-logfile ./gunicorn-error.log --workers 4 --worker-class uvicorn.workers.UvicornWorker src.main:app
[Install]
WantedBy = multi-user.target
If you want to run the api behind a proxy (e.g. to make managing SSL ceritficates easier with Certbot) you need to make sure original IP addresses are forwarded to the API for sessions management to work correctly.
Example configuration for Nginx:
server {
server_name <list of server domain names>;
# make API accessible on <server_root>/api URL
location /api {
proxy_pass http://localhost:<local API port number>;
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $http_host;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
}
}
You can use DigitalOcean's config generation tool to generate a secure Nginx config