Skip to content

Commit

Permalink
Query database (#76)
Browse files Browse the repository at this point in the history
Add database query

Close #5
  • Loading branch information
mariosimao authored May 13, 2022
1 parent 2980748 commit dadd19b
Show file tree
Hide file tree
Showing 29 changed files with 2,253 additions and 7 deletions.
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
- [Find a page](./how-to/find-a-page.md)
- [Update a page](./how-to/update-a-page.md)
- [Delete a page](./how-to/delete-a-page.md)
- [List database pages](./how-to/list-database-pages.md)
- [Query database](./how-to/query-database.md)

## Blocks

Expand Down
18 changes: 18 additions & 0 deletions docs/how-to/list-database-pages.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# List database pages

```php
<?php

use Notion\Notion;

$token = $_ENV["NOTION_SECRET"];

$notion = Notion::create($token);

$databaseId = "c986d7b0-7051-4f18-b165-cc0b9503ffc2";
$database = $notion->databases()->find($pageId);

$pages = $notion->databases()->queryAllPages($database);

count($pages);
```
35 changes: 35 additions & 0 deletions docs/how-to/query-database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Query database

```php
<?php

use Notion\Notion;
use Notion\Databases\Query;
use Notion\Databases\Query\CompoundFilter;
use Notion\Databases\Query\DateFilter;
use Notion\Databases\Query\Sort;
use Notion\Databases\Query\TextFilter;

$token = $_ENV["NOTION_SECRET"];

$notion = Notion::create($token);

$databaseId = "c986d7b0-7051-4f18-b165-cc0b9503ffc2";
$database = $notion->databases()->find($pageId);

$query = Query::create()
->withFilter(
CompoundFilter::and(
DateFilter::createdTime::pastWeek(),
TextFilter::property("Name")->contains("John"),
)
)
->withAddedSort(Sort::property("Name")->ascending())
->withPageSize(20);

$result = $notion->databases()->query($database, $query);

$pages = $result->pages(); // array of Page
$result->hasMore(); // true or false
$result->nextCursor() // cursor ID or null
```
64 changes: 64 additions & 0 deletions src/Databases/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

namespace Notion\Databases;

use Notion\Databases\Query\Result;
use Notion\Databases\Query\Sort;
use Notion\NotionException;
use Notion\Pages\Page;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;

/**
* @psalm-import-type DatabaseJson from Database
* @psalm-import-type QueryResultJson from Result
*/
class Client
{
Expand Down Expand Up @@ -137,4 +141,64 @@ public function delete(Database $database): void
throw new NotionException($message, $code);
}
}

public function query(Database $database, Query $query): Result
{
$data = $query->toArray();

$databaseId = $database->id();
$url = "https://api.notion.com/v1/databases/{$databaseId}/query";
$request = $this->requestFactory->createRequest("POST", $url)
->withHeader("Authorization", "Bearer {$this->token}")
->withHeader("Notion-Version", $this->version)
->withHeader("Content-Type", "application/json");

$request->getBody()->write(json_encode($data));

$response = $this->psrClient->sendRequest($request);

/** @var array */
$body = json_decode((string) $response->getBody(), true);

if ($response->getStatusCode() !== 200) {
/** @var array{ message: string, code: string} $body */
$message = $body["message"];
$code = $body["code"];

throw new NotionException($message, $code);
}

/** @psalm-var QueryResultJson $body */
return Result::fromArray($body);
}

/**
* @param list<Sort> $sorts
*
* @return list<Page>
*/
public function queryAllPages(Database $database, array $sorts = []): array
{
$query = Query::create()
->withSorts($sorts)
->withPageSize(Query::MAX_PAGE_SIZE);

$pages = [];
$startCursor = null;
$hasMore = true;

while ($hasMore) {
if ($startCursor !== null) {
$query = $query->withStartCursor($startCursor);
}

$result = $this->query($database, $query);

$pages = array_merge($pages, $result->pages());
$hasMore = $result->hasMore();
$startCursor = $result->nextCursor();
}

return $pages;
}
}
117 changes: 117 additions & 0 deletions src/Databases/Query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php

namespace Notion\Databases;

use Exception;
use Notion\Databases\Query\Filter;
use Notion\Databases\Query\Sort;

/** @psalm-immutable */
class Query
{
public const MAX_PAGE_SIZE = 100;

private Filter|null $filter;

/** @var list<Sort> */
private array $sorts;

private string|null $startCursor;

private int $pageSize;

/** @param list<Sort> $sorts */
private function __construct(
Filter|null $filter,
array $sorts,
string|null $startCursor,
int $pageSize
) {
$this->filter = $filter;
$this->sorts = $sorts;
$this->startCursor = $startCursor;
$this->pageSize = $pageSize;
}

public static function create(): self
{
return new self(null, [], null, self::MAX_PAGE_SIZE);
}

public function withFilter(Filter $filter): self
{
return new self($filter, $this->sorts, $this->startCursor, $this->pageSize);
}

/** Add new sort with lowest priority */
public function withAddedSort(Sort $sort): self
{
$sorts = $this->sorts;
$sorts[] = $sort;

return new self($this->filter, $sorts, $this->startCursor, $this->pageSize);
}

/**
* Replace all sorts
*
* @param list<Sort> $sorts
*/
public function withSorts(array $sorts): self
{
return new self($this->filter, $sorts, $this->startCursor, $this->pageSize);
}

public function withStartCursor(string $startCursor): self
{
return new self($this->filter, $this->sorts, $startCursor, $this->pageSize);
}

public function withPageSize(int $pageSize): self
{
if ($pageSize < 0 || $pageSize > self::MAX_PAGE_SIZE) {
throw new Exception("Maximum page size: " . self::MAX_PAGE_SIZE);
}

return new self($this->filter, $this->sorts, $this->startCursor, $pageSize);
}

public function filter(): Filter|null
{
return $this->filter;
}

/** @return list<Sort> */
public function sorts(): array
{
return $this->sorts;
}

public function startCursor(): string|null
{
return $this->startCursor;
}

public function pageSize(): int
{
return $this->pageSize;
}

public function toArray(): array
{
$array = [
"sorts" => array_map(fn (Sort $s) => $s->toArray(), $this->sorts),
"page_size" => $this->pageSize,
];

if ($this->filter !== null) {
$array["filter"] = $this->filter->toArray();
}

if ($this->startCursor !== null) {
$array["start_cursor"] = $this->startCursor;
}

return $array;
}
}
77 changes: 77 additions & 0 deletions src/Databases/Query/CheckboxFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

namespace Notion\Databases\Query;

/** @psalm-immutable */
class CheckboxFilter implements Filter, Condition
{
private const OPERATOR_EQUALS = "equals";
private const OPERATOR_DOES_NOT_EQUAL = "does_not_equal";

private string $propertyType = "property";
private string $propertyName;
/** @var self::OPERATOR_* */
private string $operator;
private bool $value;

/** @param self::OPERATOR_* $operator */
private function __construct(
string $propertyName,
string $operator,
bool $value,
) {
$this->propertyName = $propertyName;
$this->operator = $operator;
$this->value = $value;
}

public static function property(string $propertyName): self
{
return new self(
$propertyName,
self::OPERATOR_EQUALS,
true
);
}

public function propertyType(): string
{
return $this->propertyType;
}

public function propertyName(): string
{
return $this->propertyName;
}

/** @return static::OPERATOR_* */
public function operator(): string
{
return $this->operator;
}

public function value(): bool
{
return $this->value;
}

public function toArray(): array
{
return [
$this->propertyType() => $this->propertyName,
"checkbox" => [
$this->operator => $this->value
],
];
}

public function equals(bool $value): self
{
return new self($this->propertyName, self::OPERATOR_EQUALS, $value);
}

public function doesNotEqual(bool $value): self
{
return new self($this->propertyName, self::OPERATOR_DOES_NOT_EQUAL, $value);
}
}
39 changes: 39 additions & 0 deletions src/Databases/Query/CompoundFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

namespace Notion\Databases\Query;

/** @psalm-immutable */
class CompoundFilter implements Filter
{
private const TYPE_AND = "and";
private const TYPE_OR = "or";

/** @var self::TYPE_* */
private string $type;
/** @var Filter[] */
private array $filters;

/** @param self::TYPE_* $type */
private function __construct(string $type, Filter ...$filters)
{
$this->type = $type;
$this->filters = $filters;
}

public static function and(Filter ...$filters): self
{
return new self(self::TYPE_AND, ...$filters);
}

public static function or(Filter ...$filters): self
{
return new self(self::TYPE_OR, ...$filters);
}

public function toArray(): array
{
return [
$this->type => array_map(fn (Filter $f) => $f->toArray(), $this->filters)
];
}
}
11 changes: 11 additions & 0 deletions src/Databases/Query/Condition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace Notion\Databases\Query;

interface Condition
{
public function propertyType(): string;
public function propertyName(): string;
public function operator(): string;
public function value(): mixed;
}
Loading

0 comments on commit dadd19b

Please sign in to comment.