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

Query database #76

Merged
merged 3 commits into from
May 13, 2022
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
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