/zolza-hairstyles-api

Hairdressing salon appointments management backend api

Primary LanguagePythonGNU General Public License v3.0GPL-3.0

GitHub Workflow Status GitHub last commit GitHub repo size

Endpoint Badge Endpoint Badge

Zołza Hairstyles Hairdressing Salon

Appointments management system - API repo

Table of Contents

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
  1. First you need to install the dependencies:

    pip install -r requirements.txt
  2. 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"
  3. 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": []
  },
  ...
]
  1. Create a database and tables

Create tables using the following command (database has to be already created):

alembic upgrade head
  1. 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

GPL-3.0