Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add a POC Notion FDW with users read only #264

Merged
merged 4 commits into from
May 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,21 @@

`Wrappers` is also a collection of FDWs built by [Supabase](https://www.supabase.com). We currently support the following FDWs, with more under development:

| FDW | Description | Read | Modify |
| ----------------------------------------------- | -------------------------------------------------------------- | ---- | ------ |
| [HelloWorld](./wrappers/src/fdw/helloworld_fdw) | A demo FDW to show how to develop a basic FDW. | | |
| [BigQuery](./wrappers/src/fdw/bigquery_fdw) | A FDW for Google [BigQuery](https://cloud.google.com/bigquery) | ✅ | ✅ |
| [Clickhouse](./wrappers/src/fdw/clickhouse_fdw) | A FDW for [ClickHouse](https://clickhouse.com/) | ✅ | ✅ |
| [Stripe](./wrappers/src/fdw/stripe_fdw) | A FDW for [Stripe](https://stripe.com/) API | ✅ | ✅ |
| [Firebase](./wrappers/src/fdw/firebase_fdw) | A FDW for Google [Firebase](https://firebase.google.com/) | ✅ | ❌ |
| [Airtable](./wrappers/src/fdw/airtable_fdw) | A FDW for [Airtable](https://airtable.com/) API | ✅ | ❌ |
| [S3](./wrappers/src/fdw/s3_fdw) | A FDW for [AWS S3](https://aws.amazon.com/s3/) | ✅ | ❌ |
| [Logflare](./wrappers/src/fdw/logflare_fdw) | A FDW for [Logflare](https://logflare.app/) | ✅ | ❌ |
| [Auth0](./wrappers/src/fdw/auth0_fdw) | A FDW for [Auth0](https://auth0.com/) | ✅ | ❌ |
| FDW | Description | Read | Modify |
| ----------------------------------------------- | ----------------------------------------------------------------------------- | ---- | ------ |
| [HelloWorld](./wrappers/src/fdw/helloworld_fdw) | A demo FDW to show how to develop a basic FDW. | | |
| [BigQuery](./wrappers/src/fdw/bigquery_fdw) | A FDW for Google [BigQuery](https://cloud.google.com/bigquery) | ✅ | ✅ |
| [Clickhouse](./wrappers/src/fdw/clickhouse_fdw) | A FDW for [ClickHouse](https://clickhouse.com/) | ✅ | ✅ |
| [Stripe](./wrappers/src/fdw/stripe_fdw) | A FDW for [Stripe](https://stripe.com/) API | ✅ | ✅ |
| [Firebase](./wrappers/src/fdw/firebase_fdw) | A FDW for Google [Firebase](https://firebase.google.com/) | ✅ | ❌ |
| [Airtable](./wrappers/src/fdw/airtable_fdw) | A FDW for [Airtable](https://airtable.com/) API | ✅ | ❌ |
| [S3](./wrappers/src/fdw/s3_fdw) | A FDW for [AWS S3](https://aws.amazon.com/s3/) | ✅ | ❌ |
| [Logflare](./wrappers/src/fdw/logflare_fdw) | A FDW for [Logflare](https://logflare.app/) | ✅ | ❌ |
| [Auth0](./wrappers/src/fdw/auth0_fdw) | A FDW for [Auth0](https://auth0.com/) | ✅ | ❌ |
| [SQL Server](./wrappers/src/fdw/mssql_fdw) | A FDW for [Microsoft SQL Server](https://www.microsoft.com/en-au/sql-server/) | ✅ | ❌ |
| [Redis](./wrappers/src/fdw/redis_fdw) | A FDW for [Redis](https://redis.io/) | ✅ | ❌ |
| [AWS Cognito](./wrappers/src/fdw/cognito_fdw) | A FDW for [AWS Cognito](https://aws.amazon.com/cognito/) | ✅ | ❌ |
| [Redis](./wrappers/src/fdw/redis_fdw) | A FDW for [Redis](https://redis.io/) | ✅ | ❌ |
| [AWS Cognito](./wrappers/src/fdw/cognito_fdw) | A FDW for [AWS Cognito](https://aws.amazon.com/cognito/) | ✅ | ❌ |
| [Notion](./wrappers/src/fdw/notion_fdw) | A FDW for [Notion](https://www.notion.so/) | ✅ | ❌ |

## Features

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Currently `supabase/wrappers` supports:
| ClickHouse | ✅ | ✅ | ✅ | ✅ | ❌ |
| Firebase | ✅ | ❌ | ❌ | ❌ | ❌ |
| Logflare | ✅ | ❌ | ❌ | ❌ | ❌ |
| Notion | ✅ | ❌ | ❌ | ❌ | ❌ |
| Redis | ✅ | ❌ | ❌ | ❌ | ❌ |
| S3 | ✅ | ❌ | ❌ | ❌ | ❌ |
| Stripe | ✅ | ✅ | ✅ | ✅ | ❌ |
Expand Down
133 changes: 133 additions & 0 deletions docs/notion.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# [Notion](https://notion.so/) provides a versatile, ready-to-use solution for managing your data.


The Notion Wrapper allows you to read data from your Notion workspace for use within your Postgres database. Only the users endpoint is supported at the moment.

## Preparation

Before you get started, make sure the `wrappers` extension is installed on your database:

```sql
CREATE extension IF NOT EXISTS wrappers SCHEMA extensions;
```

Then, create the foreign data wrapper:

```sql
CREATE FOREIGN DATA WRAPPER notion_wrapper
HANDLER notion_fdw_handler
VALIDATOR notion_fdw_validator;
```

### Secure your credentials (optional)

By default, Postgres stores FDW credentials in plain text within the `pg_catalog.pg_foreign_server` table, making them visible to anyone with access to this table. To enhance security, it is advisable to use [Vault](https://supabase.com/docs/guides/database/vault) for credential storage. Vault integrates seamlessly with Wrappers to provide a secure storage solution for sensitive information. We strongly recommend utilizing Vault to safeguard your credentials.

```sql
-- Save your Notion API key in Vault and retrieve the `key_id`
INSERT INTO vault.secrets (name, secret)
VALUES (
'notion',
'<Notion API Key>' -- Notion API key
)
RETURNING key_id;
```

### Connecting to Notion

We need to provide Postgres with the credentials to connect to Notion, and any additional options. We can do this using the `CREATE SERVER` command:

- With Vault (recommended)

```sql
CREATE SERVER notion_server
FOREIGN DATA WRAPPER notion_wrapper
OPTIONS (
api_key_id '<key_ID>', -- The Key ID from the Vault
notion_version '<notion_version>', -- optional, default is '2022-06-28'
api_url '<api_url>' -- optional, default is 'https://api.notion.com/v1/'
);
```

- Without Vault

```sql
CREATE SERVER notion_server
FOREIGN DATA WRAPPER notion_wrapper
OPTIONS (
api_key '<your_api_key>', -- Your Notion API key
notion_version '<notion_version>', -- optional, default is '2022-06-28'
api_url '<api_url>' -- optional, default is 'https://api.notion.com/v1/'
);
```

## Creating Foreign Tables

The Notion Wrapper supports data reads from the [Notion API](https://developers.notion.com/reference).

| Object | Select | Insert | Update | Delete | Truncate |
| ---------------------------------------------------------- | :----: | :----: | :----: | :----: | :------: |
| [Users](https://developers.notion.com/reference/get-users) | ✅ | ❌ | ❌ | ❌ | ❌ |

For example:

```sql
CREATE FOREIGN TABLE my_foreign_table (
id text,
name text,
type text,
person jsonb,
bot jsonb
-- other fields
)
SERVER notion_server
OPTIONS (
object 'users',
);
```

## Query Pushdown Support

This FDW supports `where` clause pushdown. You can specify a filter in `where` clause and it will be passed to Notion API call.

For example, this query

```sql
SELECT * from notion_users where id = 'xxx';
```

will be translated Notion API call: `https://api.notion.com/v1/users/xxx`.

## Examples

Some examples on how to use Notion foreign tables.

### Users foreign table

The following command creates a "foreign table" in your Postgres database named `notion_users`:

```sql
CREATE FOREIGN TABLE notion_users (
id text,
name text,
type text,
person jsonb,
bot jsonb
)
SERVER notion_server
OPTIONS (
object 'users'
);
```

You can now fetch your Notion data from within your Postgres database:

```sql
SELECT * FROM notion_users;
```

You can also query with filters:

```sql
SELECT * FROM notion_users WHERE id = 'xxx';
```
1 change: 1 addition & 0 deletions mkdocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ nav:
- ClickHouse: 'clickhouse.md'
- Firebase: 'firebase.md'
- Logflare: 'logflare.md'
- Notion: 'notion.md'
- Redis: 'redis.md'
- S3: 's3.md'
- Stripe: 'stripe.md'
Expand Down
11 changes: 11 additions & 0 deletions wrappers/.ci/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,14 @@ services:
timeout: 5s
retries: 20

notion:
container_name: notion-local
build:
context: ../dockerfiles/notion
ports:
- "4242:4242"
healthcheck:
test: curl --fail http://0.0.0.0:4242/ || exit 1
interval: 11s
timeout: 6s
retries: 3
11 changes: 11 additions & 0 deletions wrappers/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,16 @@ redis_fdw = [
"serde_json",
"thiserror",
]
notion_fdw = [
"reqwest",
"reqwest-middleware",
"reqwest-retry",
"http",
"serde_json",
"serde",
"url",
"thiserror",
]
# Does not include helloworld_fdw because of its general uselessness
all_fdws = [
"airtable_fdw",
Expand All @@ -131,6 +141,7 @@ all_fdws = [
"mssql_fdw",
"redis_fdw",
"cognito_fdw",
"notion_fdw",
]

[dependencies]
Expand Down
2 changes: 1 addition & 1 deletion wrappers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ This is a collection of FDWs built by [Supabase](https://www.supabase.com). We c
- [Cognito](./src/fdw/cognito_fdw): A FDW for [AWS Cogntio](https://aws.amazon.com/pm/cognito/).
- [SQL Server](./src/fdw/mssql_fdw): A FDW for [Microsoft SQL Server](https://www.microsoft.com/en-au/sql-server/) which supports data read only.
- [Redis](./src/fdw/redis_fdw): A FDW for [Redis](https://redis.io/) which supports data read only.

- [Notion](./src/fdw/notion_fdw): A FDW for [Notion](https://notion.so/) which supports users read only.
11 changes: 11 additions & 0 deletions wrappers/dockerfiles/notion/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM python:3.10-slim

WORKDIR /usr/src/app

RUN apt-get update \
&& apt-get install -y --no-install-recommends curl

COPY . .
RUN pip install -r requirements.txt

CMD [ "uvicorn", "server:app", "--host", "0.0.0.0", "--port", "4242" ]
28 changes: 28 additions & 0 deletions wrappers/dockerfiles/notion/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"users": [
{
"object": "user",
"id": "d40e767c-d7af-4b18-a86d-55c61f1e39a4",
"name": "John Doe",
"avatar_url": "https://s3-us-west-2.amazonaws.com/public.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg",
"type": "person",
"person": {
"email": "[email protected]"
}
},
{
"object": "user",
"id": "9a3b5ae0-c6e6-482d-b0e1-ed315ee6dc57",
"name": "Beep Boop",
"type": "bot",
"avatar_url": null,
"bot": {
"owner": {
"type": "workspace",
"workspace": true
},
"workspace_name": "John's Workspace"
}
}
]
}
1 change: 1 addition & 0 deletions wrappers/dockerfiles/notion/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fastapi
39 changes: 39 additions & 0 deletions wrappers/dockerfiles/notion/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env python3

import http.server
import socketserver
import json

from typing import Union
from fastapi import APIRouter, FastAPI, HTTPException

router = APIRouter(prefix="/v1")
data = json.loads(open("data.json").read())


@router.get("/users")
def users():
return {
"object": "list",
"next_cursor": None,
"has_more": False,
"type": "user",
"request_id": "829ea7f8-97b4-4a05-a1f3-df6ef3c467b4",
"results": data.get("users"),
}


@router.get("/users/{user_id}")
def user(user_id: str):
user = next((user for user in data.get("users") if user.get("id") == user_id), None)
if user:
return {
"object": "user",
"request_id": "829ea7f8-97b4-4a05-a1f3-df6ef3c467b4",
**user,
}

raise HTTPException(404, "User not found")

app = FastAPI()
app.include_router(router)
3 changes: 3 additions & 0 deletions wrappers/src/fdw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ mod redis_fdw;

#[cfg(feature = "cognito_fdw")]
mod cognito_fdw;

#[cfg(feature = "notion_fdw")]
mod notion_fdw;
Loading
Loading