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:
-
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
-
Web Client (React app on port 8081)
- Browser-based user interface
- Service configuration
- AREA creation and management
-
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
-
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 -
Start all services with Docker Compose
docker-compose up --build -
Access the application
- Flask REST API: http://localhost:8080
- Web Client: http://localhost:8081
- API Info: http://localhost:8080/about.json
First Steps
- Register a new user account
- Subscribe to available services
- Create your first AREA by connecting an Action to a REAction
- 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 operationsFlask-CORS- Cross-Origin Resource Sharingpsycopg2-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 persistencemobile_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 Pagesmirror.yml- Repository sync to Epitech servers
Development Tools
Documentation
- mdBook: Project documentation hosting
- Markdown: Documentation format
- GitHub Pages: Documentation hosting at https://crackedontiti.github.io/AREA/
Version Control
- Git: Source control
- GitHub: Repository hosting
- Branches:
main- Production branchdev- 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
- Flask Documentation
- React Documentation
- Flutter Documentation
- PostgreSQL Documentation
- Docker Documentation
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:
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the following APIs:
- Google+ API (for OAuth login)
- Gmail API (for email monitoring)
- Go to “Credentials” → “Create Credentials” → “OAuth 2.0 Client ID”
- Configure the OAuth consent screen
- Add authorized redirect URIs:
http://localhost:8080/api/auth/google/callback(for login)http://localhost:8080/api/connections/gmail/callback(for Gmail)
- Copy the Client ID and Client Secret to your
.envfile
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- SSL25- 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:
- Enable 2-Factor Authentication on your Google account
- Go to App Passwords
- Generate a new app password for “Mail”
- 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 changesproduction- 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
| Provider | Host | Port | Notes |
|---|---|---|---|
| Gmail | smtp.gmail.com | 587 | Requires App Password |
| Outlook | smtp-mail.outlook.com | 587 | Use account password |
| Yahoo | smtp.mail.yahoo.com | 587 | Requires App Password |
| SendGrid | smtp.sendgrid.net | 587 | Use API key as password |
Gmail SMTP Setup
Gmail requires an App Password (not your regular password):
- Enable 2-Factor Authentication on your Google account
- Go to App Passwords
- Select “Mail” and your device
- Click “Generate”
- 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:
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient email address |
subject | string | Yes | Email subject line (max 200 chars) |
body | string | Yes | Email 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
-
Check SMTP credentials:
# Verify environment variables are set docker-compose exec server env | grep SMTP -
Test SMTP connection:
import smtplib server = smtplib.SMTP('smtp.gmail.com', 587) server.starttls() server.login('user@gmail.com', 'app-password') -
Check workflow logs:
curl http://localhost:8080/api/areas/1/logs \ -H "Authorization: Bearer <token>"
Common Errors
| Error | Cause | Solution |
|---|---|---|
| Authentication failed | Wrong password | Use App Password for Gmail |
| Connection refused | Wrong host/port | Verify SMTP settings |
| TLS required | Missing TLS config | Set SMTP_USE_TLS=true |
| Recipient rejected | Invalid email | Check 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.
Related Documentation
- Configuration - Full SMTP configuration options
- Services & Workflows - API endpoints for workflows
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
| Setting | Value | Description |
|---|---|---|
bind | 0.0.0.0:8080 | Listen on all interfaces, port 8080 |
workers | 1 | Single worker (see note below) |
timeout | 120 | 2-minute request timeout for long operations |
loglevel | info | Configurable 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
| Variable | Default | Description |
|---|---|---|
GUNICORN_WORKERS | 1 | Number of worker processes |
LOG_LEVEL | info | Logging level (debug, info, warning, error) |
FLASK_ENV | production | Environment 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
With Docker Compose (Recommended)
# 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
| Level | Description |
|---|---|
debug | Detailed debugging information |
info | General operational messages |
warning | Warning messages |
error | Error messages only |
critical | Critical errors only |
Set via environment variable:
LOG_LEVEL=debug
Related Documentation
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
| Technology | Purpose |
|---|---|
| React 18 | UI framework |
| React Router DOM 6 | Client-side routing |
| Tailwind CSS 3 | Utility-first styling |
| Fetch API | HTTP requests |
| Context API | Global 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:
- Responsive First: Mobile-friendly layouts using Tailwind CSS breakpoints
- User Feedback: Loading states, success/error messages for all operations
- Accessibility: Proper ARIA labels, keyboard navigation support
- Consistent UI: Dark theme with consistent color scheme
- Error Handling: Graceful API error handling with user-friendly messages
Color Scheme
The application uses a dark theme:
| Element | Color |
|---|---|
| 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
- Lazy Loading: Consider code-splitting for larger pages
- Memoization: Use
useMemoanduseCallbackfor expensive operations - API Caching: The
useServiceshook caches data to avoid redundant calls - Optimistic Updates: Consider implementing for better UX
Security Considerations
- JWT Storage: Tokens stored in localStorage (consider httpOnly cookies for production)
- Token Expiration: Handle 401 errors by redirecting to login
- Input Sanitization: All user input is validated before submission
- 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:
| Category | Location | Purpose |
|---|---|---|
| UI | components/ui/ | Generic, reusable UI elements |
| Layout | components/layout/ | Page structure components |
| Features | components/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:
| Prop | Type | Default | Description |
|---|---|---|---|
label | string | - | Input label text |
name | string | required | Input name attribute |
type | string | 'text' | Input type (text, email, password, etc.) |
value | string | - | Controlled input value |
onChange | function | required | Change handler |
error | string | - | Error message to display |
placeholder | string | - | Placeholder text |
disabled | boolean | false | Disable 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:
| Prop | Type | Default | Description |
|---|---|---|---|
children | node | required | Button content |
type | string | 'button' | Button type attribute |
onClick | function | - | Click handler |
loading | boolean | false | Show loading spinner |
disabled | boolean | false | Disable button |
variant | string | 'primary' | Button style variant |
className | string | - | Additional CSS classes |
Variants:
primary- Blue backgroundsecondary- Outline styledanger- 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:
| Prop | Type | Default | Description |
|---|---|---|---|
message | string | required | Alert message |
type | string | 'error' | Alert type (success, error, warning, info) |
onClose | function | - | Close handler (shows X button if provided) |
Usage:
import Alert from '../components/Alert';
{alertMessage && (
<Alert
message={alertMessage}
type={alertType}
onClose={() => setAlertMessage(null)}
/>
)}
PageHeader
A consistent header component for pages.
Location: src/components/ui/PageHeader.jsx
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | required | Page title |
subtitle | string | - | Optional subtitle |
action | node | - | 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:
| Prop | Type | Default | Description |
|---|---|---|---|
children | node | required | Page content |
Usage:
import Layout from '../components/layout/Layout';
const MyPage = () => (
<Layout>
<div className="max-w-4xl mx-auto px-4 py-12">
</div>
</Layout>
);
Sidebar
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:
| Prop | Type | Default | Description |
|---|---|---|---|
service | object | required | Service object |
isDisconnecting | boolean | false | Loading state |
onDisconnect | function | required | Disconnect handler |
ServiceCategory
Groups services by category with connect buttons.
Location: src/components/features/ServiceCategory.jsx
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
title | string | required | Category title |
services | array | required | Array of services |
isConnecting | string | - | Service currently connecting |
onConnect | function | required | Connect handler |
StepIndicator
Visual progress indicator for multi-step forms.
Location: src/components/features/StepIndicator.jsx
Props:
| Prop | Type | Default | Description |
|---|---|---|---|
currentStep | number | required | Current step (1-3) |
totalSteps | number | 3 | Total number of steps |
Usage:
<StepIndicator currentStep={2} />
Step1ActionSelection
Action selection step for workflow creation.
Location: src/components/features/Step1ActionSelection.jsx
Props:
| Prop | Type | Description |
|---|---|---|
availableServices | array | Services with actions |
selectedService | string | Currently selected service |
onServiceChange | function | Service change handler |
actions | array | Available actions for service |
selectedAction | object | Currently selected action |
onActionChange | function | Action change handler |
actionConfig | object | Action configuration |
onConfigChange | function | Config change handler |
onNext | function | Next step handler |
canProceed | boolean | Enable 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:
| Prop | Type | Description |
|---|---|---|
name | string | Workflow name |
onNameChange | function | Name change handler |
description | string | Workflow description |
onDescriptionChange | function | Description change handler |
selectedAction | object | Selected action (summary display) |
selectedReaction | object | Selected reaction (summary display) |
onPrev | function | Previous step handler |
onSubmit | function | Submit handler |
submitting | boolean | Loading 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 Type | Input Type |
|---|---|
string | text |
string with email format | email |
string with password format | password |
number | number |
boolean | checkbox |
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:
- Functional Components: Always use functional components with hooks
- Props Documentation: Define clear prop types
- Consistent Styling: Use Tailwind CSS utilities
- Error Handling: Handle loading and error states
- 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
| Page | Route | Auth | Description |
|---|---|---|---|
| LoginPage | /login | Public | User authentication |
| RegisterPage | /register | Public | New user registration |
| DashboardPage | /dashboard | Protected | Main dashboard with overview |
| ServicesPage | /services | Protected | Service connections management |
| WorkflowsPage | /workflows | Protected | List and manage workflows |
| CreateWorkflowPage | /workflows/create | Protected | Create 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:
- Connected Services: Shows services user has authenticated with
- 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:
- Backend completes OAuth with provider
- Redirects to
/oauth/callback?token=xxx(success) or?error=message(failure) - Page reads query parameters
- If token present: stores in localStorage, redirects to
/dashboard - 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
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| POST | /api/auth/register | Create new account | No |
| POST | /api/auth/login | Authenticate user | No |
| GET | /api/auth/me | Get current user | Yes |
Services
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/services | List all services | Yes |
| GET | /api/connections | List user connections | Yes |
| GET | /api/connections/{service} | Initiate OAuth | Yes |
| DELETE | /api/connections/{service} | Disconnect service | Yes |
AREAs (Workflows)
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /api/areas | List user AREAs | Yes |
| POST | /api/areas | Create new AREA | Yes |
| GET | /api/areas/{id} | Get AREA details | Yes |
| PUT | /api/areas/{id} | Update AREA | Yes |
| DELETE | /api/areas/{id} | Delete AREA | Yes |
| PATCH | /api/areas/{id}/toggle | Toggle active status | Yes |
| GET | /api/areas/{id}/logs | Get execution logs | Yes |
Public
| Method | Endpoint | Description | Auth Required |
|---|---|---|---|
| GET | /about.json | Server info | No |
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
| Status | Meaning | Frontend Action |
|---|---|---|
| 200 | Success | Process response |
| 201 | Created | Process response |
| 400 | Bad Request | Show validation error |
| 401 | Unauthorized | Redirect to login |
| 403 | Forbidden | Show permission error |
| 404 | Not Found | Show not found message |
| 500 | Server Error | Show generic error |
OAuth Integration
Connection Flow
- Frontend initiates: Call
connectService(serviceName) - Browser redirects: To
${API_URL}/api/connections/${service}?token=${jwt} - Backend handles OAuth: Redirects to provider, handles callback
- Backend redirects back: To
${FRONTEND_URL}/oauth/callback?token=${jwt}(success) or?error=message(failure) - Frontend processes: OAuthCallbackPage stores token if present, shows result, redirects to /dashboard or /login
Supported OAuth Services
| Service | Endpoint | Scopes |
|---|---|---|
| Gmail | /api/connections/gmail | email, profile, gmail.readonly |
| Drive | /api/connections/drive | email, profile, drive |
| GitHub | /api/connections/github | user, repo |
/api/connections/facebook | email, public_profile | |
| Spotify | /api/connections/spotify | user-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
| Package | Version | Purpose |
|---|---|---|
google_fonts | ^6.1.0 | Custom typography |
http | ^1.1.0 | API communication |
shared_preferences | ^2.2.2 | Local storage for auth tokens |
webview_flutter | ^4.4.2 | OAuth 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
- User enters credentials or initiates OAuth
- App receives JWT token
- Token stored in
SharedPreferences - 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
Using Docker (Recommended)
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
-Xmx4096minclient/Dockerfile.mobile.
APK Download
The APK can be downloaded from the web client:
- Navigate to
http://localhost:8081 - Click the download button or navigate to
/client.apk - 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:
- Replace
assets/area-icon.pngwith your icon (1024x1024 recommended) - Run
flutter pub run flutter_launcher_icons - 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
Related Documentation
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:
| Field | Type | Description |
|---|---|---|
message | string | Success message |
token | string | JWT authentication token (expires in 24 hours) |
user | object | User information |
user.id | integer | User ID |
user.username | string | Username |
user.email | string | Email address |
user.created_at | string | ISO 8601 timestamp of account creation |
user.updated_at | string | ISO 8601 timestamp of last update |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Missing required fields | username, email, or password not provided |
| 400 | Password must be at least 8 characters | Password too short |
| 400 | Password is too long | Password exceeds 128 characters |
| 400 | Password requires a lowercase, uppercase and special character | Password doesn’t meet complexity requirements |
| 409 | Username already exists | Username is taken |
| 409 | Email already exists | Email 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:
| Field | Type | Description |
|---|---|---|
message | string | Success message |
token | string | JWT authentication token (expires in 24 hours) |
user | object | User information |
user.id | integer | User ID |
user.username | string | Username |
user.email | string | Email address |
user.created_at | string | ISO 8601 timestamp of account creation |
user.updated_at | string | ISO 8601 timestamp of last update |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Missing required fields | Password not provided |
| 400 | Must provide username or email | Neither username nor email provided |
| 401 | Invalid credentials | Username/email or password incorrect |
| 401 | This account uses OAuth login | Account 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid authorization header format | Authorization header malformed |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 401 | User not found | User 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/gmailwhich 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 authenticationemail- User’s email addressprofile- User’s basic profile information (name, picture)https://www.googleapis.com/auth/gmail.readonly- Read-only access to Gmailhttps://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:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Authorization code from Google |
state | string | Yes | CSRF 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:
| Field | Type | Description |
|---|---|---|
message | string | Success message |
token | string | JWT authentication token (expires in 24 hours) |
user | object | User information |
user.id | integer | User ID |
user.username | string | Username (auto-generated from Google profile) |
user.email | string | Email address from Google account |
user.oauth_provider | string | OAuth provider name (“google”). Only present for OAuth-created accounts. |
user.created_at | string | ISO 8601 timestamp of account creation |
Note: Users created via OAuth login will have the
oauth_providerfield set. These accounts cannot use traditional password login and must use the OAuth flow.
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Failed to get user info from Google | OAuth token exchange failed |
| 400 | Missing required user information from Google | Google didn’t provide email or user ID |
| 400 | OAuth 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:
| Field | Type | Required | Description |
|---|---|---|---|
email | string | No | New 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:
| Status | Error | Description |
|---|---|---|
| 400 | No data provided | Request body is empty |
| 400 | Email cannot be empty | Email field is empty string |
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 409 | Email already in use | Email 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Admin account cannot be deleted | Cannot 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/facebookwhich 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 addresspublic_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:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Authorization code from Facebook |
state | string | Yes | CSRF 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:
| Field | Type | Description |
|---|---|---|
token | string | JWT authentication token (in URL parameter) |
success | string | “true” on successful authentication |
service | string | “facebook” |
Note: Users created via OAuth login will have the
oauth_providerfield 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:
| Status | Error | Description |
|---|---|---|
| 400 | Email is required | No email provided in request |
| 500 | Failed to send reset email | SMTP 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:
| Field | Type | Required | Description |
|---|---|---|---|
token | string | Yes | Reset token from email |
password | string | Yes | New 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:
| Status | Error | Description |
|---|---|---|
| 400 | Token and password are required | Missing required fields |
| 400 | Password must be at least 8 characters | Password too short |
| 400 | Password is too long | Password exceeds 128 characters |
| 400 | Password requires a lowercase, uppercase and special character | Password doesn’t meet complexity |
| 400 | Invalid or expired reset token | Token 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!"
}'
Related Documentation
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:
| Field | Type | Description |
|---|---|---|
status | string | Overall health status (“healthy” or “unhealthy”) |
database | string | Database connection status (“connected” or error message) |
scheduler | string | Scheduler status (“enabled”, “disabled”, or error message) |
services.scheduler_interval | string | How often the scheduler checks for workflow triggers |
services.active_count | integer | Number 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:
| Field | Type | Description |
|---|---|---|
client.host | string | IP address of the client making the request |
server.current_time | integer | Current server time in Unix epoch timestamp format |
server.services | array | List of all active services available on the server |
server.services[].name | string | Internal identifier for the service (e.g., “timer”, “gmail”) |
server.services[].display_name | string | Human-readable service name (e.g., “Timer”, “Gmail”) |
server.services[].description | string | Brief description of what the service does |
server.services[].requires_oauth | boolean | Whether the service requires OAuth2 authentication |
server.services[].icon_url | string/null | URL to the service icon image |
server.services[].actions | array | List of action triggers available for this service |
server.services[].actions[].name | string | Internal identifier for the action |
server.services[].actions[].description | string | Description of when this action triggers |
server.services[].reactions | array | List of reactions available for this service |
server.services[].reactions[].name | string | Internal identifier for the reaction |
server.services[].reactions[].description | string | Description 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:
| Field | Type | Description |
|---|---|---|
services | array | List of all active services |
services[].name | string | Internal identifier for the service |
services[].display_name | string | Human-readable service name |
services[].description | string | Brief description of the service |
services[].requires_oauth | boolean | Whether OAuth2 authentication is required |
services[].icon_url | string/null | URL to service icon |
services[].actions | array | Available action triggers for this service |
services[].actions[].id | integer | Database ID of the action |
services[].actions[].name | string | Internal action identifier |
services[].actions[].display_name | string | Human-readable action name |
services[].actions[].description | string | Description of when this action triggers |
services[].actions[].config_schema | object | JSON schema for action configuration |
services[].reactions | array | Available reactions for this service |
services[].reactions[].id | integer | Database ID of the reaction |
services[].reactions[].name | string | Internal reaction identifier |
services[].reactions[].display_name | string | Human-readable reaction name |
services[].reactions[].description | string | Description of what this reaction does |
services[].reactions[].config_schema | object | JSON 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token 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:
| Field | Type | Description |
|---|---|---|
connections | array | List of all OAuth-enabled services with connection status |
connections[].service_id | integer | Database ID of the service |
connections[].service_name | string | Internal service identifier |
connections[].display_name | string | Human-readable service name |
connections[].description | string | Brief service description |
connections[].is_connected | boolean | Whether user has connected this service |
connections[].connected_at | string/null | ISO 8601 timestamp of when service was connected, null if not connected |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token 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:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Name of the workflow |
action_id | integer | Yes | ID of the action trigger |
reaction_id | integer | Yes | ID of the reaction to execute |
action_config | object | Yes | Configuration for the action (varies by action type) |
reaction_config | object | Yes | Configuration for the reaction (varies by reaction type) |
is_active | boolean | No | Whether 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:
| Status | Error | Description |
|---|---|---|
| 400 | Missing required field: <field> | Required field not provided |
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 404 | Action not found | action_id doesn’t exist |
| 404 | Reaction not found | reaction_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:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | integer | No | 1 | Page number for pagination |
per_page | integer | No | 20 | Number of items per page |
is_active | string | No | - | 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:
| Field | Type | Description |
|---|---|---|
areas | array | List of workflows for the current page |
total | integer | Total number of workflows |
page | integer | Current page number |
per_page | integer | Number of items per page |
total_pages | integer | Total number of pages |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
area_id | integer | Yes | ID 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Unauthorized access to this workflow | User doesn’t own this workflow |
| 404 | Workflow not found | Workflow 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
area_id | integer | Yes | ID of the workflow to update |
Request Body:
{
"name": "string",
"action_config": {
"key": "value"
},
"reaction_config": {
"key": "value"
},
"is_active": boolean
}
Request Fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | New name for the workflow |
action_config | object | No | New configuration for the action |
reaction_config | object | No | New configuration for the reaction |
is_active | boolean | No | Whether 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Unauthorized access to this workflow | User doesn’t own this workflow |
| 404 | Workflow not found | Workflow 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
area_id | integer | Yes | ID of the workflow to update |
Request Body:
{
"name": "string",
"is_active": boolean,
"action_config": {
"key": "value"
},
"reaction_config": {
"key": "value"
}
}
Request Fields:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | No | New name for the workflow |
is_active | boolean | No | Whether the workflow is active |
action_config | object | No | New configuration for the action |
reaction_config | object | No | New 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Unauthorized access to this workflow | User doesn’t own this workflow (and is not admin) |
| 404 | Workflow not found | Workflow 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
area_id | integer | Yes | ID 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:
| Field | Type | Description |
|---|---|---|
message | string | Success message indicating if workflow was enabled or disabled |
area | object | Complete updated workflow object with toggled is_active status |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Unauthorized access to this workflow | User doesn’t own this workflow |
| 404 | Workflow not found | Workflow 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
area_id | integer | Yes | ID of the workflow to delete |
Request Body: None required
Success Response (200 OK):
{
"message": "Workflow deleted successfully"
}
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Unauthorized access to this workflow | User doesn’t own this workflow |
| 404 | Workflow not found | Workflow 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:
| Field | Type | Description |
|---|---|---|
message | string | Success message with count of deleted workflows |
deleted_count | integer | Number of workflows that were deleted |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
area_id | integer | Yes | ID of the workflow to get logs for |
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
page | integer | No | 1 | Page number for pagination |
per_page | integer | No | 50 | Number 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:
| Field | Type | Description |
|---|---|---|
logs | array | List of execution logs for the current page |
logs[].id | integer | Log entry ID |
logs[].area_id | integer | Workflow ID |
logs[].area_name | string | Name of the workflow |
logs[].status | string | Execution status: “success”, “failed”, or “error” |
logs[].message | string | Execution message or error description |
logs[].triggered_at | string | ISO 8601 timestamp of when workflow was triggered |
logs[].execution_time_ms | integer | Execution time in milliseconds |
total | integer | Total number of log entries |
page | integer | Current page number |
per_page | integer | Number of items per page |
total_pages | integer | Total number of pages |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Unauthorized access to this workflow | User doesn’t own this workflow |
| 404 | Workflow not found | Workflow 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:
| Field | Type | Description |
|---|---|---|
users | array | List of all users in the system |
users[].id | integer | User database ID |
users[].username | string | User’s username |
users[].email | string | User’s email address |
users[].oauth_provider | string/null | OAuth provider if user registered via OAuth (“google”, “facebook”, or null) |
users[].created_at | string | ISO 8601 timestamp of account creation |
users[].workflow_count | integer | Number of workflows this user has |
users[].workflows | array | List of user’s workflows with full details |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 403 | Admin access required | User is not the admin |
Example:
curl -X GET http://localhost:8080/api/admin/users \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Related Documentation
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:
| Field | Type | Description |
|---|---|---|
connections | array | List of all OAuth-enabled services |
service_id | integer | Service database ID |
service_name | string | Internal service name |
display_name | string | Display name of the service |
description | string | Service description |
is_connected | boolean | Whether user has connected this service |
connected_at | string | ISO 8601 timestamp of connection (null if not connected) |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token 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:
| Field | Type | Description |
|---|---|---|
message | string | Success message with count of disconnected services |
disconnected_count | integer | Number of services that were disconnected |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Yes (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 authenticationemail- User’s email addressprofile- User’s basic profile informationhttps://www.googleapis.com/auth/gmail.readonly- Read-only access to Gmailhttps://www.googleapis.com/auth/drive- Access to Google Drive
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authentication required | No token provided in header or URL |
| 401 | Invalid token | Token is malformed or invalid |
| 401 | Invalid token payload | Token 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Authorization code from Google |
state | string | Yes | CSRF 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:
| Field | Type | Description |
|---|---|---|
success | boolean | Always true on successful connection |
message | string | Success message |
connections.gmail | object | Gmail connection details |
connections.drive | object | Drive connection details (same OAuth token) |
*.id | integer | Connection database ID |
*.user_id | integer | User ID |
*.service_id | integer | Service ID |
*.service_name | string | Internal service name |
*.service_display_name | string | Display name of the service |
*.connected_at | string | ISO 8601 timestamp of initial connection |
*.updated_at | string | ISO 8601 timestamp of last token update |
*.token_expires_at | string | ISO 8601 timestamp of when access token expires |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Invalid session | Session expired or user_id not found in session |
| 404 | Gmail service not found | Gmail service not configured in database |
| 500 | OAuth 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 404 | Gmail service not found | Gmail service not configured in database |
| 404 | Gmail not connected | User 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Yes (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 addresspublic_profile- User’s basic profile information (name, picture)user_posts- Access to user’s posts
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authentication required | No token provided in header or URL |
| 401 | Invalid token | Token is malformed or invalid |
| 401 | Invalid token payload | Token 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Authorization code from Facebook |
state | string | Yes | CSRF 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:
| Field | Type | Description |
|---|---|---|
success | boolean | Always true on successful connection |
message | string | Success message |
connection | object | Connection details |
connection.id | integer | Connection database ID |
connection.user_id | integer | User ID |
connection.service_id | integer | Service ID |
connection.service_name | string | Internal service name |
connection.service_display_name | string | Display name of the service |
connection.connected_at | string | ISO 8601 timestamp of initial connection |
connection.updated_at | string | ISO 8601 timestamp of last token update |
connection.token_expires_at | string | ISO 8601 timestamp (typically ~60 days) |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Invalid session | Session expired or user_id not found in session |
| 404 | Facebook service not found | Facebook service not configured in database |
| 500 | OAuth 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 404 | Facebook service not found | Facebook service not configured in database |
| 404 | Facebook not connected | User 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Yes (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 repositoriesread:user- Read user profile datauser:email- Access to user’s email addresses
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authentication required | No token provided in header or URL |
| 401 | Invalid token | Token is malformed or invalid |
| 401 | Invalid token payload | Token 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Authorization code from GitHub |
state | string | Yes | CSRF 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:
| Field | Type | Description |
|---|---|---|
success | boolean | Always true on successful connection |
message | string | Success message |
connection | object | Connection details |
connection.id | integer | Connection database ID |
connection.user_id | integer | User ID |
connection.service_id | integer | Service ID |
connection.service_name | string | Internal service name |
connection.service_display_name | string | Display name of the service |
connection.connected_at | string | ISO 8601 timestamp of initial connection |
connection.updated_at | string | ISO 8601 timestamp of last token update |
connection.token_expires_at | string | ISO 8601 timestamp (GitHub tokens typically don’t expire) |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | Invalid session | Session expired or user_id not found in session |
| 404 | GitHub service not found | GitHub service not configured in database |
| 500 | OAuth 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 404 | GitHub service not found | GitHub service not configured in database |
| 404 | GitHub not connected | User 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
token | string | Yes (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 stateuser-modify-playback-state- Control playbackplaylist-read-private- Read private playlistsplaylist-modify-public- Modify public playlistsplaylist-modify-private- Modify private playlistsuser-library-read- Read saved tracksuser-library-modify- Modify saved tracks
Error Responses:
| Status | Error | Description |
|---|---|---|
| 401 | Authentication required | No token provided in header or URL |
| 401 | Invalid token | Token is malformed or invalid |
| 401 | Invalid token payload | Token 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:
| Parameter | Type | Required | Description |
|---|---|---|---|
code | string | Yes | Authorization code from Spotify |
state | string | Yes | CSRF protection token |
error | string | No | Error 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:
| Field | Type | Description |
|---|---|---|
success | boolean | Always true on successful connection |
message | string | Success message |
connection | object | Connection details |
connection.id | integer | Connection database ID |
connection.user_id | integer | User ID |
connection.service_id | integer | Service ID |
connection.service_name | string | Internal service name |
connection.service_display_name | string | Display name of the service |
connection.connected_at | string | ISO 8601 timestamp of initial connection |
connection.updated_at | string | ISO 8601 timestamp of last token update |
connection.token_expires_at | string | ISO 8601 timestamp (typically 1 hour) |
Error Responses:
| Status | Error | Description |
|---|---|---|
| 400 | OAuth error: <error> | User denied authorization or other OAuth error |
| 400 | Missing authorization code or state | Required parameters not provided |
| 400 | Invalid or expired OAuth state | State token is invalid or expired |
| 404 | Spotify service not found | Spotify service not configured in database |
| 500 | Token exchange failed: <details> | Failed to exchange code for access token |
| 500 | OAuth 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:
| Status | Error | Description |
|---|---|---|
| 401 | Authorization token is missing | No Authorization header provided |
| 401 | Invalid or expired token | Token is invalid or has expired |
| 404 | Spotify service not found | Spotify service not configured in database |
| 404 | Spotify not connected | User hasn’t connected Spotify yet |
Example:
curl -X DELETE http://localhost:8080/api/connections/spotify \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Related Documentation
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
- Navigate to
/api/connections/gmailor use the frontend connection interface - Authorize AREA to access your Google account
- OAuth scopes granted:
openid,email,profile- Basic authenticationhttps://www.googleapis.com/auth/gmail.readonly- Read Gmailhttps://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:
| Field | Type | Required | Description |
|---|---|---|---|
sender | string | Yes | Email 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:
| Field | Type | Required | Description |
|---|---|---|---|
keyword | string | Yes | Keyword 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:
| Field | Type | Required | Description |
|---|---|---|---|
folder_name | string | Yes | Name 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:
| Field | Type | Required | Description |
|---|---|---|---|
file_name | string | Yes | Name of the file to create |
content | string | Yes | Text content of the file |
folder_name | string | No | Folder 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:
| Field | Type | Required | Description |
|---|---|---|---|
folder_name | string | Yes | Name 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:
| Field | Type | Required | Description |
|---|---|---|---|
file_name | string | Yes | Name of the file to share |
email | string | Yes | Email address to share with (valid email format) |
role | string | Yes | Permission 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
roleis eitherreaderorwriter
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
- Navigate to
/api/connections/facebookor use the frontend connection interface - Authorize AREA to access your Facebook account
- OAuth scopes granted:
email- User’s email addresspublic_profile- Basic profile informationuser_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:
| Field | Type | Required | Description |
|---|---|---|---|
keyword | string | Yes | Keyword 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:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | Content 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_postspermission 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_postspermission 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
- Navigate to
/api/connections/githubor use the frontend connection interface - Authorize AREA to access your GitHub account
- OAuth scopes granted:
repo- Full control of private and public repositoriesread:user- Read user profile datauser: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:
| Field | Type | Required | Description |
|---|---|---|---|
repo_name | string | Yes | Full 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:
| Field | Type | Required | Description |
|---|---|---|---|
repo_name | string | Yes | Full 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:
| Field | Type | Required | Description |
|---|---|---|---|
repo_name | string | Yes | Full 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:
| Field | Type | Required | Description |
|---|---|---|---|
repo_name | string | Yes | Full repository name in format username/repo |
title | string | Yes | Issue title (max 200 characters) |
body | string | Yes | Issue 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
reposcope 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
reposcope 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
reposcope
Repository Not Found:
- Double-check spelling of repository name
- Ensure you have access to the repository
- For organization repos, use
organization/repoformat - Private repos require
repoOAuth 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
- Navigate to
/api/connections/spotifyor use the frontend connection interface - Authorize AREA to access your Spotify account
- OAuth scopes granted:
user-read-playback-state- Read current playback stateuser-modify-playback-state- Control playbackplaylist-read-private- Read private playlistsplaylist-modify-public- Modify public playlistsplaylist-modify-private- Modify private playlistsuser-library-read- Read saved tracksuser-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:
| Field | Type | Required | Description |
|---|---|---|---|
playlist_id | string | Yes | Spotify 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:
| Field | Type | Required | Description |
|---|---|---|---|
playlist_id | string | Yes | Spotify playlist ID |
track_uri | string | Yes | Spotify 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:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Playlist name (max 100 characters) |
description | string | No | Playlist description (max 300 characters) |
public | boolean | No | Make 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:
| Field | Type | Required | Description |
|---|---|---|---|
track_uri | string | No | Spotify track URI to play |
context_uri | string | No | Spotify 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:IDor 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:
- Open Spotify on any device (phone, computer, web player, smart speaker)
- Start playing any track (can pause it immediately)
- Device is now “active” and can receive playback commands
- Device must remain connected and logged in
URI Formats
AREA accepts multiple formats for track URIs:
spotify:track:6rqhFgbbKwnb9MLmUQDhG66rqhFgbbKwnb9MLmUQDhG6https://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)
OAuth Consent Screen
- 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_providerandoauth_provider_idfound - 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_providerandoauth_provider_idfields - 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_hashto 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=*