Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Welcome to the AREA documentation.

What is AREA?

AREA (Action-REAction) is an automation platform that connects different services to create automated workflows. Similar to IFTTT or Zapier, AREA allows users to define triggers (Actions) from one service and automated responses (REActions) from another.

Example Use Cases

  • Facebook to Spotify: Automatically send a post to Facebook as soon as you start playing music
  • GitHub to Gmail: Send a email when an issue is created on GitHub
  • Time-based Actions: Execute tasks at specific dates or times

Architecture

The AREA project consists of three main components:

  1. Application Server (Flask REST API on port 8080)

    • Handles all business logic
    • Manages user authentication and authorization
    • Executes AREAs through hooks
    • Provides REST API endpoints
  2. Web Client (React app on port 8081)

    • Browser-based user interface
    • Service configuration
    • AREA creation and management
  3. Mobile Client (Flutter Android APK)

    • Native Android application
    • Same functionality as web client
    • Available for download from web client

Getting Started

Prerequisites

Before you begin, ensure you have the following installed:

  • Docker 20.10+
  • Docker Compose 2.0+
  • Git 2.30+

Optional (for local development without Docker):

  • Python 3.12+
  • Node.js 18+

Quick Start

  1. Clone the repository

    git clone https://github.com/EpitechPGE3-2025/G-DEV-500-TLS-5-2-area-3 # or https://github.com/CrackedOnTiti/AREA.git
    cd AREA
    
  2. Start all services with Docker Compose

    docker-compose up --build
    
  3. Access the application

    • Flask REST API: http://localhost:8080
    • Web Client: http://localhost:8081
    • API Info: http://localhost:8080/about.json

First Steps

  1. Register a new user account
  2. Subscribe to available services
  3. Create your first AREA by connecting an Action to a REAction
  4. Watch your automation run!

Project Structure

AREA/
├── client/
│   ├── apk/             # Flutter mobile app
│   ├── web/             # React web client
│   └── Dockerfile.mobile
│   ├── Dockerfile.web
├── documents/           # mdBook documentation
├── server/              # Flask backend
│   ├── database/        # Database models
│   ├── routes/          # API endpoints
│   ├── scheduler/       # Core workflow activator
│   ├── static/          # Assets
│   ├── utils/           # Helper functions
│   ├── app.py           # Main application
│   ├── config.py        # Environment configuration
│   ├── Dockerfile
│   ├── gunicorn.conf.py # Gunicorn configuration
│   ├── init_db.py       # Database preperator/seed_data.py caller
│   ├── requirements.txt # Python environment dependecies
│   └── seed_data.py     # Mandatory data implant
├── .env.example
├── .gitignore
├── docker-compose.yml   # Multi-container orchestration
├── HOWTOCONTRIBUTE.md
└── README.md

Contributing

Interested in contributing to AREA?

Check out our contribution guide: HOWTOCONTRIBUTE.md

The guide covers:

  • Code style and conventions
  • Pull request process
  • Issue reporting
  • Development setup

Tech Stack

This document outlines the technologies, frameworks, and tools used in the AREA project.

┌─────────────────────────────────────────┐
│           Frontend Layer                │
├─────────────────┬───────────────────────┤
│   Web Client    │    Mobile Client      │
│   React         │    Flutter            │
│   Port 8081     │    Android APK        │
└────────┬────────┴──────────┬────────────┘
         │                   │
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │   Backend Layer   │
         │   Flask REST API  │
         │   Port 8080       │
         └─────────┬─────────┘
                   │
         ┌─────────▼─────────┐
         │  Database Layer   │
         │   PostgreSQL      │
         │   Port 5432       │
         └───────────────────┘

Frontend Technologies

Framework & Language

  • Framework: React
  • Language: JavaScript
  • Port: 8081
  • Purpose: Web-based user interface
  • Container: Node.js 18 Alpine

Why React for Web: React’s component-based architecture provides excellent reusability and maintainability for complex web interfaces. The virtual DOM ensures efficient updates, and the massive ecosystem offers pre-built solutions for common challenges. TypeScript adds type safety, catching errors at compile time rather than runtime. Vite provides significantly faster build times and hot module replacement compared to Create React App.

Why not Next.js: While Next.js offers SSR/SSG benefits, our application is highly interactive and client-side heavy. React with Vite provides better development experience and faster builds for our SPA requirements without unnecessary SSR complexity.


Mobile Frontend

Framework & Language

  • Framework: Flutter 3.0+
  • Language: Dart
  • Platform: Android APK
  • Purpose: Native mobile application

Why Flutter for Mobile: Flutter provides truly native performance with a single codebase for both iOS and Android. The hot reload feature dramatically speeds up development, and the widget-based architecture offers excellent customization. Dart’s ahead-of-time compilation results in fast startup times and smooth animations.

Why not React Native: School requirements.


Backend Technologies

Application Server

  • Framework: Flask 3.0.0
  • Language: Python 3.12
  • Port: 8080
  • Purpose: REST API server, business logic
  • Key Libraries:
    • Flask-SQLAlchemy - ORM for database operations
    • Flask-CORS - Cross-Origin Resource Sharing
    • psycopg2-binary - PostgreSQL adapter

Database

  • DBMS: PostgreSQL 15
  • Port: 5432
  • Purpose: Data persistence for users, services, actions, reactions, and AREAs

Why Python, python is the only language I used so far for backend and I love it… But for more serious reasons it’s easy to setup, I have a lot of experience with it, the community is awsome and most importantly, let’s say I want to add a AI chatbot well boto3 is quite literally made for python so yeah kind of thinking ahead but generally speaking it’s simplicity is verry time efficient

Why Postgres, I have had a lot of experience with many other options such as mySql, Sqlite, mongoDb, DynamoDB… But overall Postgres takes the cake for a SQL based DB. It has relationships unlike mongo, is quite fast, and has more features than mySql. I would have potentially went for DynamoDB if i where to deploy this to have a “Scalable” database but this is easily enough for this project. PostgreSql 🗣️🔛🔝🔥


DevOps & Infrastructure

Containerization

  • Docker: Application containerization
  • Docker Compose: Multi-container orchestration
  • Volumes:
    • postgres_data - Database persistence
    • mobile_build - Shared volume for APK distribution

Container Services

services:
  database    → PostgreSQL 15
  server      → Flask REST API
  client_web  → React web app
  client_mobile → Flutter

CI/CD

  • Platform: GitHub Actions
  • Workflows:
    • deploy.yml - Automated mdBook documentation deployment to GitHub Pages
    • mirror.yml - Repository sync to Epitech servers

Development Tools

Documentation

Version Control

  • Git: Source control
  • GitHub: Repository hosting
  • Branches:
    • main - Production branch
    • dev - Development branch

API Architecture

REST API Endpoints

The Flask server exposes a RESTful API for client communication:

  • Authentication: /api/auth/*
  • Services: /api/services
  • Actions: /api/actions
  • Reactions: /api/reactions
  • AREAs: /api/areas
  • About: /about.json (required by spec)

Data Format

  • Request/Response: JSON
  • Authentication: JWT tokens
  • CORS: Enabled for cross-origin requests

Learn More

Configuration

This guide covers all environment variables and configuration options for the AREA application.

Environment Variables

The application is configured through environment variables. Create a .env file in the server/ directory with the following variables:


Database Configuration

DATABASE_URL

Description: PostgreSQL database connection string

Required: No

Default: postgresql://area_user:area_password@localhost:5432/area_db

Format: postgresql://[user]:[password]@[host]:[port]/[database]

Example:

DATABASE_URL=postgresql://myuser:mypassword@localhost:5432/area_db

Authentication & Security

JWT_SECRET_KEY

Description: Secret key for signing JWT tokens. Also used for Flask session management.

Required: Yes

Default: None

Security Note: Use a strong, random string. Never commit this to version control.

Example:

JWT_SECRET_KEY=your-super-secret-key-here-change-this-in-production

Generate a secure key:

openssl rand -hex 32

CORS Configuration

CORS_ORIGINS

Description: Comma-separated list of allowed origins for CORS requests

Required: No

Default: * (allows all origins)

Example:

# Allow all origins (development only)
CORS_ORIGINS=*

# Allow specific origins (production)
CORS_ORIGINS=http://localhost:3000,https://myapp.com

Google OAuth2 Configuration

These credentials are required for Google OAuth login and Gmail integration.

GOOGLE_CLIENT_ID

Description: OAuth 2.0 Client ID from Google Cloud Console

Required: Yes (for OAuth features)

Default: None

Example:

GOOGLE_CLIENT_ID=123456789-abcdefghijklmnop.apps.googleusercontent.com

GOOGLE_CLIENT_SECRET

Description: OAuth 2.0 Client Secret from Google Cloud Console

Required: Yes (for OAuth features)

Default: None

Example:

GOOGLE_CLIENT_SECRET=GOCSPX-abcdefghijklmnopqrstuvwxyz

Setting up Google OAuth:

  1. Go to Google Cloud Console
  2. Create a new project or select an existing one
  3. Enable the following APIs:
    • Google+ API (for OAuth login)
    • Gmail API (for email monitoring)
  4. Go to “Credentials” → “Create Credentials” → “OAuth 2.0 Client ID”
  5. Configure the OAuth consent screen
  6. Add authorized redirect URIs:
    • http://localhost:8080/api/auth/google/callback (for login)
    • http://localhost:8080/api/connections/gmail/callback (for Gmail)
  7. Copy the Client ID and Client Secret to your .env file

SMTP Email Configuration

Configure SMTP settings to enable the application to send emails (for the send_email reaction).

SMTP_HOST

Description: SMTP server hostname

Required: No

Default: smtp.gmail.com

Example:

SMTP_HOST=smtp.gmail.com

SMTP_PORT

Description: SMTP server port

Required: No

Default: 587

Common ports:

  • 587 - TLS (recommended)
  • 465 - SSL
  • 25 - Unencrypted (not recommended)

Example:

SMTP_PORT=587

SMTP_USERNAME

Description: SMTP authentication username (usually your email address)

Required: Yes (for email sending)

Default: None

Example:

SMTP_USERNAME=your-email@gmail.com

SMTP_PASSWORD

Description: SMTP authentication password

Required: Yes (for email sending)

Default: None

Security Note: For Gmail, use an App Password instead of your regular password.

Example:

SMTP_PASSWORD=your-app-password-here

SMTP_FROM_EMAIL

Description: Email address to use in the “From” field

Required: No

Default: Same as SMTP_USERNAME

Example:

SMTP_FROM_EMAIL=noreply@myapp.com

SMTP_USE_TLS

Description: Whether to use TLS encryption

Required: No

Default: true

Values: true or false

Example:

SMTP_USE_TLS=true

Gmail SMTP Setup:

  1. Enable 2-Factor Authentication on your Google account
  2. Go to App Passwords
  3. Generate a new app password for “Mail”
  4. Use this app password as SMTP_PASSWORD

Scheduler Configuration

The scheduler automatically checks and executes workflows at regular intervals.

SCHEDULER_ENABLED

Description: Enable or disable the background scheduler

Required: No

Default: true

Values: true or false

Example:

SCHEDULER_ENABLED=true

SCHEDULER_CHECK_INTERVAL_MINUTES

Description: How often (in minutes) the scheduler checks for workflows to execute

Required: No

Default: 1

Recommended: 1 for testing, 5 or more for production

Example:

SCHEDULER_CHECK_INTERVAL_MINUTES=1

SCHEDULER_TIMEZONE

Description: Timezone for the scheduler (affects time-based triggers)

Required: No

Default: Europe/Paris

Format: IANA timezone

Example:

# Paris time
SCHEDULER_TIMEZONE=Europe/Paris

# New York time
SCHEDULER_TIMEZONE=America/New_York

Flask Configuration

FLASK_APP

Description: Entry point for Flask application

Required: No (for development)

Default: app.py

Example:

FLASK_APP=app.py

FLASK_ENV

Description: Flask environment mode

Required: No

Default: development

Values:

  • development - Debug mode enabled, auto-reload on code changes
  • production - Debug mode disabled, optimized for performance

Example:

FLASK_ENV=development

Complete Example .env File

Here’s a complete example with all variables:

# Database Configuration
POSTGRES_DB=area_db
POSTGRES_USER=area_user
POSTGRES_PASSWORD=change_me

# Application Configuration
DATABASE_URL=postgresql://area_user:change_me@database:5432/area_db

# JWT Configuration (generate with: openssl rand -hex 32)
JWT_SECRET_KEY=change_me

# Flask Configuration
FLASK_APP=app.py
FLASK_ENV=development

# CORS Configuration
CORS_ORIGINS=*

# Google OAuth2 Configuration (get from Google Cloud Console)
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret

# Meta OAuth2 Configuration (get from Facebook Developer Console)
FACEBOOK_CLIENT_ID=your-facebook-app-id
FACEBOOK_CLIENT_SECRET=your-facebook-app-secret

# GitHub OAuth2 Configuration (get from GitHub Developer Settings)
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret

# Spotify OAuth2 Configuration (get from Spotify Developer Dashboard)
SPOTIFY_CLIENT_ID=your-spotify-client-id
SPOTIFY_CLIENT_SECRET=your-spotify-client-secret

# SMTP Email Configuration
SMTP_HOST=smtp-mail.outlook.com
SMTP_PORT=587
SMTP_USERNAME=your-email@outlook.com
SMTP_PASSWORD=generated-app-password # w/ google you can generate one after 2fa
SMTP_FROM_EMAIL=your-email@outlook.com
SMTP_USE_TLS=true

# Scheduler Configuration
SCHEDULER_ENABLED=true
SCHEDULER_CHECK_INTERVAL_MINUTES=1
SCHEDULER_TIMEZONE=Europe/Paris

Docker Environment

When using Docker Compose, environment variables are automatically loaded from the .env file in the project root. Make sure to create this file before running:

docker-compose up --build

SMTP Setup

Overview

AREA can send emails as a reaction in workflows using SMTP. This guide covers how to configure your SMTP server to enable the “Send Email” reaction.


Configuration

SMTP settings are configured via environment variables. See Configuration for full details.

Required Environment Variables

SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=your-email@gmail.com
SMTP_PASSWORD=your-app-password
SMTP_FROM_EMAIL=your-email@gmail.com
SMTP_USE_TLS=true

Common SMTP Providers

ProviderHostPortNotes
Gmailsmtp.gmail.com587Requires App Password
Outlooksmtp-mail.outlook.com587Use account password
Yahoosmtp.mail.yahoo.com587Requires App Password
SendGridsmtp.sendgrid.net587Use API key as password

Gmail SMTP Setup

Gmail requires an App Password (not your regular password):

  1. Enable 2-Factor Authentication on your Google account
  2. Go to App Passwords
  3. Select “Mail” and your device
  4. Click “Generate”
  5. Use the 16-character password as SMTP_PASSWORD
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USERNAME=yourname@gmail.com
SMTP_PASSWORD=xxxx xxxx xxxx xxxx  # App Password
SMTP_FROM_EMAIL=yourname@gmail.com
SMTP_USE_TLS=true

Reactions

Send Email

Name: send_email

Description: Sends an email to a specified recipient.

Configuration:

{
  "to": "recipient@example.com",
  "subject": "Email Subject",
  "body": "Email body content"
}

Parameters:

FieldTypeRequiredDescription
tostringYesRecipient email address
subjectstringYesEmail subject line (max 200 chars)
bodystringYesEmail body content (max 5000 chars)

Example Workflows

1. Daily Reminder Email

Send yourself a reminder every day at 9 AM:

{
  "name": "Daily Reminder",
  "action_id": 1,
  "reaction_id": 1,
  "action_config": {
    "time": "09:00"
  },
  "reaction_config": {
    "to": "me@example.com",
    "subject": "Daily Reminder",
    "body": "Don't forget to check your tasks!"
  }
}

2. GitHub Star Notification

Get emailed when someone stars your repository:

{
  "name": "Star Alert",
  "action_id": "<new_star_on_repo_id>",
  "reaction_id": "<send_email_id>",
  "action_config": {
    "repo_name": "username/my-project"
  },
  "reaction_config": {
    "to": "me@example.com",
    "subject": "New GitHub Star!",
    "body": "Someone starred your repository!"
  }
}

3. Gmail to Personal Email Forward

Forward emails from a specific sender to another address:

{
  "name": "Forward Boss Emails",
  "action_id": "<email_received_from_id>",
  "reaction_id": "<send_email_id>",
  "action_config": {
    "sender": "boss@company.com"
  },
  "reaction_config": {
    "to": "personal@email.com",
    "subject": "Work Email Alert",
    "body": "You received an email from your boss!"
  }
}

Troubleshooting

Email Not Sending

  1. Check SMTP credentials:

    # Verify environment variables are set
    docker-compose exec server env | grep SMTP
    
  2. Test SMTP connection:

    import smtplib
    server = smtplib.SMTP('smtp.gmail.com', 587)
    server.starttls()
    server.login('user@gmail.com', 'app-password')
    
  3. Check workflow logs:

    curl http://localhost:8080/api/areas/1/logs \
      -H "Authorization: Bearer <token>"
    

Common Errors

ErrorCauseSolution
Authentication failedWrong passwordUse App Password for Gmail
Connection refusedWrong host/portVerify SMTP settings
TLS requiredMissing TLS configSet SMTP_USE_TLS=true
Recipient rejectedInvalid emailCheck recipient address

Gmail-Specific Issues

  • “Less secure app” error: Gmail no longer supports this. Use App Passwords instead.
  • “Sign-in attempt blocked”: Enable 2FA and use App Password.
  • Daily limit reached: Gmail limits to ~500 emails/day for personal accounts.

Deployment & Server Configuration

Overview

The AREA backend runs on Gunicorn, a production-grade WSGI HTTP server for Python. This document covers server configuration, deployment options, and production considerations.


Gunicorn Configuration

The server uses Gunicorn with a custom configuration file (server/gunicorn.conf.py).

Configuration Options

# Bind to all interfaces on port 8080
bind = "0.0.0.0:8080"

# Worker configuration
workers = 1  # Single worker to avoid scheduler duplication
worker_class = "sync"
worker_connections = 1000

# Timeouts
timeout = 120  # Request timeout in seconds
keepalive = 5  # Keep-alive connections

# Logging
accesslog = "-"  # Log to stdout
errorlog = "-"   # Log to stderr
loglevel = "info"

# Process naming
proc_name = "area_server"

# Auto-reload in development
reload = True  # When FLASK_ENV=development

Key Settings

SettingValueDescription
bind0.0.0.0:8080Listen on all interfaces, port 8080
workers1Single worker (see note below)
timeout1202-minute request timeout for long operations
loglevelinfoConfigurable via LOG_LEVEL env var

Note on Workers: The server uses a single worker because the APScheduler runs inside the Flask app. Multiple workers would create duplicate schedulers, causing workflows to execute multiple times. For scaling, the scheduler should be moved to a separate container.


Environment Variables

Server Configuration

VariableDefaultDescription
GUNICORN_WORKERS1Number of worker processes
LOG_LEVELinfoLogging level (debug, info, warning, error)
FLASK_ENVproductionEnvironment mode (enables auto-reload in development)

Example

# Production
GUNICORN_WORKERS=1
LOG_LEVEL=warning
FLASK_ENV=production

# Development
GUNICORN_WORKERS=1
LOG_LEVEL=debug
FLASK_ENV=development

Docker Deployment

Server Dockerfile

FROM python:3.12-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*

# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8080

# Start Gunicorn with config file
CMD ["gunicorn", "--config", "gunicorn.conf.py", "app:app"]

Docker Compose

The complete stack is defined in docker-compose.yml:

services:
  database:
    image: postgres:15
    environment:
      POSTGRES_DB: area_db
      POSTGRES_USER: area_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data

  server:
    build: ./server
    ports:
      - "8080:8080"
    depends_on:
      - database
    environment:
      DATABASE_URL: postgresql://area_user:${POSTGRES_PASSWORD}@database:5432/area_db
      JWT_SECRET_KEY: ${JWT_SECRET_KEY}
      # ... other env vars

  client_web:
    build:
      context: ./client
      dockerfile: Dockerfile.web
    ports:
      - "8081:8081"

  client_mobile:
    build:
      context: ./client
      dockerfile: Dockerfile.mobile
    volumes:
      - mobile_build:/build

volumes:
  postgres_data:
  mobile_build:

Starting the Server

# Start all services
docker-compose up --build

# Start in background
docker-compose up -d --build

# View logs
docker-compose logs -f server

Manual Start

cd server

# Install dependencies
pip install -r requirements.txt

# Set environment variables
export DATABASE_URL=postgresql://user:pass@localhost:5432/area_db
export JWT_SECRET_KEY=your-secret-key

# Start with Gunicorn
gunicorn --config gunicorn.conf.py app:app

# Or with Flask development server
flask run --host=0.0.0.0 --port=8080

Health Checks

Health Endpoint

The server exposes a health check endpoint:

curl http://localhost:8080/health

Response:

{
  "status": "healthy",
  "database": "connected",
  "scheduler": "enabled",
  "services": {
    "scheduler_interval": "1 minutes",
    "active_count": 8
  }
}

Docker Health Check

Add to docker-compose.yml:

server:
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    interval: 30s
    timeout: 10s
    retries: 3

Logging

Log Format

Access logs follow the format:

%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"

Example:

172.20.0.1 - - [18/Jan/2026:10:30:00 +0000] "GET /api/auth/me HTTP/1.1" 200 234 "-" "Mozilla/5.0"

Log Levels

LevelDescription
debugDetailed debugging information
infoGeneral operational messages
warningWarning messages
errorError messages only
criticalCritical errors only

Set via environment variable:

LOG_LEVEL=debug

Architecture & Diagrams

This document provides visual representations of the AREA platform architecture.


Class Diagram - Database Models

classDiagram
    class User {
        +int id
        +string username
        +string email
        +string password_hash
        +string oauth_provider
        +string oauth_provider_id
        +string reset_token
        +datetime reset_token_expires
        +datetime created_at
        +datetime updated_at
    }

    class Service {
        +int id
        +string name
        +string display_name
        +string description
        +bool requires_oauth
        +string icon_url
        +bool is_active
    }

    class Action {
        +int id
        +int service_id
        +string name
        +string display_name
        +string description
        +json config_schema
    }

    class Reaction {
        +int id
        +int service_id
        +string name
        +string display_name
        +string description
        +json config_schema
    }

    class UserArea {
        +int id
        +int user_id
        +string name
        +int action_id
        +int reaction_id
        +json action_config
        +json reaction_config
        +bool is_active
        +datetime last_triggered
        +datetime created_at
        +datetime updated_at
    }

    class UserServiceConnection {
        +int id
        +int user_id
        +int service_id
        +string access_token
        +string refresh_token
        +datetime token_expires_at
        +datetime connected_at
        +datetime updated_at
    }

    class WorkflowLog {
        +int id
        +int area_id
        +string status
        +string message
        +datetime triggered_at
        +int execution_time_ms
    }

    User "1" --> "*" UserArea : owns
    User "1" --> "*" UserServiceConnection : has
    Service "1" --> "*" Action : provides
    Service "1" --> "*" Reaction : provides
    Service "1" --> "*" UserServiceConnection : connects
    Action "1" --> "*" UserArea : triggers
    Reaction "1" --> "*" UserArea : executes
    UserArea "1" --> "*" WorkflowLog : generates

Sequence Diagram - User Authentication

sequenceDiagram
    participant U as User
    participant C as Client
    participant S as Server
    participant DB as Database

    U->>C: Enter credentials
    C->>S: POST /api/auth/login
    S->>DB: Query user by email/username
    DB-->>S: User record
    S->>S: Verify password hash
    S->>S: Generate JWT token
    S-->>C: 200 OK + JWT token
    C->>C: Store token
    C-->>U: Login successful

Sequence Diagram - OAuth2 Service Connection

sequenceDiagram
    participant U as User
    participant C as Client
    participant S as Server
    participant P as OAuth Provider
    participant DB as Database

    U->>C: Click "Connect Gmail"
    C->>S: GET /api/connections/gmail?token=JWT
    S->>S: Validate JWT, store user_id in session
    S-->>C: 302 Redirect to Google
    C->>P: OAuth consent screen
    U->>P: Grant permission
    P-->>S: GET /api/connections/gmail/callback?code=XXX
    S->>P: Exchange code for tokens
    P-->>S: Access + Refresh tokens
    S->>DB: Store UserServiceConnection
    S-->>C: 200 OK - Connected
    C-->>U: Gmail connected!

Sequence Diagram - Workflow Execution

sequenceDiagram
    participant SC as Scheduler
    participant DB as Database
    participant A as Action Checker
    participant R as Reaction Executor
    participant API as External API

    loop Every minute
        SC->>DB: Get active workflows
        DB-->>SC: List of UserAreas

        loop For each workflow
            SC->>A: Check action condition
            A->>API: Query external service (if needed)
            API-->>A: Response data
            A-->>SC: triggered: true/false

            alt Action triggered
                SC->>R: Execute reaction
                R->>API: Call external API
                API-->>R: Success/Error
                R-->>SC: Execution result
                SC->>DB: Log workflow execution
                SC->>DB: Update last_triggered
            end
        end
    end

Component Diagram - System Architecture

flowchart TB
    subgraph Clients
        WEB[Web Client<br/>React :8081]
        MOB[Mobile Client<br/>Flutter APK]
    end

    subgraph Server
        API[Flask API<br/>:8080]
        SCHED[Scheduler<br/>APScheduler]
    end

    subgraph Database
        DB[(PostgreSQL)]
    end

    subgraph External Services
        GOOGLE[Google APIs<br/>Gmail, Drive]
        FB[Facebook API]
        GH[GitHub API]
        SP[Spotify API]
    end

    WEB <-->|REST API| API
    MOB <-->|REST API| API
    API <--> DB
    SCHED <--> DB
    SCHED --> GOOGLE
    SCHED --> FB
    SCHED --> GH
    SCHED --> SP
    API -->|OAuth2| GOOGLE
    API -->|OAuth2| FB
    API -->|OAuth2| GH
    API -->|OAuth2| SP

Docker Architecture

flowchart LR
    subgraph Docker Compose
        subgraph server [Server Container]
            FLASK[Flask App<br/>:8080]
        end

        subgraph web [Web Client Container]
            REACT[React App<br/>:8081]
        end

        subgraph mobile [Mobile Build Container]
            FLUTTER[Flutter Build]
        end

        subgraph db [Database Container]
            PG[(PostgreSQL<br/>:5432)]
        end

        VOL[(mobile_build<br/>Volume)]
    end

    FLASK <--> PG
    mobile --> VOL
    web --> VOL
    REACT -->|/client.apk| VOL

Web Client

The AREA web client is a React-based single-page application (SPA) that provides a user-friendly interface for managing automation workflows. It communicates with the Flask REST API backend to perform all operations.

Overview

The web client allows users to:

  • Authentication: Register, login, and manage their accounts
  • Service Management: Connect to third-party services via OAuth2 (Gmail, Drive, GitHub, Spotify, Facebook)
  • Workflow Creation: Create AREAs by linking Actions to REActions
  • Dashboard: Monitor active workflows and their execution status
  • Profile Settings: Manage account settings and connected services

Tech Stack

TechnologyPurpose
React 18UI framework
React Router DOM 6Client-side routing
Tailwind CSS 3Utility-first styling
Fetch APIHTTP requests
Context APIGlobal state management

Quick Start

Prerequisites

  • Node.js 18+
  • npm or yarn

Local Development

# Navigate to web client directory
cd client/web

# Install dependencies
npm install

# Start development server
npm start

The development server runs on http://localhost:8081.

Docker Deployment

# Build and run with Docker Compose
docker-compose up --build client_web

Project Structure

client/web/
├── public/
│   └── index.html
├── src/
│   ├── assets/              # Static assets (images, icons)
│   ├── components/          # Reusable UI components
│   │   ├── layout/          # Layout components (Layout, Sidebar, etc.)
│   │   ├── features/        # Feature-specific components
│   │   └── ui/              # Generic UI components (Input, Button, Alert)
│   ├── context/             # React Context providers
│   │   └── AuthContext.jsx  # Authentication state management
│   ├── hooks/               # Custom React hooks
│   │   ├── useServices.js   # Services data fetching hook
│   │   └── useWorkflows.js  # Workflows data fetching hook
│   ├── pages/               # Page components
│   ├── services/            # API service modules
│   │   ├── authService.js   # Authentication API calls
│   │   ├── servicesService.js
│   │   └── areasService.js
│   ├── utils/               # Utility functions
│   │   ├── constants.js     # App-wide constants (OAUTH_SERVICES, SERVICE_CATEGORIES)
│   │   ├── styles.js        # Reusable Tailwind class constants
│   │   └── validation.js    # Form validation helpers
│   ├── App.jsx              # Root component with routing
│   └── index.js             # Entry point
├── package.json
├── tailwind.config.js
└── Dockerfile

Environment Configuration

Create a .env file in client/web/:

# API Base URL
REACT_APP_API_URL=http://localhost:8080

# OAuth Callback URL (used for OAuth redirects)
REACT_APP_OAUTH_CALLBACK_URL=http://localhost:8081/oauth/callback

Design Principles

The web client follows these core principles:

  1. Responsive First: Mobile-friendly layouts using Tailwind CSS breakpoints
  2. User Feedback: Loading states, success/error messages for all operations
  3. Accessibility: Proper ARIA labels, keyboard navigation support
  4. Consistent UI: Dark theme with consistent color scheme
  5. Error Handling: Graceful API error handling with user-friendly messages

Color Scheme

The application uses a dark theme:

ElementColor
Background#000000 (black)
Card Background#0a0a0a (near-black)
Primary Text#ffffff (white)
Secondary Text#9ca3af (gray-400)
Accent/Primary#3b82f6 (blue-500)
Success#22c55e (green-500)
Error#ef4444 (red-500)
Borders#ffffff (white)

See also:

Web Client Architecture

This document describes the architectural patterns and design decisions used in the AREA web client.

Component Architecture

The web client follows a layered architecture with clear separation of concerns:

┌─────────────────────────────────────────────────────────────┐
│                       Pages Layer                           │
│  (LoginPage, DashboardPage, ServicesPage, WorkflowsPage)    │
├─────────────────────────────────────────────────────────────┤
│                     Components Layer                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │   Layout    │  │   Features  │  │        UI           │  │
│  │ Components  │  │ Components  │  │    Components       │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
├─────────────────────────────────────────────────────────────┤
│                       Hooks Layer                           │
│              (useServices, useAuth, etc.)                   │
├─────────────────────────────────────────────────────────────┤
│                     Services Layer                          │
│     (authService, servicesService, areasService)            │
├─────────────────────────────────────────────────────────────┤
│                     Context Layer                           │
│                    (AuthContext)                            │
└─────────────────────────────────────────────────────────────┘

State Management

Global State (Context API)

The application uses React Context for global state management:

AuthContext - Manages authentication state:

  • Current user information
  • JWT token
  • Login/logout functions
  • Authentication status
import { useAuth } from '../context/AuthContext';

const MyComponent = () => {
  const { user, token, login, logout, isAuthenticated } = useAuth();
};

Local State

Component-level state is managed with useState for:

  • Form data
  • Loading states
  • Error messages
  • UI state (modals, dropdowns)

Custom Hooks

Reusable stateful logic is extracted into custom hooks:

useServices - Fetches and caches services data:

import { useServices } from '../hooks/useServices';

const { services, connections, loading, error, refetch } = useServices();

Routing Architecture

Route Structure

/                     → Redirect to /login
/login                → LoginPage (public)
/register             → RegisterPage (public)
/dashboard            → DashboardPage (protected)
/services             → ServicesPage (protected)
/workflows            → WorkflowsPage (protected)
/workflows/create     → CreateWorkflowPage (protected)
/oauth/callback       → OAuthCallbackPage (handles OAuth redirects)

Route Protection

Routes are protected using wrapper components:

const PublicRoute = ({ children }) => {
  const { isAuthenticated } = useAuth();
  return !isAuthenticated() ? children : <Navigate to="/dashboard" replace />;
};

const ProtectedRoute = ({ children }) => {
  const { isAuthenticated } = useAuth();
  return isAuthenticated() ? children : <Navigate to="/login" replace />;
};

Data Flow

Authentication Flow

┌──────────┐    ┌──────────────┐    ┌──────────┐    ┌──────────────┐
│  User    │───>│  LoginPage   │───>│ authSvc  │───>│  Backend     │
│  Input   │    │  (form)      │    │ .login() │    │  /api/auth   │
└──────────┘    └──────────────┘    └──────────┘    └──────────────┘
                       │                                    │
                       │                                    │
                       v                                    v
              ┌──────────────┐                     ┌──────────────┐
              │ AuthContext  │<────────────────────│  JWT Token   │
              │   .login()   │                     │  + User Data │
              └──────────────┘                     └──────────────┘
                       │
                       v
              ┌──────────────┐
              │ localStorage │
              │ (persist)    │
              └──────────────┘

OAuth Flow (Frontend Perspective)

┌──────────┐    ┌──────────────┐    ┌──────────────────────────────┐
│  User    │───>│  Services    │───>│ window.location.href =       │
│  Click   │    │  Page        │    │ /api/connections/{service}   │
│ Connect  │    │              │    │ ?token={jwt}                 │
└──────────┘    └──────────────┘    └──────────────────────────────┘
                                                   │
                                                   v
                                    ┌──────────────────────────────┐
                                    │  Backend handles OAuth       │
                                    │  with service provider       │
                                    └──────────────────────────────┘
                                                   │
                                                   v
                                    ┌──────────────────────────────┐
                                    │  Redirect to /oauth/callback │
                                    │  ?success=true               │
                                    └──────────────────────────────┘
                                                   │
                                                   v
                                    ┌──────────────────────────────┐
                                    │  OAuthCallbackPage           │
                                    │  redirects to /services      │
                                    └──────────────────────────────┘

Workflow Creation Flow

Step 1: Select Action
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Select    │────>│   Select    │────>│  Configure  │
│   Service   │     │   Action    │     │   Action    │
└─────────────┘     └─────────────┘     └─────────────┘
                                               │
Step 2: Select Reaction                        v
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Select    │────>│   Select    │────>│  Configure  │
│   Service   │     │  Reaction   │     │  Reaction   │
└─────────────┘     └─────────────┘     └─────────────┘
                                               │
Step 3: Name & Submit                          v
┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   Enter     │────>│   Submit    │────>│  Redirect   │
│   Name      │     │   to API    │     │  /workflows │
└─────────────┘     └─────────────┘     └─────────────┘

API Integration Pattern

All API calls follow a consistent pattern:

import { getToken } from './authService';

const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080';

export const fetchData = async () => {
  const token = getToken();
  
  const response = await fetch(`${API_URL}/api/endpoint`, {
    method: 'GET',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    }
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || 'Request failed');
  }

  return await response.json();
};

Error Handling Strategy

API Errors

try {
  const data = await apiCall();
} catch (error) {
  setAlertMessage(error.message || 'An unexpected error occurred');
  setAlertType('error');
}

Form Validation Errors

const validateForm = () => {
  const newErrors = {};
  
  if (!formData.email) {
    newErrors.email = 'Email is required';
  } else if (!validateEmail(formData.email)) {
    newErrors.email = 'Invalid email format';
  }
  
  setErrors(newErrors);
  return Object.keys(newErrors).length === 0;
};

Performance Considerations

  1. Lazy Loading: Consider code-splitting for larger pages
  2. Memoization: Use useMemo and useCallback for expensive operations
  3. API Caching: The useServices hook caches data to avoid redundant calls
  4. Optimistic Updates: Consider implementing for better UX

Security Considerations

  1. JWT Storage: Tokens stored in localStorage (consider httpOnly cookies for production)
  2. Token Expiration: Handle 401 errors by redirecting to login
  3. Input Sanitization: All user input is validated before submission
  4. CORS: Backend configured to accept requests from web client origin

Web Client Components

This document describes the reusable components available in the AREA web client.

Component Categories

Components are organized into three categories:

CategoryLocationPurpose
UIcomponents/ui/Generic, reusable UI elements
Layoutcomponents/layout/Page structure components
Featurescomponents/features/Domain-specific components

UI Components

Input

A form input component with built-in error handling and consistent styling.

Location: src/components/Input.jsx

Props:

PropTypeDefaultDescription
labelstring-Input label text
namestringrequiredInput name attribute
typestring'text'Input type (text, email, password, etc.)
valuestring-Controlled input value
onChangefunctionrequiredChange handler
errorstring-Error message to display
placeholderstring-Placeholder text
disabledbooleanfalseDisable input

Usage:

import Input from '../components/Input';

<Input
  label="Email"
  name="email"
  type="email"
  value={formData.email}
  onChange={handleChange}
  error={errors.email}
  placeholder="Enter your email"
/>

Button

A button component with loading state support.

Location: src/components/Button.jsx

Props:

PropTypeDefaultDescription
childrennoderequiredButton content
typestring'button'Button type attribute
onClickfunction-Click handler
loadingbooleanfalseShow loading spinner
disabledbooleanfalseDisable button
variantstring'primary'Button style variant
classNamestring-Additional CSS classes

Variants:

  • primary - Blue background
  • secondary - Outline style
  • danger - Red background

Usage:

import Button from '../components/Button';

<Button
  type="submit"
  loading={isSubmitting}
  disabled={!isValid}
>
  Submit
</Button>

Alert

A notification component for displaying success/error messages.

Location: src/components/Alert.jsx

Props:

PropTypeDefaultDescription
messagestringrequiredAlert message
typestring'error'Alert type (success, error, warning, info)
onClosefunction-Close handler (shows X button if provided)

Usage:

import Alert from '../components/Alert';

{alertMessage && (
  <Alert
    message={alertMessage}
    type={alertType}
    onClose={() => setAlertMessage(null)}
  />
)}

A consistent header component for pages.

Location: src/components/ui/PageHeader.jsx

Props:

PropTypeDefaultDescription
titlestringrequiredPage title
subtitlestring-Optional subtitle
actionnode-Optional action button/element

Usage:

import PageHeader from '../components/ui/PageHeader';
import Button from '../components/Button';

<PageHeader
  title="My Workflows"
  subtitle="Manage your automation workflows"
  action={
    <Button onClick={() => navigate('/workflows/create')}>
      Create New
    </Button>
  }
/>

Layout Components

Layout

The main layout wrapper that includes the sidebar navigation.

Location: src/components/layout/Layout.jsx

Props:

PropTypeDefaultDescription
childrennoderequiredPage content

Usage:

import Layout from '../components/layout/Layout';

const MyPage = () => (
  <Layout>
    <div className="max-w-4xl mx-auto px-4 py-12">
    </div>
  </Layout>
);

The navigation sidebar component.

Location: src/components/layout/Sidebar.jsx

Navigation Items:

  • Dashboard (/dashboard)
  • Services (/services)
  • Workflows (/workflows)
  • Logout (action)

Feature Components

ConnectedServiceItem

Displays a connected service with disconnect option.

Location: src/components/features/ConnectedServiceItem.jsx

Props:

PropTypeDefaultDescription
serviceobjectrequiredService object
isDisconnectingbooleanfalseLoading state
onDisconnectfunctionrequiredDisconnect handler

ServiceCategory

Groups services by category with connect buttons.

Location: src/components/features/ServiceCategory.jsx

Props:

PropTypeDefaultDescription
titlestringrequiredCategory title
servicesarrayrequiredArray of services
isConnectingstring-Service currently connecting
onConnectfunctionrequiredConnect handler

StepIndicator

Visual progress indicator for multi-step forms.

Location: src/components/features/StepIndicator.jsx

Props:

PropTypeDefaultDescription
currentStepnumberrequiredCurrent step (1-3)
totalStepsnumber3Total number of steps

Usage:

<StepIndicator currentStep={2} />

Step1ActionSelection

Action selection step for workflow creation.

Location: src/components/features/Step1ActionSelection.jsx

Props:

PropTypeDescription
availableServicesarrayServices with actions
selectedServicestringCurrently selected service
onServiceChangefunctionService change handler
actionsarrayAvailable actions for service
selectedActionobjectCurrently selected action
onActionChangefunctionAction change handler
actionConfigobjectAction configuration
onConfigChangefunctionConfig change handler
onNextfunctionNext step handler
canProceedbooleanEnable next button

Step2ReactionSelection

Reaction selection step for workflow creation.

Location: src/components/features/Step2ReactionSelection.jsx

Props: Similar to Step1ActionSelection, but for reactions.


Step3NameAndSubmit

Final step for naming and submitting the workflow.

Location: src/components/features/Step3NameAndSubmit.jsx

Props:

PropTypeDescription
namestringWorkflow name
onNameChangefunctionName change handler
descriptionstringWorkflow description
onDescriptionChangefunctionDescription change handler
selectedActionobjectSelected action (summary display)
selectedReactionobjectSelected reaction (summary display)
onPrevfunctionPrevious step handler
onSubmitfunctionSubmit handler
submittingbooleanLoading state

Dynamic Form Generation

The workflow creation steps use dynamic form generation based on config_schema from the API.

Schema Structure

{
  "type": "object",
  "properties": {
    "time": {
      "type": "string",
      "pattern": "^([0-1][0-9]|2[0-3]):[0-5][0-9]$",
      "description": "Time in HH:MM format"
    },
    "recipient": {
      "type": "string",
      "description": "Email address"
    }
  },
  "required": ["time"]
}

Field Type Mapping

Schema TypeInput Type
stringtext
string with email formatemail
string with password formatpassword
numbernumber
booleancheckbox

Example Implementation

const renderConfigFields = (schema, config, onChange) => {
  if (!schema?.properties) return null;

  return Object.entries(schema.properties).map(([key, prop]) => (
    <Input
      key={key}
      label={prop.description || key}
      name={key}
      type={getInputType(prop)}
      value={config[key] || ''}
      onChange={(e) => onChange(key, e.target.value)}
      required={schema.required?.includes(key)}
      placeholder={prop.description}
    />
  ));
};

Creating New Components

When creating new components, follow these guidelines:

  1. Functional Components: Always use functional components with hooks
  2. Props Documentation: Define clear prop types
  3. Consistent Styling: Use Tailwind CSS utilities
  4. Error Handling: Handle loading and error states
  5. Accessibility: Include proper ARIA attributes

Component Template

import React from 'react';

const ComponentName = ({ propName, onClick }) => {
  return (
    <div className="component-styles">
    </div>
  );
};

export default ComponentName;

Web Client Pages

This document describes the pages (views) in the AREA web client application.

Page Overview

PageRouteAuthDescription
LoginPage/loginPublicUser authentication
RegisterPage/registerPublicNew user registration
DashboardPage/dashboardProtectedMain dashboard with overview
ServicesPage/servicesProtectedService connections management
WorkflowsPage/workflowsProtectedList and manage workflows
CreateWorkflowPage/workflows/createProtectedCreate new workflow
OAuthCallbackPage/oauth/callback-Handle OAuth redirects

Public Pages

LoginPage

Location: src/pages/LoginPage.jsx

Purpose: Authenticate existing users with username/email and password.

Features:

  • Form validation
  • Error handling for invalid credentials
  • OAuth login buttons (Google, Facebook)
  • Link to registration page
  • Redirect to dashboard on success

State Management:

const [formData, setFormData] = useState({
  identifier: '',
  password: '',
});
const [errors, setErrors] = useState({});
const [loading, setLoading] = useState(false);
const [alertMessage, setAlertMessage] = useState(null);
const [alertType, setAlertType] = useState('error');

API Integration:

import { login as loginService } from '../services/authService';

const handleSubmit = async (e) => {
  e.preventDefault();
  const response = await loginService(formData.identifier, formData.password);
  login(response.token, response.user);
  navigate('/dashboard');
};

RegisterPage

Location: src/pages/RegisterPage.jsx

Purpose: Create new user accounts.

Features:

  • Username, email, password fields
  • Password confirmation
  • Validation for all fields
  • Link to login page
  • Redirect to login on success

Validation Rules:

  • Username: 3-20 alphanumeric characters
  • Email: Valid email format
  • Password: Minimum 8 characters
  • Confirm Password: Must match password

State Management:

const [formData, setFormData] = useState({
  username: '',
  email: '',
  password: '',
  confirmPassword: '',
});

Protected Pages

DashboardPage

Location: src/pages/DashboardPage.jsx

Purpose: Main landing page after authentication showing overview.

Features:

  • Welcome message with user info
  • Quick stats (active workflows, connected services)
  • Recent workflow activity
  • Quick action buttons

Layout:

<Layout>
  <div className="max-w-4xl mx-auto px-4 py-12">
    <PageHeader title="Dashboard" subtitle="Welcome back!" />
    {/* Stats cards */}
    {/* Recent activity */}
    {/* Quick actions */}
  </div>
</Layout>

ServicesPage

Location: src/pages/ServicesPage.jsx

Purpose: Manage service connections (OAuth integrations).

Features:

  • List connected services with disconnect option
  • Browse available services by category
  • Connect to new services via OAuth
  • Visual feedback during connection process

Sections:

  1. Connected Services: Shows services user has authenticated with
  2. Available Services: Grouped by category (Google, Social, etc.)

Service Categories (from utils/constants.js):

export const SERVICE_CATEGORIES = [
  { key: 'google', title: 'Google', services: ['gmail', 'drive'] },
  { key: 'social', title: 'Social & Music', services: ['facebook', 'spotify'] },
  { key: 'developer', title: 'Developer', services: ['github'] },
  { key: 'utilities', title: 'Utilities', services: ['timer', 'email', 'system'] },
];

OAuth Connection:

import { connectService, disconnectService } from '../services/servicesService';

const handleConnect = (serviceName) => {
  setConnectingService(serviceName);
  connectService(serviceName);
};

const handleDisconnect = async (serviceName) => {
  await disconnectService(serviceName);
  await refetch();
};

WorkflowsPage

Location: src/pages/WorkflowsPage.jsx

Purpose: View and manage existing workflows (AREAs).

Features:

  • List all user workflows
  • Toggle workflow active/inactive status
  • Delete workflows
  • View workflow details
  • Create new workflow button

Workflow Card Display:

  • Workflow name
  • Action → Reaction summary
  • Active/Inactive status toggle
  • Last triggered timestamp
  • Delete action

Using the useWorkflows Hook:

import { useWorkflows } from '../hooks/useWorkflows';
import { deleteArea, toggleArea } from '../services/areasService';

const WorkflowsPage = () => {
  const { workflows, loading, error: fetchError, refetch } = useWorkflows();

  const handleToggle = async (workflowId) => {
    await toggleArea(workflowId);
    await refetch();
  };

  const handleDelete = async (workflowId) => {
    if (window.confirm('Are you sure you want to delete this workflow?')) {
      await deleteArea(workflowId);
      await refetch();
    }
  };
};

CreateWorkflowPage

Location: src/pages/CreateWorkflowPage.jsx

Purpose: Multi-step wizard for creating new workflows.

Step 1 - Select Action:

  • Choose service with actions
  • Select specific action
  • Configure action parameters

Step 2 - Select Reaction:

  • Choose service with reactions
  • Select specific reaction
  • Configure reaction parameters

Step 3 - Name & Submit:

  • Enter workflow name
  • Optional description
  • Review summary
  • Submit workflow

State Management:

const [currentStep, setCurrentStep] = useState(1);
const [selectedActionService, setSelectedActionService] = useState('');
const [selectedAction, setSelectedAction] = useState(null);
const [actionConfig, setActionConfig] = useState({});
const [selectedReactionService, setSelectedReactionService] = useState('');
const [selectedReaction, setSelectedReaction] = useState(null);
const [reactionConfig, setReactionConfig] = useState({});
const [workflowName, setWorkflowName] = useState('');
const [workflowDescription, setWorkflowDescription] = useState('');

Service Filtering:

const getAvailableServices = (withActions = false, withReactions = false) => {
  return services.filter(service => {
    const hasRequiredItems = withActions
      ? service.actions?.length > 0
      : withReactions
      ? service.reactions?.length > 0
      : true;
    return hasRequiredItems && isServiceAvailable(service.name);
  });
};

Submit Workflow:

import { createArea } from '../services/areasService';

const handleSubmit = async () => {
  const areaData = {
    name: workflowName,
    description: workflowDescription,
    action_id: selectedAction.id,
    reaction_id: selectedReaction.id,
    action_config: actionConfig,
    reaction_config: reactionConfig
  };
  await createArea(areaData);
  navigate('/workflows');
};

OAuthCallbackPage

Location: src/pages/OAuthCallbackPage.jsx

Purpose: Handle OAuth redirect callbacks from backend.

Flow:

  1. Backend completes OAuth with provider
  2. Redirects to /oauth/callback?token=xxx (success) or ?error=message (failure)
  3. Page reads query parameters
  4. If token present: stores in localStorage, redirects to /dashboard
  5. If error present: displays error, redirects to /login

Implementation:

const OAuthCallbackPage = () => {
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  const [message, setMessage] = useState('Processing...');

  useEffect(() => {
    const error = searchParams.get('error');
    const token = searchParams.get('token');

    if (error) {
      setMessage(`Failed: ${error}`);
      setTimeout(() => navigate('/login'), 3000);
    } else if (token) {
      setMessage('Login successful! Welcome.');
      localStorage.setItem('token', token);
      setTimeout(() => {
        window.location.href = '/dashboard';
      }, 1500);
    } else {
      setMessage('Redirecting...');
      setTimeout(() => navigate('/login'), 2000);
    }
  }, []);

  return (
    <div className="min-h-screen bg-black flex items-center justify-center">
      {/* Loading spinner and message */}
    </div>
  );
};

Page Layout Pattern

All protected pages follow a consistent layout pattern:

import React, { useState, useEffect } from 'react';
import Layout from '../components/layout/Layout';
import PageHeader from '../components/ui/PageHeader';

const PageName = () => {
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  const [data, setData] = useState([]);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      setLoading(true);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <Layout>
      <div className="max-w-4xl mx-auto px-4 py-12">
        <PageHeader
          title="Page Title"
          subtitle="Page description"
        />

        {loading && (
          <div className="text-center text-white">Loading...</div>
        )}

        {error && (
          <div className="bg-black border border-red-500 text-red-400 px-6 py-4 rounded-lg">
            {error}
          </div>
        )}

        {!loading && !error && (
          <div>
          </div>
        )}
      </div>
    </Layout>
  );
};

export default PageName;

Planned Pages

ProfilePage (To Be Implemented)

Route: /profile

Features:

  • Display user information (username, email)
  • List connected services
  • Change password functionality
  • Account deletion option
  • Notification preferences

ChangePasswordPage (To Be Implemented)

Route: /profile/change-password

Features:

  • Current password verification
  • New password with confirmation
  • Password strength indicator
  • Success/error feedback

WorkflowDetailsPage (To Be Implemented)

Route: /workflows/:id

Features:

  • Workflow configuration details
  • Execution history/logs
  • Edit workflow
  • Manual trigger option

Web Client API Integration

This document describes how the web client integrates with the Flask REST API backend.

API Configuration

Base URL

The API base URL is configured via environment variable:

const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:8080';

Request Headers

All authenticated requests include:

headers: {
  'Authorization': `Bearer ${token}`,
  'Content-Type': 'application/json'
}

Service Modules

authService.js

Location: src/services/authService.js

Handles all authentication-related API calls and token management.

Functions:

export const getToken = () => localStorage.getItem('token');
export const setToken = (token) => localStorage.setItem('token', token);
export const getStoredUser = () => JSON.parse(localStorage.getItem('user'));
export const setStoredUser = (user) => localStorage.setItem('user', JSON.stringify(user));
export const removeToken = () => localStorage.removeItem('token');
export const removeStoredUser = () => localStorage.removeItem('user');
export const logout = () => {
  removeToken();
  removeStoredUser();
};

export const login = async (usernameOrEmail, password) => {
  const body = usernameOrEmail.includes('@')
    ? { email: usernameOrEmail, password }
    : { username: usernameOrEmail, password };

  const response = await fetch(`${API_URL}/api/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body)
  });
};

export const register = async (username, email, password) => {
  const response = await fetch(`${API_URL}/api/auth/register`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, email, password })
  });
};

export const getCurrentUser = async () => {
  const token = getToken();
  const response = await fetch(`${API_URL}/api/auth/me`, {
    headers: { 'Authorization': `Bearer ${token}` }
  });
};

servicesService.js

Location: src/services/servicesService.js

Handles service listing and OAuth connections.

Helper Function:

const getAuthHeaders = () => {
  const token = localStorage.getItem('token');
  return {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  };
};

Functions:

export const getServices = async () => {
  const response = await fetch(`${API_URL}/api/services`, {
    method: 'GET',
    headers: getAuthHeaders()
  });
};

export const getUserConnections = async () => {
  const response = await fetch(`${API_URL}/api/connections`, {
    method: 'GET',
    headers: getAuthHeaders()
  });
};

export const connectService = (serviceName) => {
  const token = localStorage.getItem('token');
  if (!token) return;
  window.location.href = `${API_URL}/api/connections/${serviceName.toLowerCase()}?token=${encodeURIComponent(token)}`;
};

export const disconnectService = async (serviceName) => {
  const endpoint = serviceName.toLowerCase() === 'drive' ? 'gmail' : serviceName.toLowerCase();
  const response = await fetch(`${API_URL}/api/connections/${endpoint}`, {
    method: 'DELETE',
    headers: getAuthHeaders()
  });
};

areasService.js

Location: src/services/areasService.js

Handles AREA (workflow) CRUD operations.

Helper Function:

const getAuthHeaders = () => {
  const token = localStorage.getItem('token');
  return {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  };
};

Functions:

export const getAreas = async () => {
  const response = await fetch(`${API_URL}/api/areas`, {
    method: 'GET',
    headers: getAuthHeaders()
  });
};

export const createArea = async (areaData) => {
  const response = await fetch(`${API_URL}/api/areas`, {
    method: 'POST',
    headers: getAuthHeaders(),
    body: JSON.stringify(areaData)
  });
};

export const deleteArea = async (areaId) => {
  const response = await fetch(`${API_URL}/api/areas/${areaId}`, {
    method: 'DELETE',
    headers: getAuthHeaders()
  });
};

export const toggleArea = async (areaId) => {
  const response = await fetch(`${API_URL}/api/areas/${areaId}/toggle`, {
    method: 'PATCH',
    headers: getAuthHeaders()
  });
};

API Endpoints Reference

Authentication

MethodEndpointDescriptionAuth Required
POST/api/auth/registerCreate new accountNo
POST/api/auth/loginAuthenticate userNo
GET/api/auth/meGet current userYes

Services

MethodEndpointDescriptionAuth Required
GET/api/servicesList all servicesYes
GET/api/connectionsList user connectionsYes
GET/api/connections/{service}Initiate OAuthYes
DELETE/api/connections/{service}Disconnect serviceYes

AREAs (Workflows)

MethodEndpointDescriptionAuth Required
GET/api/areasList user AREAsYes
POST/api/areasCreate new AREAYes
GET/api/areas/{id}Get AREA detailsYes
PUT/api/areas/{id}Update AREAYes
DELETE/api/areas/{id}Delete AREAYes
PATCH/api/areas/{id}/toggleToggle active statusYes
GET/api/areas/{id}/logsGet execution logsYes

Public

MethodEndpointDescriptionAuth Required
GET/about.jsonServer infoNo

Error Handling Pattern

All service functions follow this error handling pattern:

export const apiFunction = async (params) => {
  const token = getToken();
  
  try {
    const response = await fetch(`${API_URL}/api/endpoint`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(params)
    });

    if (!response.ok) {
      const errorData = await response.json();
      throw new Error(errorData.error || 'Request failed');
    }

    return await response.json();
  } catch (error) {
    // Re-throw with meaningful message
    throw new Error(error.message || 'An unexpected error occurred');
  }
};

HTTP Status Code Handling

StatusMeaningFrontend Action
200SuccessProcess response
201CreatedProcess response
400Bad RequestShow validation error
401UnauthorizedRedirect to login
403ForbiddenShow permission error
404Not FoundShow not found message
500Server ErrorShow generic error

OAuth Integration

Connection Flow

  1. Frontend initiates: Call connectService(serviceName)
  2. Browser redirects: To ${API_URL}/api/connections/${service}?token=${jwt}
  3. Backend handles OAuth: Redirects to provider, handles callback
  4. Backend redirects back: To ${FRONTEND_URL}/oauth/callback?token=${jwt} (success) or ?error=message (failure)
  5. Frontend processes: OAuthCallbackPage stores token if present, shows result, redirects to /dashboard or /login

Supported OAuth Services

ServiceEndpointScopes
Gmail/api/connections/gmailemail, profile, gmail.readonly
Drive/api/connections/driveemail, profile, drive
GitHub/api/connections/githubuser, repo
Facebook/api/connections/facebookemail, public_profile
Spotify/api/connections/spotifyuser-read-email, user-library-read

Custom Hooks

useServices

Location: src/hooks/useServices.js

Provides cached access to services and connections data.

import { useServices } from '../hooks/useServices';

const MyComponent = () => {
  const { services, connections, loading, error, refetch } = useServices();
};

Implementation:

export const useServices = () => {
  const [services, setServices] = useState([]);
  const [connections, setConnections] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchData();
  }, []);

  const fetchData = async () => {
    try {
      setLoading(true);
      setError(null);
      const [servicesData, connectionsData] = await Promise.all([
        getServices(),
        getUserConnections()
      ]);
      setServices(servicesData.services || []);
      setConnections(connectionsData.connections || []);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return { services, connections, loading, error, refetch: fetchData };
};

useWorkflows

Location: src/hooks/useWorkflows.js

Provides cached access to user’s workflows (AREAs).

import { useWorkflows } from '../hooks/useWorkflows';

const MyComponent = () => {
  const { workflows, loading, error, refetch } = useWorkflows();
};

Implementation:

export const useWorkflows = () => {
  const [workflows, setWorkflows] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    fetchWorkflows();
  }, []);

  const fetchWorkflows = async () => {
    try {
      setLoading(true);
      setError(null);
      const data = await getAreas();
      setWorkflows(data.areas || []);
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  return { workflows, loading, error, refetch: fetchWorkflows };
};

Response Data Structures

Service Object

{
  "id": 1,
  "name": "gmail",
  "description": "Google Gmail",
  "icon": "gmail.png",
  "actions": [
    {
      "id": 1,
      "name": "new_email",
      "description": "When a new email is received",
      "config_schema": {
        "type": "object",
        "properties": {
          "from_filter": {
            "type": "string",
            "description": "Filter by sender email"
          }
        }
      }
    }
  ],
  "reactions": [
    {
      "id": 1,
      "name": "send_email",
      "description": "Send an email",
      "config_schema": {
        "type": "object",
        "properties": {
          "to": { "type": "string" },
          "subject": { "type": "string" },
          "body": { "type": "string" }
        },
        "required": ["to", "subject"]
      }
    }
  ]
}

AREA Object

{
  "id": 1,
  "name": "My Workflow",
  "description": "Workflow description",
  "is_active": true,
  "created_at": "2024-01-15T10:30:00Z",
  "updated_at": "2024-01-15T10:30:00Z",
  "action": {
    "id": 1,
    "name": "new_email",
    "service": "gmail"
  },
  "action_config": {
    "from_filter": "boss@company.com"
  },
  "reaction": {
    "id": 2,
    "name": "create_file",
    "service": "drive"
  },
  "reaction_config": {
    "folder": "Work Emails"
  }
}

Mobile Client (Android APK)

Overview

The AREA mobile client is a native Android application built with Flutter. It provides the same functionality as the web client, allowing users to manage workflows, connect services, and monitor automations on the go.

Tech Stack

  • Framework: Flutter 3.24.5
  • Language: Dart (SDK >=3.0.0 <4.0.0)
  • Platform: Android (min SDK 21)

Dependencies

PackageVersionPurpose
google_fonts^6.1.0Custom typography
http^1.1.0API communication
shared_preferences^2.2.2Local storage for auth tokens
webview_flutter^4.4.2OAuth authentication flows

Project Structure

client/apk/
├── lib/
│   ├── main.dart              # App entry point
│   ├── screens/               # UI screens
│   │   ├── admin_screen.dart
│   │   ├── create_workflow_screen.dart
│   │   ├── home_screen.dart
│   │   ├── login_screen.dart
│   │   ├── oauth_webview_screen.dart
│   │   ├── profile_screen.dart
│   │   ├── register_screen.dart
│   │   └── services_screen.dart
│   ├── services/              # Business logic
│   │   ├── api_service.dart   # REST API client
│   │   └── storage_service.dart
│   └── widgets/               # Reusable components
│       ├── universal_drawer.dart
│       └── universal_header.dart
├── assets/
│   └── area-icon.png          # App launcher icon
├── android/                   # Android native config
└── pubspec.yaml               # Dependencies

Screens

Login Screen

  • Email/username and password authentication
  • Google OAuth login via WebView
  • Facebook OAuth login via WebView
  • Link to registration

Register Screen

  • New user registration form
  • Password validation (8+ chars, uppercase, lowercase, special char)
  • Auto-login after registration

Home Screen/Dash

  • Dashboard with active workflows
  • Quick stats and recent activity
  • Navigation to other screens

Services Screen

  • List of available services (Gmail, Drive, Facebook, GitHub, Spotify)
  • OAuth connection status for each service
  • Connect/disconnect services via WebView

Create Workflow Screen

  • Action selection from connected services
  • Reaction selection
  • Configuration forms for action/reaction parameters
  • Workflow naming and activation

Profile Screen

  • User information display
  • Email update
  • Account deletion
  • Logout

Admin Screen

  • User management (admin only)
  • View all users and their workflows
  • System statistics

OAuth WebView Screen

  • Embedded browser for OAuth flows
  • Handles callback URLs and token extraction
  • Returns to app after successful authentication

API Integration

The mobile app communicates with the Flask backend at http://localhost:8080 (configurable).

Authentication Flow

  1. User enters credentials or initiates OAuth
  2. App receives JWT token
  3. Token stored in SharedPreferences
  4. Token included in all subsequent API requests

API Service

The api_service.dart handles all HTTP communication:

// Example API call structure
Future<Response> get(String endpoint) async {
  final token = await StorageService.getToken();
  return http.get(
    Uri.parse('$baseUrl$endpoint'),
    headers: {
      'Authorization': 'Bearer $token',
      'Content-Type': 'application/json',
    },
  );
}

Building the APK

The APK is automatically built when running Docker Compose:

docker-compose up --build

The APK will be available at:

  • Web download: http://localhost:8081/client.apk
  • Container path: /build/client.apk

Manual Build

Prerequisites:

  • Flutter SDK 3.24.5
  • Android SDK (API 34)
  • Java 17
cd client/apk

# Install dependencies
flutter pub get

# Generate launcher icons
flutter pub run flutter_launcher_icons

# Build release APK
flutter build apk --release

# APK location
ls build/app/outputs/flutter-apk/app-release.apk

Docker Build Configuration

The mobile build uses a dedicated Dockerfile (client/Dockerfile.mobile) with:

  • Base image: Ubuntu 22.04
  • Flutter version: 3.24.5 (pinned for stability)
  • Android SDK: API 34, Build Tools 34.0.0
  • Java: OpenJDK 17
  • Gradle memory: 4GB (-Xmx4096m)

Build Optimization

Gradle is configured for faster builds:

org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m -XX:+UseSerialGC
org.gradle.daemon=false
org.gradle.parallel=false

Note: If your machine has limited RAM, modify -Xmx4096m in client/Dockerfile.mobile.


APK Download

The APK can be downloaded from the web client:

  1. Navigate to http://localhost:8081
  2. Click the download button or navigate to /client.apk
  3. Install on Android device (enable “Unknown sources” in settings)

Direct Download

curl -O http://localhost:8081/client.apk

Configuration

API Base URL

The API base URL is configured in api_service.dart. For production, update this to your server’s public URL.

App Icon

The launcher icon is configured in pubspec.yaml:

flutter_launcher_icons:
  android: true
  ios: false
  image_path: "assets/area-icon.png"
  min_sdk_android: 21

To update the icon:

  1. Replace assets/area-icon.png with your icon (1024x1024 recommended)
  2. Run flutter pub run flutter_launcher_icons
  3. Rebuild the APK

Troubleshooting

APK Not Updating

If the APK shows old content after rebuilding:

# Clear all Docker caches and volumes
docker builder prune -f
docker-compose down -v
docker-compose build --no-cache
docker-compose up

OAuth Login Fails

  • Ensure the server is running and accessible
  • Check that OAuth redirect URIs include mobile callback URLs
  • Verify WebView has internet permission in AndroidManifest.xml

Build Fails

  • Ensure sufficient disk space (Flutter build requires ~10GB)
  • Check Gradle memory settings if build runs out of memory
  • Verify Android SDK licenses are accepted

API Routes - Authentication

This document describes authentication and user management endpoints.

Base URL

http://localhost:8080

Authentication Endpoints

POST /api/auth/register

Description: Register a new user account.

Authentication: Not required

Request Body:

{
  "username": "string",
  "email": "string",
  "password": "string"
}

Password Requirements:

  • Minimum 8 characters
  • Maximum 128 characters
  • Must contain uppercase letter
  • Must contain lowercase letter
  • Must contain special character

Success Response (201 Created):

{
  "message": "User registered successfully",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 1,
    "username": "alice",
    "email": "alice@example.com",
    "created_at": "2025-11-27T17:54:14.738713",
    "updated_at": "2025-11-27T17:54:14.738718"
  }
}

Response Fields:

FieldTypeDescription
messagestringSuccess message
tokenstringJWT authentication token (expires in 24 hours)
userobjectUser information
user.idintegerUser ID
user.usernamestringUsername
user.emailstringEmail address
user.created_atstringISO 8601 timestamp of account creation
user.updated_atstringISO 8601 timestamp of last update

Error Responses:

StatusErrorDescription
400Missing required fieldsusername, email, or password not provided
400Password must be at least 8 charactersPassword too short
400Password is too longPassword exceeds 128 characters
400Password requires a lowercase, uppercase and special characterPassword doesn’t meet complexity requirements
409Username already existsUsername is taken
409Email already existsEmail is already registered

Example:

curl -X POST http://localhost:8080/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "email": "alice@example.com",
    "password": "SecurePass123!"
  }'

POST /api/auth/login

Description: Login with username/email and password to receive a JWT token.

Authentication: Not required

Request Body:

{
  "username": "string",
  "password": "string"
}

OR

{
  "email": "string",
  "password": "string"
}

Success Response (200 OK):

{
  "message": "Login successful",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 1,
    "username": "alice",
    "email": "alice@example.com",
    "created_at": "2025-11-27T17:54:14.738713",
    "updated_at": "2025-11-27T17:54:14.738718"
  }
}

Response Fields:

FieldTypeDescription
messagestringSuccess message
tokenstringJWT authentication token (expires in 24 hours)
userobjectUser information
user.idintegerUser ID
user.usernamestringUsername
user.emailstringEmail address
user.created_atstringISO 8601 timestamp of account creation
user.updated_atstringISO 8601 timestamp of last update

Error Responses:

StatusErrorDescription
400Missing required fieldsPassword not provided
400Must provide username or emailNeither username nor email provided
401Invalid credentialsUsername/email or password incorrect
401This account uses OAuth loginAccount registered via OAuth, use social login

Example:

curl -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "password": "SecurePass123!"
  }'

GET /api/auth/me

Description: Get current authenticated user information.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Success Response (200 OK):

{
  "user": {
    "id": 1,
    "username": "alice",
    "email": "alice@example.com",
    "created_at": "2025-11-27T17:54:14.738713",
    "updated_at": "2025-11-27T17:54:14.738718"
  }
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid authorization header formatAuthorization header malformed
401Invalid or expired tokenToken is invalid or has expired
401User not foundUser associated with token doesn’t exist

Example:

curl -X GET http://localhost:8080/api/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

GET /api/auth/google/login

Description: Initiates Google OAuth2 authentication flow by redirecting to Google’s consent screen. This endpoint is used to sign in to AREA using a Google account.

Note: This is different from /api/connections/gmail which is used to connect Gmail as a service for workflow automation after you’ve already signed into AREA.

Authentication: Not required

Success Response (302 Redirect): Redirects user to Google OAuth consent screen where they can authorize the application.

OAuth Scopes Requested:

  • openid - Basic OpenID authentication
  • email - User’s email address
  • profile - User’s basic profile information (name, picture)
  • https://www.googleapis.com/auth/gmail.readonly - Read-only access to Gmail
  • https://www.googleapis.com/auth/drive - Access to Google Drive

Note: This endpoint requests Gmail and Drive scopes in addition to basic authentication scopes. This allows users to immediately use Gmail and Drive services in workflows without needing to reconnect through /api/connections/gmail.

Example:

# In browser, navigate to:
http://localhost:8080/api/auth/google/login

GET /api/auth/google/callback

Description: Handles the OAuth2 callback from Google after user authentication.

Authentication: Not required (callback from Google)

Query Parameters:

ParameterTypeRequiredDescription
codestringYesAuthorization code from Google
statestringYesCSRF protection token

Success Response (200 OK):

{
  "message": "Google login successful",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 1,
    "username": "john_doe",
    "email": "john@example.com",
    "oauth_provider": "google",
    "created_at": "2025-11-29T18:30:00Z"
  }
}

Response Fields:

FieldTypeDescription
messagestringSuccess message
tokenstringJWT authentication token (expires in 24 hours)
userobjectUser information
user.idintegerUser ID
user.usernamestringUsername (auto-generated from Google profile)
user.emailstringEmail address from Google account
user.oauth_providerstringOAuth provider name (“google”). Only present for OAuth-created accounts.
user.created_atstringISO 8601 timestamp of account creation

Note: Users created via OAuth login will have the oauth_provider field set. These accounts cannot use traditional password login and must use the OAuth flow.

Error Responses:

StatusErrorDescription
400Failed to get user info from GoogleOAuth token exchange failed
400Missing required user information from GoogleGoogle didn’t provide email or user ID
400OAuth authentication failed: <details>General OAuth error (network, invalid code, etc.)

Example:

# This endpoint is called automatically by Google after user consent
# You cannot call it directly - it requires valid OAuth state and code

PATCH /api/auth/me

Description: Update current authenticated user’s profile information.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body:

{
  "email": "string"
}

Request Fields:

FieldTypeRequiredDescription
emailstringNoNew email address

Success Response (200 OK):

{
  "message": "Profile updated successfully",
  "user": {
    "id": 1,
    "username": "alice",
    "email": "newemail@example.com",
    "created_at": "2025-11-27T17:54:14.738713",
    "updated_at": "2025-11-27T18:30:00.000000"
  }
}

Error Responses:

StatusErrorDescription
400No data providedRequest body is empty
400Email cannot be emptyEmail field is empty string
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
409Email already in useEmail is already taken by another user

Example:

curl -X PATCH http://localhost:8080/api/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newemail@example.com"
  }'

DELETE /api/auth/me

Description: Delete current user’s account and all associated data (workflows, service connections, logs).

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body: None required

Success Response (200 OK):

{
  "message": "Account deleted successfully"
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Admin account cannot be deletedCannot delete the admin account

Example:

curl -X DELETE http://localhost:8080/api/auth/me \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

GET /api/auth/facebook/login

Description: Initiates Facebook OAuth2 authentication flow by redirecting to Facebook’s consent screen. This endpoint is used to sign in to AREA using a Facebook account.

Note: This is different from /api/connections/facebook which is used to connect Facebook as a service for workflow automation after you’ve already signed into AREA.

Authentication: Not required

Success Response (302 Redirect): Redirects user to Facebook OAuth consent screen where they can authorize the application.

OAuth Scopes Requested:

  • email - User’s email address
  • public_profile - User’s basic profile information (name, picture)

Example:

# In browser, navigate to:
http://localhost:8080/api/auth/facebook/login

GET /api/auth/facebook/callback

Description: Handles the OAuth2 callback from Facebook after user authentication.

Authentication: Not required (callback from Facebook)

Query Parameters:

ParameterTypeRequiredDescription
codestringYesAuthorization code from Facebook
statestringYesCSRF protection token

Success Response (302 Redirect): Redirects to frontend with JWT token:

http://localhost:8081/oauth/callback?token=<jwt>&success=true&service=facebook

Response Fields:

FieldTypeDescription
tokenstringJWT authentication token (in URL parameter)
successstring“true” on successful authentication
servicestring“facebook”

Note: Users created via OAuth login will have the oauth_provider field set. These accounts cannot use traditional password login and must use the OAuth flow.

Error Response (302 Redirect): Redirects to frontend with error:

http://localhost:8081/oauth/callback?error=<error_message>

Example:

# This endpoint is called automatically by Facebook after user consent
# You cannot call it directly - it requires valid OAuth state and code

POST /api/auth/forgot-password

Description: Request a password reset email. For security, always returns success message regardless of whether the email exists.

Authentication: Not required

Request Body:

{
  "email": "string"
}

Success Response (200 OK):

{
  "message": "If an account with that email exists, a password reset link has been sent"
}

Error Responses:

StatusErrorDescription
400Email is requiredNo email provided in request
500Failed to send reset emailSMTP error occurred

Example:

curl -X POST http://localhost:8080/api/auth/forgot-password \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com"
  }'

POST /api/auth/reset-password

Description: Reset password using a token received via email.

Authentication: Not required

Request Body:

{
  "token": "string",
  "password": "string"
}

Request Fields:

FieldTypeRequiredDescription
tokenstringYesReset token from email
passwordstringYesNew password (min 8 chars, requires uppercase, lowercase, special char)

Success Response (200 OK):

{
  "message": "Password successfully reset. You can now log in with your new password."
}

Error Responses:

StatusErrorDescription
400Token and password are requiredMissing required fields
400Password must be at least 8 charactersPassword too short
400Password is too longPassword exceeds 128 characters
400Password requires a lowercase, uppercase and special characterPassword doesn’t meet complexity
400Invalid or expired reset tokenToken is invalid or has expired

Example:

curl -X POST http://localhost:8080/api/auth/reset-password \
  -H "Content-Type: application/json" \
  -d '{
    "token": "abc123def456",
    "password": "NewSecurePass123!"
  }'

API Routes - Services & Workflows

This document describes service discovery and workflow (AREA) management endpoints.

Base URL

http://localhost:8080

System Endpoints

GET /

Description: Root endpoint for the application server.

Authentication: Not required

Success Response (200 OK):

Nibba

Example:

curl http://localhost:8080/

GET /health

Description: Health check endpoint for monitoring and deployment. Returns the status of the server, database, and scheduler.

Authentication: Not required

Success Response (200 OK):

{
  "status": "healthy",
  "database": "connected",
  "scheduler": "enabled",
  "services": {
    "scheduler_interval": "1 minutes",
    "active_count": 8
  }
}

Response Fields:

FieldTypeDescription
statusstringOverall health status (“healthy” or “unhealthy”)
databasestringDatabase connection status (“connected” or error message)
schedulerstringScheduler status (“enabled”, “disabled”, or error message)
services.scheduler_intervalstringHow often the scheduler checks for workflow triggers
services.active_countintegerNumber of active services in the database

Error Response (503 Service Unavailable):

{
  "status": "unhealthy",
  "database": "error: connection refused",
  "scheduler": "enabled",
  "services": {}
}

Example:

curl http://localhost:8080/health

General Endpoints

GET /about.json

Description: Returns information about the server, client, and all available services with their actions and reactions.

Authentication: Not required

Success Response (200 OK):

{
  "client": {
    "host": "string"
  },
  "server": {
    "current_time": 1234567890,
    "services": [
      {
        "name": "string",
        "display_name": "string",
        "description": "string",
        "requires_oauth": boolean,
        "icon_url": "string | null",
        "actions": [
          {
            "name": "string",
            "description": "string"
          }
        ],
        "reactions": [
          {
            "name": "string",
            "description": "string"
          }
        ]
      }
    ]
  }
}

Response Fields:

FieldTypeDescription
client.hoststringIP address of the client making the request
server.current_timeintegerCurrent server time in Unix epoch timestamp format
server.servicesarrayList of all active services available on the server
server.services[].namestringInternal identifier for the service (e.g., “timer”, “gmail”)
server.services[].display_namestringHuman-readable service name (e.g., “Timer”, “Gmail”)
server.services[].descriptionstringBrief description of what the service does
server.services[].requires_oauthbooleanWhether the service requires OAuth2 authentication
server.services[].icon_urlstring/nullURL to the service icon image
server.services[].actionsarrayList of action triggers available for this service
server.services[].actions[].namestringInternal identifier for the action
server.services[].actions[].descriptionstringDescription of when this action triggers
server.services[].reactionsarrayList of reactions available for this service
server.services[].reactions[].namestringInternal identifier for the reaction
server.services[].reactions[].descriptionstringDescription of what this reaction does

Example Response:

{
  "client": {
    "host": "172.20.0.1"
  },
  "server": {
    "current_time": 1763556518,
    "services": [
      {
        "name": "timer",
        "display_name": "Timer",
        "description": "Time-based triggers and actions",
        "requires_oauth": false,
        "icon_url": null,
        "actions": [
          {
            "name": "time_matches",
            "description": "Triggers when current time matches HH:MM pattern"
          },
          {
            "name": "interval_elapsed",
            "description": "Triggers every X minutes"
          }
        ],
        "reactions": []
      }
    ]
  }
}

Example:

curl http://localhost:8080/about.json

Services Endpoints

GET /api/services

Description: List all available services with their actions and reactions.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Success Response (200 OK):

{
  "services": [
    {
      "name": "string",
      "display_name": "string",
      "description": "string",
      "requires_oauth": boolean,
      "icon_url": "string | null",
      "actions": [
        {
          "id": integer,
          "name": "string",
          "display_name": "string",
          "description": "string",
          "config_schema": {
            "type": "object",
            "properties": {}
          }
        }
      ],
      "reactions": [
        {
          "id": integer,
          "name": "string",
          "display_name": "string",
          "description": "string",
          "config_schema": {
            "type": "object",
            "properties": {}
          }
        }
      ]
    }
  ]
}

Response Fields:

FieldTypeDescription
servicesarrayList of all active services
services[].namestringInternal identifier for the service
services[].display_namestringHuman-readable service name
services[].descriptionstringBrief description of the service
services[].requires_oauthbooleanWhether OAuth2 authentication is required
services[].icon_urlstring/nullURL to service icon
services[].actionsarrayAvailable action triggers for this service
services[].actions[].idintegerDatabase ID of the action
services[].actions[].namestringInternal action identifier
services[].actions[].display_namestringHuman-readable action name
services[].actions[].descriptionstringDescription of when this action triggers
services[].actions[].config_schemaobjectJSON schema for action configuration
services[].reactionsarrayAvailable reactions for this service
services[].reactions[].idintegerDatabase ID of the reaction
services[].reactions[].namestringInternal reaction identifier
services[].reactions[].display_namestringHuman-readable reaction name
services[].reactions[].descriptionstringDescription of what this reaction does
services[].reactions[].config_schemaobjectJSON schema for reaction configuration

Example Response:

{
  "services": [
    {
      "name": "timer",
      "display_name": "Timer",
      "description": "Time-based triggers and actions",
      "requires_oauth": false,
      "icon_url": null,
      "actions": [
        {
          "id": 1,
          "name": "time_matches",
          "display_name": "Time Matches",
          "description": "Triggers when current time matches HH:MM pattern",
          "config_schema": {
            "type": "object",
            "properties": {
              "time": {
                "type": "string",
                "pattern": "^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$",
                "description": "Time in HH:MM format (e.g., 14:30)"
              }
            },
            "required": ["time"]
          }
        }
      ],
      "reactions": []
    },
    {
      "name": "gmail",
      "display_name": "Gmail",
      "description": "Email detection and monitoring",
      "requires_oauth": true,
      "icon_url": null,
      "actions": [
        {
          "id": 3,
          "name": "email_received_from",
          "display_name": "Email Received From",
          "description": "Triggers when an email is received from a specific sender",
          "config_schema": {
            "type": "object",
            "properties": {
              "sender": {
                "type": "string",
                "description": "Email address of the sender"
              }
            },
            "required": ["sender"]
          }
        }
      ],
      "reactions": []
    }
  ]
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired

Example:

curl -X GET http://localhost:8080/api/services \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

GET /api/connections

Description: List all available services that require OAuth and their connection status for the current user.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Success Response (200 OK):

{
  "connections": [
    {
      "service_id": 2,
      "service_name": "gmail",
      "display_name": "Gmail",
      "description": "Email detection and monitoring",
      "is_connected": true,
      "connected_at": "2025-12-15T10:00:00Z"
    },
    {
      "service_id": 3,
      "service_name": "google_drive",
      "display_name": "Google Drive",
      "description": "Cloud storage integration",
      "is_connected": false,
      "connected_at": null
    }
  ]
}

Response Fields:

FieldTypeDescription
connectionsarrayList of all OAuth-enabled services with connection status
connections[].service_idintegerDatabase ID of the service
connections[].service_namestringInternal service identifier
connections[].display_namestringHuman-readable service name
connections[].descriptionstringBrief service description
connections[].is_connectedbooleanWhether user has connected this service
connections[].connected_atstring/nullISO 8601 timestamp of when service was connected, null if not connected

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired

Example:

curl -X GET http://localhost:8080/api/connections \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

AREA (Workflow) Endpoints

POST /api/areas

Description: Create a new workflow (AREA) that links an Action to a REAction.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body:

{
  "name": "string",
  "action_id": integer,
  "reaction_id": integer,
  "action_config": {
    "key": "value"
  },
  "reaction_config": {
    "key": "value"
  },
  "is_active": boolean
}

Request Fields:

FieldTypeRequiredDescription
namestringYesName of the workflow
action_idintegerYesID of the action trigger
reaction_idintegerYesID of the reaction to execute
action_configobjectYesConfiguration for the action (varies by action type)
reaction_configobjectYesConfiguration for the reaction (varies by reaction type)
is_activebooleanNoWhether the workflow is active (default: true)

Success Response (201 Created):

{
  "message": "Workflow created successfully",
  "area": {
    "id": 1,
    "user_id": 1,
    "name": "Daily Email Reminder",
    "description": null,
    "action": {
      "id": 1,
      "name": "time_matches",
      "display_name": "Time Matches",
      "service": "Timer"
    },
    "reaction": {
      "id": 1,
      "name": "send_email",
      "display_name": "Send Email",
      "service": "Email"
    },
    "action_config": {
      "time": "14:00"
    },
    "reaction_config": {
      "to": "user@example.com",
      "subject": "Daily Reminder",
      "body": "This is your daily reminder!"
    },
    "is_active": true,
    "last_triggered": null,
    "created_at": "2025-12-15T10:30:00Z",
    "updated_at": "2025-12-15T10:30:00Z"
  }
}

Error Responses:

StatusErrorDescription
400Missing required field: <field>Required field not provided
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
404Action not foundaction_id doesn’t exist
404Reaction not foundreaction_id doesn’t exist

Example:

curl -X POST http://localhost:8080/api/areas \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Daily Email Reminder",
    "action_id": 1,
    "reaction_id": 1,
    "action_config": {
      "time": "14:00"
    },
    "reaction_config": {
      "to": "user@example.com",
      "subject": "Daily Reminder",
      "body": "This is your daily reminder!"
    },
    "is_active": true
  }'

GET /api/areas

Description: List all workflows for the current authenticated user with pagination and filtering options.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Query Parameters:

ParameterTypeRequiredDefaultDescription
pageintegerNo1Page number for pagination
per_pageintegerNo20Number of items per page
is_activestringNo-Filter by active status (“true” or “false”)

Success Response (200 OK):

{
  "areas": [
    {
      "id": 1,
      "user_id": 1,
      "name": "Daily Email Reminder",
      "description": null,
      "action": {
        "id": 1,
        "name": "time_matches",
        "display_name": "Time Matches",
        "service": "Timer"
      },
      "reaction": {
        "id": 1,
        "name": "send_email",
        "display_name": "Send Email",
        "service": "Email"
      },
      "action_config": {
        "time": "14:00"
      },
      "reaction_config": {
        "to": "user@example.com",
        "subject": "Daily Reminder",
        "body": "This is your daily reminder!"
      },
      "is_active": true,
      "last_triggered": "2025-12-15T14:00:05Z",
      "created_at": "2025-12-15T10:30:00Z",
      "updated_at": "2025-12-15T10:30:00Z"
    }
  ],
  "total": 1,
  "page": 1,
  "per_page": 20,
  "total_pages": 1
}

Response Fields:

FieldTypeDescription
areasarrayList of workflows for the current page
totalintegerTotal number of workflows
pageintegerCurrent page number
per_pageintegerNumber of items per page
total_pagesintegerTotal number of pages

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired

Example:

# Get first page with default pagination
curl -X GET http://localhost:8080/api/areas \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Get active workflows only
curl -X GET "http://localhost:8080/api/areas?is_active=true" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Get page 2 with 10 items per page
curl -X GET "http://localhost:8080/api/areas?page=2&per_page=10" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

GET /api/areas/<area_id>

Description: Get a specific workflow by its ID.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

URL Parameters:

ParameterTypeRequiredDescription
area_idintegerYesID of the workflow to retrieve

Success Response (200 OK):

{
  "id": 1,
  "user_id": 1,
  "name": "Daily Email Reminder",
  "description": null,
  "action": {
    "id": 1,
    "name": "time_matches",
    "display_name": "Time Matches",
    "service": "Timer"
  },
  "reaction": {
    "id": 1,
    "name": "send_email",
    "display_name": "Send Email",
    "service": "Email"
  },
  "action_config": {
    "time": "14:00"
  },
  "reaction_config": {
    "to": "user@example.com",
    "subject": "Daily Reminder",
    "body": "This is your daily reminder!"
  },
  "is_active": true,
  "last_triggered": "2025-12-15T14:00:05Z",
  "created_at": "2025-12-15T10:30:00Z",
  "updated_at": "2025-12-15T10:30:00Z"
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Unauthorized access to this workflowUser doesn’t own this workflow
404Workflow not foundWorkflow with given ID doesn’t exist

Example:

curl -X GET http://localhost:8080/api/areas/1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

PUT /api/areas/<area_id>

Description: Update an existing workflow. Only the workflow owner can update it.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

URL Parameters:

ParameterTypeRequiredDescription
area_idintegerYesID of the workflow to update

Request Body:

{
  "name": "string",
  "action_config": {
    "key": "value"
  },
  "reaction_config": {
    "key": "value"
  },
  "is_active": boolean
}

Request Fields:

FieldTypeRequiredDescription
namestringNoNew name for the workflow
action_configobjectNoNew configuration for the action
reaction_configobjectNoNew configuration for the reaction
is_activebooleanNoWhether the workflow is active

Success Response (200 OK):

{
  "message": "Workflow updated successfully",
  "area": {
    "id": 1,
    "user_id": 1,
    "name": "Updated Email Reminder",
    "description": null,
    "action": {
      "id": 1,
      "name": "time_matches",
      "display_name": "Time Matches",
      "service": "Timer"
    },
    "reaction": {
      "id": 1,
      "name": "send_email",
      "display_name": "Send Email",
      "service": "Email"
    },
    "action_config": {
      "time": "15:00"
    },
    "reaction_config": {
      "to": "newemail@example.com",
      "subject": "Updated Reminder",
      "body": "This is an updated reminder!"
    },
    "is_active": true,
    "last_triggered": "2025-12-15T14:00:05Z",
    "created_at": "2025-12-15T10:30:00Z",
    "updated_at": "2025-12-15T15:45:00Z"
  }
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Unauthorized access to this workflowUser doesn’t own this workflow
404Workflow not foundWorkflow with given ID doesn’t exist

Example:

curl -X PUT http://localhost:8080/api/areas/1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Email Reminder",
    "action_config": {
      "time": "15:00"
    },
    "reaction_config": {
      "to": "newemail@example.com",
      "subject": "Updated Reminder",
      "body": "This is an updated reminder!"
    }
  }'

PATCH /api/areas/<area_id>

Description: Partial update a workflow. Only updates the fields provided in the request body. Admins can update any workflow.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

URL Parameters:

ParameterTypeRequiredDescription
area_idintegerYesID of the workflow to update

Request Body:

{
  "name": "string",
  "is_active": boolean,
  "action_config": {
    "key": "value"
  },
  "reaction_config": {
    "key": "value"
  }
}

Request Fields:

FieldTypeRequiredDescription
namestringNoNew name for the workflow
is_activebooleanNoWhether the workflow is active
action_configobjectNoNew configuration for the action
reaction_configobjectNoNew configuration for the reaction

Success Response (200 OK):

{
  "message": "Workflow updated successfully",
  "area": {
    "id": 1,
    "user_id": 1,
    "name": "Updated Workflow Name",
    "action": {
      "id": 1,
      "name": "time_matches",
      "display_name": "Time Matches",
      "service": "Timer"
    },
    "reaction": {
      "id": 1,
      "name": "send_email",
      "display_name": "Send Email",
      "service": "Email"
    },
    "action_config": {
      "time": "14:00"
    },
    "reaction_config": {
      "to": "user@example.com",
      "subject": "Reminder",
      "body": "This is a reminder!"
    },
    "is_active": true,
    "last_triggered": null,
    "created_at": "2025-12-15T10:30:00Z",
    "updated_at": "2025-12-15T15:45:00Z"
  }
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Unauthorized access to this workflowUser doesn’t own this workflow (and is not admin)
404Workflow not foundWorkflow with given ID doesn’t exist

Example:

curl -X PATCH http://localhost:8080/api/areas/1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Updated Workflow Name",
    "is_active": false
  }'

PATCH /api/areas/<area_id>/toggle

Description: Toggle the active status of a workflow (enable/disable). Only the workflow owner can toggle it.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

URL Parameters:

ParameterTypeRequiredDescription
area_idintegerYesID of the workflow to toggle

Request Body: None required

Success Response (200 OK):

{
  "message": "Workflow enabled successfully",
  "area": {
    "id": 1,
    "user_id": 1,
    "name": "Daily Email Reminder",
    "description": null,
    "action": {
      "id": 1,
      "name": "time_matches",
      "display_name": "Time Matches",
      "service": "Timer"
    },
    "reaction": {
      "id": 1,
      "name": "send_email",
      "display_name": "Send Email",
      "service": "Email"
    },
    "action_config": {
      "time": "14:00"
    },
    "reaction_config": {
      "to": "user@example.com",
      "subject": "Daily Reminder",
      "body": "This is your daily reminder!"
    },
    "is_active": true,
    "last_triggered": "2025-12-15T14:00:05Z",
    "created_at": "2025-12-15T10:30:00Z",
    "updated_at": "2025-12-15T16:00:00Z"
  }
}

Response Fields:

FieldTypeDescription
messagestringSuccess message indicating if workflow was enabled or disabled
areaobjectComplete updated workflow object with toggled is_active status

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Unauthorized access to this workflowUser doesn’t own this workflow
404Workflow not foundWorkflow with given ID doesn’t exist

Example:

curl -X PATCH http://localhost:8080/api/areas/1/toggle \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

DELETE /api/areas/<area_id>

Description: Delete a workflow permanently. Only the workflow owner can delete it.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

URL Parameters:

ParameterTypeRequiredDescription
area_idintegerYesID of the workflow to delete

Request Body: None required

Success Response (200 OK):

{
  "message": "Workflow deleted successfully"
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Unauthorized access to this workflowUser doesn’t own this workflow
404Workflow not foundWorkflow with given ID doesn’t exist

Example:

curl -X DELETE http://localhost:8080/api/areas/1 \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

DELETE /api/areas/reset

Description: Delete all workflows for the current user. This also deletes all associated workflow logs.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body: None required

Success Response (200 OK):

{
  "message": "Successfully deleted 5 workflow(s)",
  "deleted_count": 5
}

Response Fields:

FieldTypeDescription
messagestringSuccess message with count of deleted workflows
deleted_countintegerNumber of workflows that were deleted

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired

Example:

curl -X DELETE http://localhost:8080/api/areas/reset \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

GET /api/areas/<area_id>/logs

Description: Get execution logs for a specific workflow with pagination. Shows history of when the workflow was triggered and execution results.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

URL Parameters:

ParameterTypeRequiredDescription
area_idintegerYesID of the workflow to get logs for

Query Parameters:

ParameterTypeRequiredDefaultDescription
pageintegerNo1Page number for pagination
per_pageintegerNo50Number of log entries per page

Success Response (200 OK):

{
  "logs": [
    {
      "id": 1,
      "area_id": 1,
      "area_name": "Daily Email Reminder",
      "status": "success",
      "message": "Email sent successfully",
      "triggered_at": "2025-12-15T14:00:05Z",
      "execution_time_ms": 245
    },
    {
      "id": 2,
      "area_id": 1,
      "area_name": "Daily Email Reminder",
      "status": "failed",
      "message": "SMTP connection failed",
      "triggered_at": "2025-12-14T14:00:03Z",
      "execution_time_ms": 1520
    }
  ],
  "total": 2,
  "page": 1,
  "per_page": 50,
  "total_pages": 1
}

Response Fields:

FieldTypeDescription
logsarrayList of execution logs for the current page
logs[].idintegerLog entry ID
logs[].area_idintegerWorkflow ID
logs[].area_namestringName of the workflow
logs[].statusstringExecution status: “success”, “failed”, or “error”
logs[].messagestringExecution message or error description
logs[].triggered_atstringISO 8601 timestamp of when workflow was triggered
logs[].execution_time_msintegerExecution time in milliseconds
totalintegerTotal number of log entries
pageintegerCurrent page number
per_pageintegerNumber of items per page
total_pagesintegerTotal number of pages

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Unauthorized access to this workflowUser doesn’t own this workflow
404Workflow not foundWorkflow with given ID doesn’t exist

Example:

# Get first page with default pagination
curl -X GET http://localhost:8080/api/areas/1/logs \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Get page 2 with 20 logs per page
curl -X GET "http://localhost:8080/api/areas/1/logs?page=2&per_page=20" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Admin Endpoints

GET /api/admin/users

Description: Get all users with their workflows. Only accessible by the admin user (user ID 1).

Authentication: Required (Bearer token - Admin only)

Request Headers:

Authorization: Bearer <token>

Success Response (200 OK):

{
  "users": [
    {
      "id": 1,
      "username": "admin",
      "email": "admin@example.com",
      "oauth_provider": null,
      "created_at": "2025-11-27T17:54:14.738713",
      "workflow_count": 3,
      "workflows": [
        {
          "id": 1,
          "name": "Daily Reminder",
          "action": {
            "id": 1,
            "name": "time_matches",
            "display_name": "Time Matches",
            "service": "Timer"
          },
          "reaction": {
            "id": 1,
            "name": "send_email",
            "display_name": "Send Email",
            "service": "Email"
          },
          "is_active": true,
          "last_triggered": "2025-12-15T14:00:05Z",
          "created_at": "2025-12-15T10:30:00Z"
        }
      ]
    },
    {
      "id": 2,
      "username": "alice",
      "email": "alice@example.com",
      "oauth_provider": "google",
      "created_at": "2025-12-01T09:00:00.000000",
      "workflow_count": 2,
      "workflows": []
    }
  ]
}

Response Fields:

FieldTypeDescription
usersarrayList of all users in the system
users[].idintegerUser database ID
users[].usernamestringUser’s username
users[].emailstringUser’s email address
users[].oauth_providerstring/nullOAuth provider if user registered via OAuth (“google”, “facebook”, or null)
users[].created_atstringISO 8601 timestamp of account creation
users[].workflow_countintegerNumber of workflows this user has
users[].workflowsarrayList of user’s workflows with full details

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
403Admin access requiredUser is not the admin

Example:

curl -X GET http://localhost:8080/api/admin/users \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

API Routes - OAuth Service Connections

This document describes OAuth connection endpoints for integrating external services (Gmail, Drive, Facebook, GitHub, Spotify).

Base URL

http://localhost:8080

General Endpoints

GET /api/connections

Description: List all services and their connection status for the current user.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Success Response (200 OK):

{
  "connections": [
    {
      "service_id": 2,
      "service_name": "gmail",
      "display_name": "Gmail",
      "description": "Email automation service",
      "is_connected": true,
      "connected_at": "2025-12-15T10:00:00Z"
    },
    {
      "service_id": 3,
      "service_name": "facebook",
      "display_name": "Facebook",
      "description": "Social media automation",
      "is_connected": false,
      "connected_at": null
    }
  ]
}

Response Fields:

FieldTypeDescription
connectionsarrayList of all OAuth-enabled services
service_idintegerService database ID
service_namestringInternal service name
display_namestringDisplay name of the service
descriptionstringService description
is_connectedbooleanWhether user has connected this service
connected_atstringISO 8601 timestamp of connection (null if not connected)

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired

Example:

curl -X GET http://localhost:8080/api/connections \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

DELETE /api/connections/reset

Description: Disconnect all OAuth services for the current user. This removes all stored access tokens and service connections.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body: None required

Success Response (200 OK):

{
  "message": "Successfully disconnected 4 service(s)",
  "disconnected_count": 4
}

Response Fields:

FieldTypeDescription
messagestringSuccess message with count of disconnected services
disconnected_countintegerNumber of services that were disconnected

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired

Example:

curl -X DELETE http://localhost:8080/api/connections/reset \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Gmail OAuth Connections

GET/POST /api/connections/gmail

Description: Initiate the Gmail OAuth2 authentication flow. Redirects the user to Google’s consent screen to authorize Gmail access.

Authentication: Required (Bearer token or URL parameter)

Request Headers:

Authorization: Bearer <token>

OR Query Parameters:

ParameterTypeRequiredDescription
tokenstringYes (if no header)JWT authentication token

Success Response (302 Redirect): Redirects user to Google OAuth consent screen where they can authorize Gmail access.

OAuth Scopes Requested:

  • openid - Basic OpenID authentication
  • email - User’s email address
  • profile - User’s basic profile information
  • https://www.googleapis.com/auth/gmail.readonly - Read-only access to Gmail
  • https://www.googleapis.com/auth/drive - Access to Google Drive

Error Responses:

StatusErrorDescription
401Authentication requiredNo token provided in header or URL
401Invalid tokenToken is malformed or invalid
401Invalid token payloadToken doesn’t contain user_id

Example:

# Using Authorization header
curl -X GET http://localhost:8080/api/connections/gmail \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Using URL parameter (useful for browser redirects)
# In browser, navigate to:
http://localhost:8080/api/connections/gmail?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

GET /api/connections/gmail/callback

Description: Handles the OAuth2 callback from Google after user authorization. This endpoint is called automatically by Google and should not be called directly.

Authentication: Not required (callback from Google, uses session data)

Query Parameters:

ParameterTypeRequiredDescription
codestringYesAuthorization code from Google
statestringYesCSRF protection token

Success Response (200 OK):

{
  "success": true,
  "message": "Gmail and Drive connected successfully",
  "connections": {
    "gmail": {
      "id": 1,
      "user_id": 1,
      "service_id": 2,
      "service_name": "gmail",
      "service_display_name": "Gmail",
      "connected_at": "2025-12-15T10:00:00Z",
      "updated_at": "2025-12-15T10:00:00Z",
      "token_expires_at": "2025-12-15T11:00:00Z"
    },
    "drive": {
      "id": 2,
      "user_id": 1,
      "service_id": 3,
      "service_name": "drive",
      "service_display_name": "Google Drive",
      "connected_at": "2025-12-15T10:00:00Z",
      "updated_at": "2025-12-15T10:00:00Z",
      "token_expires_at": "2025-12-15T11:00:00Z"
    }
  }
}

Response Fields:

FieldTypeDescription
successbooleanAlways true on successful connection
messagestringSuccess message
connections.gmailobjectGmail connection details
connections.driveobjectDrive connection details (same OAuth token)
*.idintegerConnection database ID
*.user_idintegerUser ID
*.service_idintegerService ID
*.service_namestringInternal service name
*.service_display_namestringDisplay name of the service
*.connected_atstringISO 8601 timestamp of initial connection
*.updated_atstringISO 8601 timestamp of last token update
*.token_expires_atstringISO 8601 timestamp of when access token expires

Error Responses:

StatusErrorDescription
400Invalid sessionSession expired or user_id not found in session
404Gmail service not foundGmail service not configured in database
500OAuth failed: <details>OAuth token exchange failed or other error

Example:

# This endpoint is called automatically by Google after user consent
# You cannot call it directly - it requires valid OAuth state and code from Google

DELETE /api/connections/gmail

Description: Disconnect Gmail service by removing the stored OAuth connection. This revokes the application’s access to the user’s Gmail account.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body: None required

Success Response (200 OK):

{
  "message": "Gmail disconnected successfully"
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
404Gmail service not foundGmail service not configured in database
404Gmail not connectedUser hasn’t connected Gmail yet

Example:

curl -X DELETE http://localhost:8080/api/connections/gmail \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Facebook OAuth Connections

GET/POST /api/connections/facebook

Description: Initiate the Facebook OAuth2 authentication flow. Redirects the user to Facebook’s consent screen to authorize Facebook access.

Authentication: Required (Bearer token or URL parameter)

Request Headers:

Authorization: Bearer <token>

OR Query Parameters:

ParameterTypeRequiredDescription
tokenstringYes (if no header)JWT authentication token

Success Response (302 Redirect): Redirects user to Facebook OAuth consent screen where they can authorize Facebook access.

OAuth Scopes Requested:

  • email - User’s email address
  • public_profile - User’s basic profile information (name, picture)
  • user_posts - Access to user’s posts

Error Responses:

StatusErrorDescription
401Authentication requiredNo token provided in header or URL
401Invalid tokenToken is malformed or invalid
401Invalid token payloadToken doesn’t contain user_id

Example:

# Using Authorization header
curl -X GET http://localhost:8080/api/connections/facebook \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Using URL parameter (useful for browser redirects)
# In browser, navigate to:
http://localhost:8080/api/connections/facebook?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

GET /api/connections/facebook/callback

Description: Handles the OAuth2 callback from Facebook after user authorization. This endpoint is called automatically by Facebook and should not be called directly.

Authentication: Not required (callback from Facebook, uses session data)

Query Parameters:

ParameterTypeRequiredDescription
codestringYesAuthorization code from Facebook
statestringYesCSRF protection token

Success Response (200 OK):

{
  "success": true,
  "message": "Facebook connected successfully",
  "connection": {
    "id": 1,
    "user_id": 1,
    "service_id": 4,
    "service_name": "facebook",
    "service_display_name": "Facebook",
    "connected_at": "2025-12-15T10:00:00Z",
    "updated_at": "2025-12-15T10:00:00Z",
    "token_expires_at": "2026-02-12T10:00:00Z"
  }
}

Response Fields:

FieldTypeDescription
successbooleanAlways true on successful connection
messagestringSuccess message
connectionobjectConnection details
connection.idintegerConnection database ID
connection.user_idintegerUser ID
connection.service_idintegerService ID
connection.service_namestringInternal service name
connection.service_display_namestringDisplay name of the service
connection.connected_atstringISO 8601 timestamp of initial connection
connection.updated_atstringISO 8601 timestamp of last token update
connection.token_expires_atstringISO 8601 timestamp (typically ~60 days)

Error Responses:

StatusErrorDescription
400Invalid sessionSession expired or user_id not found in session
404Facebook service not foundFacebook service not configured in database
500OAuth failed: <details>OAuth token exchange failed or other error

Example:

# This endpoint is called automatically by Facebook after user consent
# You cannot call it directly - it requires valid OAuth state and code from Facebook

DELETE /api/connections/facebook

Description: Disconnect Facebook service by removing the stored OAuth connection. This revokes the application’s access to the user’s Facebook account.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body: None required

Success Response (200 OK):

{
  "message": "Facebook disconnected successfully"
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
404Facebook service not foundFacebook service not configured in database
404Facebook not connectedUser hasn’t connected Facebook yet

Example:

curl -X DELETE http://localhost:8080/api/connections/facebook \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

GitHub OAuth Connections

GET/POST /api/connections/github

Description: Initiate the GitHub OAuth2 authentication flow. Redirects the user to GitHub’s consent screen to authorize GitHub access.

Authentication: Required (Bearer token or URL parameter)

Request Headers:

Authorization: Bearer <token>

OR Query Parameters:

ParameterTypeRequiredDescription
tokenstringYes (if no header)JWT authentication token

Success Response (302 Redirect): Redirects user to GitHub OAuth consent screen where they can authorize GitHub access.

OAuth Scopes Requested:

  • repo - Full control of private and public repositories
  • read:user - Read user profile data
  • user:email - Access to user’s email addresses

Error Responses:

StatusErrorDescription
401Authentication requiredNo token provided in header or URL
401Invalid tokenToken is malformed or invalid
401Invalid token payloadToken doesn’t contain user_id

Example:

# Using Authorization header
curl -X GET http://localhost:8080/api/connections/github \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Using URL parameter (useful for browser redirects)
# In browser, navigate to:
http://localhost:8080/api/connections/github?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

GET /api/connections/github/callback

Description: Handles the OAuth2 callback from GitHub after user authorization. This endpoint is called automatically by GitHub and should not be called directly.

Authentication: Not required (callback from GitHub, uses session data)

Query Parameters:

ParameterTypeRequiredDescription
codestringYesAuthorization code from GitHub
statestringYesCSRF protection token

Success Response (200 OK):

{
  "success": true,
  "message": "GitHub connected successfully",
  "connection": {
    "id": 1,
    "user_id": 1,
    "service_id": 5,
    "service_name": "github",
    "service_display_name": "GitHub",
    "connected_at": "2025-12-15T10:00:00Z",
    "updated_at": "2025-12-15T10:00:00Z",
    "token_expires_at": "2026-12-15T10:00:00Z"
  }
}

Response Fields:

FieldTypeDescription
successbooleanAlways true on successful connection
messagestringSuccess message
connectionobjectConnection details
connection.idintegerConnection database ID
connection.user_idintegerUser ID
connection.service_idintegerService ID
connection.service_namestringInternal service name
connection.service_display_namestringDisplay name of the service
connection.connected_atstringISO 8601 timestamp of initial connection
connection.updated_atstringISO 8601 timestamp of last token update
connection.token_expires_atstringISO 8601 timestamp (GitHub tokens typically don’t expire)

Error Responses:

StatusErrorDescription
400Invalid sessionSession expired or user_id not found in session
404GitHub service not foundGitHub service not configured in database
500OAuth failed: <details>OAuth token exchange failed or other error

Example:

# This endpoint is called automatically by GitHub after user consent
# You cannot call it directly - it requires valid OAuth state and code from GitHub

DELETE /api/connections/github

Description: Disconnect GitHub service by removing the stored OAuth connection. This revokes the application’s access to the user’s GitHub account.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body: None required

Success Response (200 OK):

{
  "message": "GitHub disconnected successfully"
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
404GitHub service not foundGitHub service not configured in database
404GitHub not connectedUser hasn’t connected GitHub yet

Example:

curl -X DELETE http://localhost:8080/api/connections/github \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Spotify OAuth Connections

GET/POST /api/connections/spotify

Description: Initiate the Spotify OAuth2 authentication flow. Redirects the user to Spotify’s consent screen to authorize Spotify access.

Authentication: Required (Bearer token or URL parameter)

Request Headers:

Authorization: Bearer <token>

OR Query Parameters:

ParameterTypeRequiredDescription
tokenstringYes (if no header)JWT authentication token

Success Response (302 Redirect): Redirects user to Spotify OAuth consent screen where they can authorize Spotify access.

OAuth Scopes Requested:

  • user-read-playback-state - Read playback state
  • user-modify-playback-state - Control playback
  • playlist-read-private - Read private playlists
  • playlist-modify-public - Modify public playlists
  • playlist-modify-private - Modify private playlists
  • user-library-read - Read saved tracks
  • user-library-modify - Modify saved tracks

Error Responses:

StatusErrorDescription
401Authentication requiredNo token provided in header or URL
401Invalid tokenToken is malformed or invalid
401Invalid token payloadToken doesn’t contain user_id

Example:

# Using Authorization header
curl -X GET http://localhost:8080/api/connections/spotify \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Using URL parameter (useful for browser redirects)
# In browser, navigate to:
http://localhost:8080/api/connections/spotify?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

GET /api/connections/spotify/callback

Description: Handles the OAuth2 callback from Spotify after user authorization. This endpoint is called automatically by Spotify and should not be called directly.

Authentication: Not required (callback from Spotify, uses custom state storage)

Query Parameters:

ParameterTypeRequiredDescription
codestringYesAuthorization code from Spotify
statestringYesCSRF protection token
errorstringNoError code if authorization failed

Success Response (200 OK):

{
  "success": true,
  "message": "Spotify connected successfully",
  "connection": {
    "id": 1,
    "user_id": 1,
    "service_id": 6,
    "service_name": "spotify",
    "service_display_name": "Spotify",
    "connected_at": "2025-12-15T10:00:00Z",
    "updated_at": "2025-12-15T10:00:00Z",
    "token_expires_at": "2025-12-15T11:00:00Z"
  }
}

Response Fields:

FieldTypeDescription
successbooleanAlways true on successful connection
messagestringSuccess message
connectionobjectConnection details
connection.idintegerConnection database ID
connection.user_idintegerUser ID
connection.service_idintegerService ID
connection.service_namestringInternal service name
connection.service_display_namestringDisplay name of the service
connection.connected_atstringISO 8601 timestamp of initial connection
connection.updated_atstringISO 8601 timestamp of last token update
connection.token_expires_atstringISO 8601 timestamp (typically 1 hour)

Error Responses:

StatusErrorDescription
400OAuth error: <error>User denied authorization or other OAuth error
400Missing authorization code or stateRequired parameters not provided
400Invalid or expired OAuth stateState token is invalid or expired
404Spotify service not foundSpotify service not configured in database
500Token exchange failed: <details>Failed to exchange code for access token
500OAuth failed: <details>Other OAuth-related error

Example:

# This endpoint is called automatically by Spotify after user consent
# You cannot call it directly - it requires valid OAuth state and code from Spotify

DELETE /api/connections/spotify

Description: Disconnect Spotify service by removing the stored OAuth connection. This revokes the application’s access to the user’s Spotify account.

Authentication: Required (Bearer token)

Request Headers:

Authorization: Bearer <token>

Request Body: None required

Success Response (200 OK):

{
  "message": "Spotify disconnected successfully"
}

Error Responses:

StatusErrorDescription
401Authorization token is missingNo Authorization header provided
401Invalid or expired tokenToken is invalid or has expired
404Spotify service not foundSpotify service not configured in database
404Spotify not connectedUser hasn’t connected Spotify yet

Example:

curl -X DELETE http://localhost:8080/api/connections/spotify \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Google Services (Gmail & Drive)

Overview

Google services integration provides automation for Gmail email monitoring and Google Drive file management. Both services share the same OAuth connection, allowing seamless integration across Google’s ecosystem.

Connection Setup

  1. Navigate to /api/connections/gmail or use the frontend connection interface
  2. Authorize AREA to access your Google account
  3. OAuth scopes granted:
    • openid, email, profile - Basic authentication
    • https://www.googleapis.com/auth/gmail.readonly - Read Gmail
    • https://www.googleapis.com/auth/drive - Manage Google Drive files

Note: One OAuth connection enables both Gmail and Drive services.


Gmail Service

Service Details

  • Service Name: gmail
  • Display Name: Gmail
  • Description: Email detection and monitoring
  • Requires OAuth: Yes
  • Actions: 2
  • Reactions: 0

Actions

1. Email Received From

Name: email_received_from

Description: Triggers when an email is received from a specific sender.

Configuration:

{
  "sender": "example@domain.com"
}

Parameters:

FieldTypeRequiredDescription
senderstringYesEmail address of the sender (must be valid email format)

Example Workflow:

{
  "name": "Alert on Boss Emails",
  "action_id": <email_received_from_id>,
  "reaction_id": <send_email_id>,
  "action_config": {
    "sender": "boss@company.com"
  },
  "reaction_config": {
    "to": "personal@email.com",
    "subject": "Work Email Alert",
    "body": "You received an email from your boss!"
  }
}

2. Email Subject Contains

Name: email_subject_contains

Description: Triggers when an email subject line contains a specific keyword.

Configuration:

{
  "keyword": "urgent"
}

Parameters:

FieldTypeRequiredDescription
keywordstringYesKeyword to search for in email subject (case-insensitive)

Example Workflow:

{
  "name": "Flag Urgent Emails",
  "action_id": <email_subject_contains_id>,
  "reaction_id": <log_message_id>,
  "action_config": {
    "keyword": "URGENT"
  },
  "reaction_config": {
    "message": "Urgent email detected!"
  }
}

Google Drive Service

Service Details

  • Service Name: drive
  • Display Name: Google Drive
  • Description: Cloud storage and file management
  • Requires OAuth: Yes
  • Actions: 2
  • Reactions: 3

Actions

1. New File in Folder

Name: new_file_in_folder

Description: Triggers when a new file is added to a specific folder in Google Drive.

Configuration:

{
  "folder_name": "Work Documents"
}

Parameters:

FieldTypeRequiredDescription
folder_namestringYesName of the folder to monitor

Example Workflow:

{
  "name": "Backup Work Docs",
  "action_id": <new_file_in_folder_id>,
  "reaction_id": <send_email_id>,
  "action_config": {
    "folder_name": "Important Projects"
  },
  "reaction_config": {
    "to": "backup@service.com",
    "subject": "New file added",
    "body": "A new file was added to Important Projects folder"
  }
}

2. New File Uploaded

Name: new_file_uploaded

Description: Triggers when any new file is uploaded to Google Drive (monitors all folders).

Configuration:

{}

Parameters: None

Example Workflow:

{
  "name": "Track All Uploads",
  "action_id": <new_file_uploaded_id>,
  "reaction_id": <log_message_id>,
  "action_config": {},
  "reaction_config": {
    "message": "New file uploaded to Drive"
  }
}

Reactions

1. Create File

Name: create_file

Description: Creates a new text file in Google Drive.

Configuration:

{
  "file_name": "notes.txt",
  "content": "File content here",
  "folder_name": "My Folder"
}

Parameters:

FieldTypeRequiredDescription
file_namestringYesName of the file to create
contentstringYesText content of the file
folder_namestringNoFolder name (omit for root directory)

Example Workflow:

{
  "name": "Daily Note Creation",
  "action_id": <time_matches_id>,
  "reaction_id": <create_file_id>,
  "action_config": {
    "time": "09:00"
  },
  "reaction_config": {
    "file_name": "daily-log.txt",
    "content": "Daily log entry",
    "folder_name": "Logs"
  }
}

2. Create Folder

Name: create_folder

Description: Creates a new folder in Google Drive.

Configuration:

{
  "folder_name": "Project Files"
}

Parameters:

FieldTypeRequiredDescription
folder_namestringYesName of the folder to create

Example Workflow:

{
  "name": "Morning Folder Setup",
  "action_id": <time_matches_id>,
  "reaction_id": <create_folder_id>,
  "action_config": {
    "time": "09:00"
  },
  "reaction_config": {
    "folder_name": "Daily Reports"
  }
}

3. Share File

Name: share_file

Description: Shares an existing file in Google Drive with another user.

Configuration:

{
  "file_name": "report.pdf",
  "email": "colleague@company.com",
  "role": "reader"
}

Parameters:

FieldTypeRequiredDescription
file_namestringYesName of the file to share
emailstringYesEmail address to share with (valid email format)
rolestringYesPermission level: reader or writer

Example Workflow:

{
  "name": "Auto-Share Reports",
  "action_id": <new_file_in_folder_id>,
  "reaction_id": <share_file_id>,
  "action_config": {
    "folder_name": "Reports"
  },
  "reaction_config": {
    "file_name": "weekly-report.pdf",
    "email": "manager@company.com",
    "role": "reader"
  }
}

Common Use Cases

1. Email Notification on Important Messages

  • Trigger: Email received from specific sender
  • Action: Send notification email to personal account

2. Auto-Organize Drive Files

  • Trigger: New file uploaded to Drive
  • Action: Create folder structure based on date

3. Collaborative File Sharing

  • Trigger: New file in shared folder
  • Action: Automatically share with team members

4. Backup Important Emails

  • Trigger: Email subject contains “backup”
  • Action: Create Drive file with email details

API Endpoints

See OAuth Connections for connection endpoints.

Troubleshooting

Gmail Not Detecting Emails:

  • Check OAuth connection is active
  • Verify sender email address is exact match
  • Scheduler runs every 1 minute - there may be a delay

Drive File Creation Fails:

  • Ensure OAuth permissions include Drive scope
  • Check folder name exists (if specified)
  • Verify file name doesn’t contain invalid characters

Sharing Fails:

  • Verify email address format is valid
  • Check that file exists in Drive
  • Ensure role is either reader or writer

Facebook Service

Overview

Facebook service provides automation for monitoring and creating posts on your personal timeline. Monitor your posting activity and automatically create posts based on triggers.

Connection Setup

  1. Navigate to /api/connections/facebook or use the frontend connection interface
  2. Authorize AREA to access your Facebook account
  3. OAuth scopes granted:
    • email - User’s email address
    • public_profile - Basic profile information
    • user_posts - Read and create posts on your timeline

Service Details

  • Service Name: facebook
  • Display Name: Facebook
  • Description: Personal timeline post monitoring
  • Requires OAuth: Yes
  • Actions: 2
  • Reactions: 1

Actions

1. New Post Created

Name: new_post_created

Description: Triggers when you create a new post on your Facebook timeline.

Configuration:

{}

Parameters: None

Example Workflow:

{
  "name": "Log My Posts",
  "action_id": <new_post_created_id>,
  "reaction_id": <log_message_id>,
  "action_config": {},
  "reaction_config": {
    "message": "You just posted on Facebook!"
  }
}

Use Cases:

  • Track your posting frequency
  • Get notifications when you post
  • Create backups of your posts
  • Log posting activity to external services

2. Post Contains Keyword

Name: post_contains_keyword

Description: Triggers when you create a Facebook post that contains a specific keyword.

Configuration:

{
  "keyword": "vacation"
}

Parameters:

FieldTypeRequiredDescription
keywordstringYesKeyword to search for in post content (case-insensitive)

Example Workflow:

{
  "name": "Track Work Posts",
  "action_id": <post_contains_keyword_id>,
  "reaction_id": <send_email_id>,
  "action_config": {
    "keyword": "work"
  },
  "reaction_config": {
    "to": "manager@company.com",
    "subject": "Work-related Facebook post",
    "body": "You posted about work on Facebook"
  }
}

Use Cases:

  • Monitor posts about specific topics
  • Track mentions of certain keywords
  • Alert on sensitive content
  • Create topic-based archives

Reactions

1. Create Post

Name: create_post

Description: Creates a new post on your Facebook timeline.

Configuration:

{
  "message": "Hello from AREA automation!"
}

Parameters:

FieldTypeRequiredDescription
messagestringYesContent of the post (max 5000 characters)

Example Workflow:

{
  "name": "Daily Motivational Post",
  "action_id": <time_matches_id>,
  "reaction_id": <create_post_id>,
  "action_config": {
    "time": "08:00"
  },
  "reaction_config": {
    "message": "Good morning! Have a great day!"
  }
}

Use Cases:

  • Schedule automated posts
  • Cross-post from other platforms
  • Share daily updates automatically
  • Post reminders or announcements

Common Use Cases

1. Daily Automated Posts

  • Trigger: Time matches (e.g., 9:00 AM)
  • Reaction: Create post with motivational quote

2. Cross-Platform Posting

  • Trigger: New file uploaded to Drive
  • Reaction: Create Facebook post announcing the upload

3. Post Activity Tracking

  • Trigger: New post created
  • Reaction: Log message to workflow logs for analytics

4. Keyword-Based Workflows

  • Trigger: Post contains “event”
  • Reaction: Create Drive file with event details

5. Scheduled Announcements

  • Trigger: Date matches (specific date)
  • Reaction: Create post with announcement

API Endpoints

See OAuth Connections for connection endpoints.


Limitations

  • Post Monitoring: Only detects your own posts, not friends’ posts
  • Rate Limiting: Facebook Graph API has rate limits - excessive posting may be throttled
  • Permissions: Requires user_posts permission during OAuth authorization
  • Content: Posts are text-only (no images/videos through this API)
  • Timeline: Can only post to your own timeline, not to pages or groups

Troubleshooting

Posts Not Being Detected:

  • Verify OAuth connection is active at /api/connections
  • Check that user_posts permission was granted during authorization
  • Scheduler runs every 1 minute - there may be a delay
  • Ensure you’re checking your own posts, not others’

Create Post Fails:

  • Check message content doesn’t exceed 5000 characters
  • Verify Facebook OAuth connection is valid
  • Check for Facebook API rate limiting
  • Ensure message is not empty

Keyword Detection Not Working:

  • Keyword search is case-insensitive but must be exact match
  • Check for typos in keyword configuration
  • Special characters may affect matching

Future Enhancements

See Issue #73 for planned optional features:

  • Post comment on own posts
  • Like/react to posts
  • Trigger on post reactions/likes received
  • Trigger on post comments received

GitHub Service

Overview

GitHub service enables automation based on repository events such as stars, issues, and pull requests. Monitor repository activity and create issues automatically based on triggers.

Connection Setup

  1. Navigate to /api/connections/github or use the frontend connection interface
  2. Authorize AREA to access your GitHub account
  3. OAuth scopes granted:
    • repo - Full control of private and public repositories
    • read:user - Read user profile data
    • user:email - Access to user’s email addresses

Service Details

  • Service Name: github
  • Display Name: GitHub
  • Description: Repository monitoring and automation
  • Requires OAuth: Yes
  • Actions: 3
  • Reactions: 1

Actions

1. New Star on Repository

Name: new_star_on_repo

Description: Triggers when someone stars one of your repositories.

Configuration:

{
  "repo_name": "username/repository"
}

Parameters:

FieldTypeRequiredDescription
repo_namestringYesFull repository name in format username/repo

Example Workflow:

{
  "name": "Thank Stars",
  "action_id": <new_star_on_repo_id>,
  "reaction_id": <send_email_id>,
  "action_config": {
    "repo_name": "myusername/my-awesome-project"
  },
  "reaction_config": {
    "to": "me@email.com",
    "subject": "New Star!",
    "body": "Someone starred your repository!"
  }
}

Use Cases:

  • Get notified when your repository gets stars
  • Track repository popularity
  • Send thank-you messages to stargazers
  • Log star activity for analytics

2. New Issue Created

Name: new_issue_created

Description: Triggers when a new issue is created in your repository.

Configuration:

{
  "repo_name": "username/repository"
}

Parameters:

FieldTypeRequiredDescription
repo_namestringYesFull repository name in format username/repo

Example Workflow:

{
  "name": "Issue Alert",
  "action_id": <new_issue_created_id>,
  "reaction_id": <send_email_id>,
  "action_config": {
    "repo_name": "myusername/my-project"
  },
  "reaction_config": {
    "to": "team@company.com",
    "subject": "New GitHub Issue",
    "body": "A new issue was created in your repository"
  }
}

Use Cases:

  • Get instant notifications on new issues
  • Alert team members about new bug reports
  • Create backup logs of all issues
  • Trigger automated responses to issues

3. New Pull Request Opened

Name: new_pr_opened

Description: Triggers when a new pull request is opened in your repository.

Configuration:

{
  "repo_name": "username/repository"
}

Parameters:

FieldTypeRequiredDescription
repo_namestringYesFull repository name in format username/repo

Example Workflow:

{
  "name": "PR Notification",
  "action_id": <new_pr_opened_id>,
  "reaction_id": <log_message_id>,
  "action_config": {
    "repo_name": "myusername/open-source-project"
  },
  "reaction_config": {
    "message": "New PR opened - review needed!"
  }
}

Use Cases:

  • Get notified about new pull requests
  • Alert code reviewers automatically
  • Track PR activity for metrics
  • Create review reminder workflows

Reactions

1. Create Issue

Name: create_issue

Description: Creates a new issue in a specified repository.

Configuration:

{
  "repo_name": "username/repository",
  "title": "Bug Report",
  "body": "Detailed description of the issue"
}

Parameters:

FieldTypeRequiredDescription
repo_namestringYesFull repository name in format username/repo
titlestringYesIssue title (max 200 characters)
bodystringYesIssue description/body (max 5000 characters)

Example Workflow:

{
  "name": "Auto-Report Failures",
  "action_id": <email_subject_contains_id>,
  "reaction_id": <create_issue_id>,
  "action_config": {
    "keyword": "FAILURE"
  },
  "reaction_config": {
    "repo_name": "myusername/monitoring",
    "title": "Automated Failure Report",
    "body": "A failure was detected in the system. Please investigate."
  }
}

Use Cases:

  • Automatically create issues from error notifications
  • Cross-post bugs from other platforms
  • Schedule recurring tasks as issues
  • Create issues from user feedback

Common Use Cases

1. Star Tracker with Email Alerts

  • Trigger: New star on repository
  • Reaction: Send email notification to yourself

2. Issue-to-Issue Automation

  • Trigger: New issue in Repository A
  • Reaction: Create issue in Repository B (cross-repo tracking)

3. PR Review Reminders

  • Trigger: New PR opened
  • Reaction: Send email to code review team

4. Automated Bug Reporting

  • Trigger: Email subject contains “ERROR”
  • Reaction: Create GitHub issue with error details

5. Repository Activity Logging

  • Trigger: New star/issue/PR
  • Reaction: Log message to workflow logs for analytics

API Endpoints

See OAuth Connections for connection endpoints.


Limitations

  • Rate Limiting: GitHub API has rate limits (5000 requests/hour for authenticated users)
  • Repository Access: Can only monitor repositories you have access to
  • Event Detection: Scheduler checks every 1 minute, so there may be delays
  • Issue Creation: Requires write access to the target repository
  • Private Repos: Requires repo scope for private repository access

Troubleshooting

Events Not Being Detected:

  • Verify repository name is in correct format: username/repo
  • Check OAuth connection is active at /api/connections
  • Ensure you have access to the repository
  • Scheduler runs every 1 minute - there may be a delay
  • For private repos, ensure repo scope was granted

Create Issue Fails:

  • Verify you have write access to the repository
  • Check repository name format: username/repo
  • Ensure title doesn’t exceed 200 characters
  • Ensure body doesn’t exceed 5000 characters
  • Verify OAuth connection has repo scope

Repository Not Found:

  • Double-check spelling of repository name
  • Ensure you have access to the repository
  • For organization repos, use organization/repo format
  • Private repos require repo OAuth scope

Advanced Usage

Repository Name Format

Always use the full repository name including the owner:

  • Correct: octocat/Hello-World
  • Incorrect: Hello-World

Monitoring Multiple Repositories

Create separate workflows for each repository you want to monitor:

{
  "name": "Monitor Project A",
  "action_id": <new_issue_created_id>,
  "action_config": {
    "repo_name": "username/project-a"
  }
}
{
  "name": "Monitor Project B",
  "action_id": <new_issue_created_id>,
  "action_config": {
    "repo_name": "username/project-b"
  }
}

Cross-Repository Workflows

Create issues in one repository based on events in another:

{
  "name": "Bug Tracker",
  "action_id": <new_issue_created_id>,
  "reaction_id": <create_issue_id>,
  "action_config": {
    "repo_name": "company/production-app"
  },
  "reaction_config": {
    "repo_name": "company/bug-tracker",
    "title": "Bug reported in production",
    "body": "New bug issue detected"
  }
}

Spotify Service

Overview

Spotify service provides automation for music playback, playlist management, and library monitoring. Control your music, manage playlists, and track your listening activity automatically.

Connection Setup

  1. Navigate to /api/connections/spotify or use the frontend connection interface
  2. Authorize AREA to access your Spotify account
  3. OAuth scopes granted:
    • user-read-playback-state - Read current playback state
    • user-modify-playback-state - Control playback
    • playlist-read-private - Read private playlists
    • playlist-modify-public - Modify public playlists
    • playlist-modify-private - Modify private playlists
    • user-library-read - Read saved tracks
    • user-library-modify - Modify saved tracks

Note: Both Free and Premium Spotify accounts can connect, but some playback features require Spotify Premium.


Service Details

  • Service Name: spotify
  • Display Name: Spotify
  • Description: Music playback control and playlist management
  • Requires OAuth: Yes
  • Actions: 3
  • Reactions: 3

Actions

1. Track Added to Playlist

Name: track_added_to_playlist

Description: Triggers when a new track is added to a specific playlist.

Configuration:

{
  "playlist_id": "37i9dQZF1DXcBWIGoYBM5M"
}

Parameters:

FieldTypeRequiredDescription
playlist_idstringYesSpotify playlist ID (found in playlist URL or share link)

How to find playlist ID:

  • Open playlist in Spotify
  • Click Share → Copy link to playlist
  • Extract ID from URL: spotify.com/playlist/[THIS_IS_THE_ID]

Example Workflow:

{
  "name": "Log Playlist Updates",
  "action_id": <track_added_to_playlist_id>,
  "reaction_id": <log_message_id>,
  "action_config": {
    "playlist_id": "37i9dQZF1DXcBWIGoYBM5M"
  },
  "reaction_config": {
    "message": "New track added to your playlist!"
  }
}

Use Cases:

  • Track changes to collaborative playlists
  • Get notified when curated playlists update
  • Create backups of playlist changes
  • Monitor playlist growth

2. Track Saved to Library

Name: track_saved

Description: Triggers when you save (like) a new track to your library.

Configuration:

{}

Parameters: None

Example Workflow:

{
  "name": "Auto-Add Liked Songs to Playlist",
  "action_id": <track_saved_id>,
  "reaction_id": <add_to_playlist_id>,
  "action_config": {},
  "reaction_config": {
    "playlist_id": "your-favorites-playlist-id",
    "track_uri": "spotify:track:TRACK_ID"
  }
}

Use Cases:

  • Automatically add liked songs to a specific playlist
  • Get notifications when you like songs
  • Track your music discovery patterns
  • Create monthly “liked songs” reports

3. Playback Started

Name: playback_started

Description: Triggers when you start playing music on Spotify.

Configuration:

{}

Parameters: None

Example Workflow:

{
  "name": "Log Listening Sessions",
  "action_id": <playback_started_id>,
  "reaction_id": <log_message_id>,
  "action_config": {},
  "reaction_config": {
    "message": "Started listening to music"
  }
}

Use Cases:

  • Track listening habits
  • Create time-based listening logs
  • Trigger actions when music starts (e.g., smart home automation)
  • Monitor music playback sessions

Reactions

1. Add Track to Playlist

Name: add_to_playlist

Description: Adds a track to a specific playlist.

Configuration:

{
  "playlist_id": "37i9dQZF1DXcBWIGoYBM5M",
  "track_uri": "spotify:track:6rqhFgbbKwnb9MLmUQDhG6"
}

Parameters:

FieldTypeRequiredDescription
playlist_idstringYesSpotify playlist ID
track_uristringYesSpotify track URI or ID (formats: spotify:track:xxxxx or just track ID)

How to find track URI:

  • Right-click track in Spotify → Share → Copy Song Link
  • Extract ID from URL: spotify.com/track/[THIS_IS_THE_ID]
  • Use as: spotify:track:[ID] or just the ID

Example Workflow:

{
  "name": "Backup Liked Songs",
  "action_id": <track_saved_id>,
  "reaction_id": <add_to_playlist_id>,
  "action_config": {},
  "reaction_config": {
    "playlist_id": "your-backup-playlist-id",
    "track_uri": "spotify:track:6rqhFgbbKwnb9MLmUQDhG6"
  }
}

Use Cases:

  • Automatically organize liked songs into playlists
  • Create genre-specific auto-curated playlists
  • Backup favorite tracks to multiple playlists
  • Share tracks across collaborative playlists

2. Create Playlist

Name: create_playlist

Description: Creates a new playlist in your Spotify account.

Configuration:

{
  "name": "My Automated Playlist",
  "description": "Created by AREA automation",
  "public": true
}

Parameters:

FieldTypeRequiredDescription
namestringYesPlaylist name (max 100 characters)
descriptionstringNoPlaylist description (max 300 characters)
publicbooleanNoMake playlist public (default: true)

Example Workflow:

{
  "name": "Weekly Playlist Creator",
  "action_id": <time_matches_id>,
  "reaction_id": <create_playlist_id>,
  "action_config": {
    "time": "00:00"
  },
  "reaction_config": {
    "name": "Weekly Favorites",
    "description": "Auto-generated playlist",
    "public": false
  }
}

Use Cases:

  • Create monthly/weekly playlists automatically
  • Generate playlists based on time or date triggers
  • Organize music collections programmatically
  • Create themed playlists on schedule

3. Start Playback

Name: start_playback

Description: Starts playing a specific track or playlist on your active Spotify device.

Configuration:

{
  "track_uri": "spotify:track:6rqhFgbbKwnb9MLmUQDhG6",
  "context_uri": "spotify:playlist:37i9dQZF1DXcBWIGoYBM5M"
}

Parameters:

FieldTypeRequiredDescription
track_uristringNoSpotify track URI to play
context_uristringNoSpotify playlist/album URI for context

Note: At least one parameter should be provided. If both are provided, track plays within the context.

Example Workflow:

{
  "name": "Morning Alarm Music",
  "action_id": <time_matches_id>,
  "reaction_id": <start_playback_id>,
  "action_config": {
    "time": "07:00"
  },
  "reaction_config": {
    "context_uri": "spotify:playlist:your-morning-playlist-id"
  }
}

Use Cases:

  • Wake up to music at specific times
  • Start playlist when arriving home (with location triggers)
  • Play focus music during work hours
  • Automated DJ for events

Common Use Cases

1. Auto-Archive Liked Songs

  • Trigger: Track saved to library
  • Reaction: Add to monthly backup playlist

2. Morning Music Routine

  • Trigger: Time matches (7:00 AM)
  • Reaction: Start playback of energizing playlist

3. Playlist Change Notifications

  • Trigger: Track added to followed playlist
  • Reaction: Send email notification

4. Monthly Playlist Generator

  • Trigger: Date matches (1st of month)
  • Reaction: Create new playlist for the month

5. Music Discovery Tracker

  • Trigger: Track saved to library
  • Reaction: Log to Drive file for monthly review

API Endpoints

See OAuth Connections for connection endpoints.


Limitations

  • Playback Control: Requires Spotify Premium for on-demand playback
  • Active Device: Start playback requires an active Spotify device (phone, computer, speaker)
  • Rate Limiting: Spotify API has rate limits
  • Track URIs: Must use valid Spotify URIs or IDs
  • Playlist Access: Can only modify playlists you own or have edit access to
  • Free Users: Limited playback control features

Troubleshooting

Tracks Not Being Detected:

  • Verify OAuth connection is active at /api/connections
  • Check playlist ID is correct
  • Ensure playlist is accessible to your account
  • Scheduler runs every 1 minute - there may be a delay

Add to Playlist Fails:

  • Verify you have edit permissions for the playlist
  • Check track URI format (should be spotify:track:ID or just ID)
  • Ensure playlist ID is valid
  • Track must exist in Spotify’s catalog

Start Playback Fails:

  • Most common: No active Spotify device
    • Solution: Open Spotify on phone/computer/speaker first
  • Verify Spotify Premium subscription (required for playback control)
  • Check track/playlist URI is valid
  • Ensure OAuth scopes include user-modify-playback-state

Create Playlist Fails:

  • Check playlist name doesn’t exceed 100 characters
  • Verify OAuth connection is active
  • Ensure description doesn’t exceed 300 characters

Advanced Usage

Finding Spotify IDs and URIs

Playlist ID:

URL: https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M
ID:  37i9dQZF1DXcBWIGoYBM5M

Track URI:

URL: https://open.spotify.com/track/6rqhFgbbKwnb9MLmUQDhG6
URI: spotify:track:6rqhFgbbKwnb9MLmUQDhG6
ID:  6rqhFgbbKwnb9MLmUQDhG6

Playback Devices

For start_playback to work, you need an active Spotify device:

  1. Open Spotify on any device (phone, computer, web player, smart speaker)
  2. Start playing any track (can pause it immediately)
  3. Device is now “active” and can receive playback commands
  4. Device must remain connected and logged in

URI Formats

AREA accepts multiple formats for track URIs:

  • spotify:track:6rqhFgbbKwnb9MLmUQDhG6
  • 6rqhFgbbKwnb9MLmUQDhG6
  • https://open.spotify.com/track/6rqhFgbbKwnb9MLmUQDhG6

All formats are automatically normalized to the correct Spotify URI format.

OAuth2 Authentication Implementation

Overview

Implemented Google OAuth2 authentication to allow users to register and login using their Google accounts. This provides a seamless authentication experience and reduces friction for new users.

Google Cloud Console Setup

Created OAuth2 Credentials

  • Project Name: AREA Dev
  • Application Type: Web application
  • OAuth Client ID: 539654341004-1omviqr547ctdc0kn4ikvovs6gfvcih7.apps.googleusercontent.com
  • Authorized Redirect URIs:
    • http://localhost:8080/api/auth/google/callback (development)
  • User Type: External
  • Application Status: Testing mode
  • Test Users: Added development team emails to audience list
  • Scopes: openid, email, profile

Architecture

OAuth2 Flow (Authorization Code Grant)

1. User clicks "Login with Google"
   ↓
2. Backend redirects to Google consent screen (/api/auth/google/login)
   ↓
3. User approves permissions on Google
   ↓
4. Google redirects back with authorization code (/api/auth/google/callback)
   ↓
5. Backend exchanges code for access token
   ↓
6. Backend fetches user profile from Google (email, name, Google ID)
   ↓
7. Backend finds or creates user in database
   ↓
8. Backend generates JWT token
   ↓
9. Returns JWT token to client

Implementation Details

Dependencies Added

Authlib==1.3.0      # OAuth2 client library
flask-cors          # Cross-Origin Resource Sharing support

Configuration (server/config.py)

# Google OAuth2
GOOGLE_CLIENT_ID = os.getenv('GOOGLE_CLIENT_ID')
GOOGLE_CLIENT_SECRET = os.getenv('GOOGLE_CLIENT_SECRET')
GOOGLE_DISCOVERY_URL = "https://accounts.google.com/.well-known/openid-configuration"

# Flask SECRET_KEY (required for OAuth session management)
SECRET_KEY = os.getenv('JWT_SECRET_KEY')  # Reuses existing JWT secret

# CORS
CORS_ORIGINS = os.getenv('CORS_ORIGINS', '*')

OAuth Client Initialization (server/app.py)

from flask_cors import CORS
from authlib.integrations.flask_client import OAuth

# Initialize CORS
CORS(app, origins=Config.CORS_ORIGINS)

# Initialize OAuth
oauth = OAuth(app)
oauth.register(
    name='google',
    client_id=Config.GOOGLE_CLIENT_ID,
    client_secret=Config.GOOGLE_CLIENT_SECRET,
    server_metadata_url=Config.GOOGLE_DISCOVERY_URL,
    client_kwargs={'scope': 'openid email profile'}
)

User Management Logic (server/utils/auth_utils.py)

find_or_create_oauth_user(provider, provider_user_id, email, name=None)

Handles the complex logic of finding or creating users from OAuth data:

Case 1: Existing OAuth User

  • User with matching oauth_provider and oauth_provider_id found
  • Returns existing user

Case 2: Existing Local User with Same Email

  • User with matching email but no OAuth data found
  • Links OAuth account to existing local account
  • Updates oauth_provider and oauth_provider_id fields
  • Allows users to login via both password and OAuth

Case 3: New User

  • Creates new user with OAuth data
  • Generates unique username from email or name
  • Sets password_hash to NULL (OAuth-only user)
  • Cannot login via password endpoint
def find_or_create_oauth_user(provider, provider_user_id, email, name=None):
    """Find or create a user from OAuth provider data"""

    # Try to find existing OAuth user
    user = User.query.filter_by(
        oauth_provider=provider,
        oauth_provider_id=provider_user_id
    ).first()

    if user:
        return user

    # Check for existing local user with same email
    existing_user = User.query.filter_by(email=email).first()
    if existing_user:
        # Link OAuth to existing account
        existing_user.oauth_provider = provider
        existing_user.oauth_provider_id = provider_user_id
        db.session.commit()
        return existing_user

    # Generate unique username
    base_username = name.lower().replace(' ', '_') if name else email.split('@')[0]
    username = base_username
    counter = 1
    while User.query.filter_by(username=username).first():
        username = f"{base_username}_{counter}"
        counter += 1

    # Create new OAuth user
    new_user = User(
        username=username,
        email=email,
        password_hash=None,
        oauth_provider=provider,
        oauth_provider_id=provider_user_id
    )

    db.session.add(new_user)
    db.session.commit()

    return new_user

Environment Variables

Required environment variables in .env:

# Google OAuth2 Configuration
GOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your-client-secret

# Flask Configuration (needed for OAuth sessions)
JWT_SECRET_KEY=your-secret-key

# CORS Configuration
CORS_ORIGINS=*

References