README.md

# Flowfull Elixir Client

[![Hex.pm](https://img.shields.io/hexpm/v/flowfull.svg)](https://hex.pm/packages/flowfull)
[![Elixir Version](https://img.shields.io/badge/Elixir-1.14+-purple.svg)](https://elixir-lang.org/)
[![License](https://img.shields.io/badge/License-AGPL--3.0-blue.svg)](LICENSE)

A zero-dependency Elixir client library for Flowfull backends with built-in authentication, session management, and query building. Includes Phoenix integration for WebSockets and LiveView.

> **Professional, type-safe, and production-ready Elixir client for Flowfull API backends.**

## Features

- ✅ **Minimal Dependencies** - Only Req and Jason
- ✅ **Type-Safe** - Full typespec coverage
- ✅ **Session Management** - Auto-detection with pluggable storage
- ✅ **Query Builder** - Chainable API with 14+ filter operators
- ✅ **Authentication** - Complete auth system (25+ endpoints)
- ✅ **Retry Logic** - Configurable retries with exponential backoff
- ✅ **Interceptors** - Request/response middleware
- ✅ **Phoenix Integration** - WebSocket and LiveView support
- ✅ **Multi-Tenant** - Built-in multi-tenant support
- ✅ **File Upload** - Multipart form data support

## Installation

Add `flowfull` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:flowfull, "~> 0.1.0"}
  ]
end
```

## Quick Start

```elixir
# Create a client
client = Flowfull.new("https://api.example.com")

# Make a simple GET request
{:ok, response} = Flowfull.get(client, "/users")

# Use query builder
{:ok, users} = 
  Flowfull.query(client, "/users")
  |> Flowfull.Query.where("age", Flowfull.Operators.gte(18))
  |> Flowfull.Query.where("status", Flowfull.Operators.eq("active"))
  |> Flowfull.Query.sort("created_at", :desc)
  |> Flowfull.Query.page(1)
  |> Flowfull.Query.limit(10)
  |> Flowfull.Query.execute()

# Authentication
{:ok, result} = Flowfull.Auth.login(client, %{
  email: "user@example.com",
  password: "password123"
})

# Get current user
{:ok, user} = Flowfull.Auth.me(client)
```

## Configuration

```elixir
# With options
client = Flowfull.new("https://api.example.com",
  session_id: "abc123",
  timeout: 60_000,
  headers: %{"X-Custom" => "value"},
  retry_attempts: 5,
  retry_exponential: true,
  storage: Flowfull.Storage.File.new(".sessions")
)

# With session function
client = Flowfull.new("https://api.example.com",
  get_session_id: fn -> 
    {:ok, System.get_env("SESSION_ID")}
  end
)

# Include session in all requests
client = Flowfull.new("https://api.example.com",
  include_session: true,
  session_header: "X-Session-Id"
)
```

## HTTP Methods

```elixir
# GET
{:ok, response} = Flowfull.get(client, "/users")

# POST
{:ok, response} = Flowfull.post(client, "/users", %{
  name: "John Doe",
  email: "john@example.com"
})

# PUT
{:ok, response} = Flowfull.put(client, "/users/123", %{
  name: "Jane Doe"
})

# PATCH
{:ok, response} = Flowfull.patch(client, "/users/123", %{
  email: "jane@example.com"
})

# DELETE
{:ok, response} = Flowfull.delete(client, "/users/123")
```

## Query Builder

```elixir
# Complex query with multiple filters
{:ok, products} = 
  Flowfull.query(client, "/products")
  |> Flowfull.Query.where("price", Flowfull.Operators.between(10, 100))
  |> Flowfull.Query.where("category", Flowfull.Operators.in_list(["electronics", "books"]))
  |> Flowfull.Query.where("name", Flowfull.Operators.ilike("%laptop%"))
  |> Flowfull.Query.where("stock", Flowfull.Operators.gt(0))
  |> Flowfull.Query.sort("price", :asc)
  |> Flowfull.Query.sort("created_at", :desc)
  |> Flowfull.Query.page(1)
  |> Flowfull.Query.limit(20)
  |> Flowfull.Query.select(["id", "name", "price"])
  |> Flowfull.Query.execute()
```

## Filter Operators

| Operator | Function | Example | SQL Equivalent |
|----------|----------|---------|----------------|
| Equality | `eq(value)` | `where("age", eq(25))` | `age = 25` |
| Not Equal | `ne(value)` | `where("status", ne("inactive"))` | `status != 'inactive'` |
| Greater Than | `gt(value)` | `where("price", gt(100))` | `price > 100` |
| Greater or Equal | `gte(value)` | `where("age", gte(18))` | `age >= 18` |
| Less Than | `lt(value)` | `where("stock", lt(10))` | `stock < 10` |
| Less or Equal | `lte(value)` | `where("price", lte(50))` | `price <= 50` |
| LIKE | `like(pattern)` | `where("name", like("%John%"))` | `name LIKE '%John%'` |
| ILIKE | `ilike(pattern)` | `where("email", ilike("%@gmail.com"))` | `email ILIKE '%@gmail.com'` |
| Contains | `contains(value)` | `where("description", contains("laptop"))` | `description LIKE '%laptop%'` |
| Starts With | `starts_with(prefix)` | `where("name", starts_with("A"))` | `name LIKE 'A%'` |
| Ends With | `ends_with(suffix)` | `where("email", ends_with("@example.com"))` | `email LIKE '%@example.com'` |
| IN | `in_list(values)` | `where("status", in_list(["active", "pending"]))` | `status IN ('active', 'pending')` |
| NOT IN | `not_in(values)` | `where("role", not_in(["admin", "super"]))` | `role NOT IN ('admin', 'super')` |
| IS NULL | `is_null()` | `where("deleted_at", is_null())` | `deleted_at IS NULL` |
| NOT NULL | `not_null()` | `where("email", not_null())` | `email IS NOT NULL` |
| BETWEEN | `between(min, max)` | `where("age", between(18, 65))` | `age BETWEEN 18 AND 65` |
| NOT BETWEEN | `not_between(min, max)` | `where("price", not_between(0, 10))` | `price NOT BETWEEN 0 AND 10` |

## Authentication

### Login & Registration

```elixir
# Login with email
{:ok, result} = Flowfull.Auth.login(client, %{
  email: "user@example.com",
  password: "password123"
})

# Login with username
{:ok, result} = Flowfull.Auth.login(client, %{
  user_name: "johndoe",
  password: "password123"
})

# Register new user
{:ok, result} = Flowfull.Auth.register(client, %{
  email: "user@example.com",
  password: "password123",
  name: "John",
  last_name: "Doe"
})

# Logout
{:ok, _} = Flowfull.Auth.logout(client)

# Get current user
{:ok, user} = Flowfull.Auth.me(client)

# Update profile
{:ok, user} = Flowfull.Auth.update_profile(client, %{
  name: "Jane",
  phone: "+1234567890"
})
```

### Password Reset

```elixir
# Request password reset
{:ok, _} = Flowfull.Auth.Password.request_reset(client, %{
  email: "user@example.com",
  reset_url: "https://app.example.com/reset-password"
})

# Validate reset token
{:ok, result} = Flowfull.Auth.Password.validate_token(client, token)

# Complete password reset
{:ok, _} = Flowfull.Auth.Password.complete_reset(client, %{
  token: token,
  password: "newpassword123"
})

# Change password (authenticated)
{:ok, _} = Flowfull.Auth.Password.change_password(client, %{
  old_password: "oldpassword",
  new_password: "newpassword"
})
```

### Token Authentication

```elixir
# Create login token
{:ok, result} = Flowfull.Auth.Token.create(client, %{
  email: "user@example.com",
  token_url: "https://app.example.com/login"
})

# Validate token
{:ok, result} = Flowfull.Auth.Token.validate(client, token)

# Login with token
{:ok, result} = Flowfull.Auth.Token.login(client, token)
```

### Social OAuth

```elixir
# Get available providers
{:ok, providers} = Flowfull.Auth.Social.get_providers(client)

# Start OAuth flow
{:ok, auth_url} = Flowfull.Auth.Social.start_oauth(
  client,
  :google,
  "https://app.example.com/callback"
)

# Login with OAuth (after callback)
{:ok, result} = Flowfull.Auth.Social.login_api(client, %{
  provider: "google",
  code: oauth_code
})

# Get linked accounts
{:ok, accounts} = Flowfull.Auth.Social.get_accounts(client)

# Unlink account
{:ok, _} = Flowfull.Auth.Social.unlink(client, :google)
```

## Phoenix Integration

### WebSocket Authentication

```elixir
# In your UserSocket module
defmodule MyAppWeb.UserSocket do
  use Phoenix.Socket

  def connect(%{"session_id" => session_id}, socket, _connect_info) do
    client = Flowfull.new(Application.get_env(:my_app, :flowfull_url))

    case Flowfull.Phoenix.validate_socket_session(client, session_id) do
      {:ok, user} ->
        {:ok, assign(socket, :user, user)}

      {:error, _reason} ->
        :error
    end
  end

  def connect(_params, _socket, _connect_info), do: :error
end
```

### LiveView Authentication

```elixir
# In your LiveView module
defmodule MyAppWeb.DashboardLive do
  use MyAppWeb, :live_view

  def mount(_params, %{"session_id" => session_id}, socket) do
    client = Flowfull.new(Application.get_env(:my_app, :flowfull_url))

    case Flowfull.Phoenix.validate_liveview_session(client, session_id) do
      {:ok, user} ->
        {:ok, assign(socket, :current_user, user)}

      {:error, _reason} ->
        {:ok, redirect(socket, to: "/login")}
    end
  end
end
```

### Controller Authentication Plug

```elixir
# In your router
pipeline :authenticated do
  plug Flowfull.Phoenix.SessionPlug,
    client: fn -> Flowfull.new(Application.get_env(:my_app, :flowfull_url)) end
end

scope "/dashboard", MyAppWeb do
  pipe_through [:browser, :authenticated]

  get "/", DashboardController, :index
end
```

## Storage Adapters

### Memory Storage (Agent-based)

```elixir
# Start the memory storage
{:ok, _} = Flowfull.Storage.Memory.start_link()

# Use with client
client = Flowfull.new("https://api.example.com",
  storage: Flowfull.Storage.Memory
)
```

### File Storage

```elixir
# Use file storage
client = Flowfull.new("https://api.example.com",
  storage: Flowfull.Storage.File.new(".sessions")
)
```

### ETS Storage (High Performance)

```elixir
# Initialize ETS storage
Flowfull.Storage.ETS.init()

# Use with client
client = Flowfull.new("https://api.example.com",
  storage: Flowfull.Storage.ETS
)
```

## Interceptors

```elixir
# Add request interceptor
client = Flowfull.add_request_interceptor(client, fn request ->
  IO.inspect(request, label: "Request")
  {:ok, request}
end)

# Add response interceptor
client = Flowfull.add_response_interceptor(client, fn response ->
  IO.inspect(response, label: "Response")
  {:ok, response}
end)
```

## Examples

See the [examples](./examples) directory for complete examples:

- [Basic Usage](./examples/basic.exs) - Simple HTTP requests
- [Query Builder](./examples/query.exs) - Advanced queries
- [Authentication](./examples/auth.exs) - Complete auth flow
- [Phoenix WebSocket](./examples/phoenix_socket.exs) - WebSocket integration
- [Phoenix LiveView](./examples/phoenix_liveview.exs) - LiveView integration

## Documentation

- [Quick Start Guide](QUICKSTART.md) - Get started in 5 minutes
- [API Documentation](https://hexdocs.pm/flowfull) - Complete API reference
- [Phoenix Integration Guide](docs/PHOENIX.md) - Phoenix-specific features

## Contributing

Contributions are welcome! Please read our [Contributing Guide](CONTRIBUTING.md) for details.

## License

**Flowfull Elixir Client** is licensed under the **AGPL-3.0-only** license.

### What does this mean?

- ✅ **Free to use** - Use commercially without paying anything
- ✅ **Modify freely** - Change the code as needed
- ✅ **Distribute** - Share with others
- ⚠️ **Share modifications** - If you modify and offer as a web service, you must release your changes
- ⚠️ **Same license** - Derivative works must use AGPL-3.0

### Commercial License Available

For organizations that cannot comply with AGPL-3.0 or need:
- 💼 Keep modifications private
- 🛡️ Legal indemnification
- 🎯 Enterprise support and SLA
- 🚀 Custom features

**Contact:** enterprise@pubflow.com
**Learn more:** https://pubflow.com/dual-licensing

See [LICENSE](LICENSE) for full details.

## Support

- 📖 [Documentation](https://hexdocs.pm/flowfull)
- 💬 [Issues](https://github.com/pubflow/flowfull-elixir/issues)
- 🌟 [GitHub](https://github.com/pubflow/flowfull-elixir)
- 💼 [Commercial License](https://pubflow.com/dual-licensing)

---

**Copyright © 2024-present Pubflow, Inc.**
**SPDX-License-Identifier:** AGPL-3.0-only

Made with ❤️ by the Pubflow team