Skip to content

Commit

Permalink
feat(search): search pages and databases
Browse files Browse the repository at this point in the history
Closes #62
  • Loading branch information
mariosimao authored Mar 16, 2023
1 parent 45eed99 commit 5c2a4bd
Show file tree
Hide file tree
Showing 21 changed files with 637 additions and 4 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Support unknown blocks and page/database properties. Pages and databases with unsuported resources will be loaded without errors. (#173)
- Add page and database properties collection with typed getters. (#179)
- Page and database properties collection with typed getters. (#179)
- Search pages and databases.

## [1.4.1]

Expand Down
8 changes: 8 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ export default defineConfig({
{ text: 'Introduction', link: '/comments/' },
],
},
{
text: 'Search',
collapsible: true,
collapsed: true,
items: [
{ text: 'Introduction', link: '/search/' },
],
},
{
text: 'Advanced',
collapsible: true,
Expand Down
46 changes: 46 additions & 0 deletions docs/search/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Search

## Introduction

Searches all parent or child pages and databases that have been shared with an
integration.

Returns all pages or databases, excluding duplicated linked databases, that have
titles that include the query param. If no query param is provided, then the
response contains all pages or databases that have been shared with the
integration.

::: warning
If you want to search a specific individual database, rather than across all
databases, then [query a database](../how-to/query-database.md) instead.
:::

## Searching

```php
use Notion\Search\Query;

$query = Query::title("Example"); // Search by page/database title...
$query = Query::all(); // ... or search everything

$result = $notion->search()->search($query);

$result->hasMore; // bool
$result->nextCursor; // CursorId when hasMore is true
$result->results; // array of Page and/or Database objects
```

## Query options

```php
use Notion\Search\Query;

$query = Query::title("Example"); // Page or database title
$query = $query->filterByPages(); // Return only pages
$query = $query->filterByDatabases(); // Return only databases
$query = $query->sortByLastEditedTime(SortDirection::Ascending); // Results order

// Pagination
$query = $query->changePageSize(10);
$query = $query->changeNextCursor("70d73991-7e06-43d9-ad3c-3711213f1235")
```
6 changes: 3 additions & 3 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
errorLevel="1"
resolveFromConfigFile="true"
memoizeMethodCallResults="true"
findUnusedPsalmSuppress="true"
findUnusedCode="true"
findUnusedBaselineEntry="true"
findUnusedPsalmSuppress="false"
findUnusedCode="false"
findUnusedBaselineEntry="false"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
Expand Down
6 changes: 6 additions & 0 deletions src/Notion.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Notion\Comments\Client as CommentsClient;
use Notion\Databases\Client as DatabasesClient;
use Notion\Pages\Client as PagesClient;
use Notion\Search\Client as SearchClient;
use Notion\Users\Client as UsersClient;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestFactoryInterface;
Expand Down Expand Up @@ -69,4 +70,9 @@ public function comments(): CommentsClient
{
return new CommentsClient($this->configuration);
}

public function search(): SearchClient
{
return new SearchClient($this->configuration);
}
}
2 changes: 2 additions & 0 deletions src/Pages/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* @psalm-import-type PageParentJson from PageParent
*
* @psalm-type PageJson = array{
* object: "page",
* id: string,
* created_time: string,
* last_edited_time: string,
Expand Down Expand Up @@ -108,6 +109,7 @@ public static function fromArray(array $array): self
public function toArray(): array
{
return [
"object" => "page",
"id" => $this->id,
"created_time" => $this->createdTime->format(Date::FORMAT),
"last_edited_time" => $this->lastEditedTime->format(Date::FORMAT),
Expand Down
33 changes: 33 additions & 0 deletions src/Search/Client.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Notion\Search;

use Notion\Configuration;
use Notion\Infrastructure\Http;

/** @psalm-import-type ResultJson from \Notion\Search\Result */
class Client
{
/**
* @internal Use `\Notion\Notion::search()` instead
*/
public function __construct(
private readonly Configuration $config,
) {
}

public function search(Query $query): Result
{
$data = json_encode($query->toArray(), JSON_PRETTY_PRINT);
$url = "https://api.notion.com/v1/search";
$request = Http::createRequest($url, $this->config)
->withMethod("POST")
->withHeader("Content-Type", "application/json");
$request->getBody()->write($data);

/** @psalm-var ResultJson $body */
$body = Http::sendRequest($request, $this->config);

return Result::fromArray($body);
}
}
38 changes: 38 additions & 0 deletions src/Search/Filter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace Notion\Search;

/** @psalm-immutable */
class Filter
{
private function __construct(
public readonly FilterValue $value,
public readonly FilterProperty $property,
) {
}

/** @psalm-mutation-free */
public static function byPages(): self
{
return new self(FilterValue::Page, FilterProperty::Object);
}

/** @psalm-mutation-free */
public static function byDatabases(): self
{
return new self(FilterValue::Database, FilterProperty::Object);
}

/**
* @internal
*
* @return array{ value: "page"|"database", property: "object" }
*/
public function toArray(): array
{
return [
"value" => $this->value->value,
"property" => $this->property->value,
];
}
}
8 changes: 8 additions & 0 deletions src/Search/FilterProperty.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Notion\Search;

enum FilterProperty: string
{
case Object = "object";
}
9 changes: 9 additions & 0 deletions src/Search/FilterValue.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php

namespace Notion\Search;

enum FilterValue: string
{
case Page = "page";
case Database = "database";
}
121 changes: 121 additions & 0 deletions src/Search/Query.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Notion\Search;

/** @psalm-immutable */
class Query
{
private function __construct(
public readonly string|null $query = null,
public readonly Filter|null $filter = null,
public readonly Sort|null $sort = null,
public readonly string|null $startCursor = null,
public readonly int|null $pageSize = null,
) {
}

public static function all(): self
{
return new self();
}

public static function title(string $query): self
{
return new self($query);
}

public function filterByPages(): self
{
return new self(
$this->query,
Filter::byPages(),
$this->sort,
$this->startCursor,
$this->pageSize,
);
}

public function filterByDatabases(): self
{
return new self(
$this->query,
Filter::byDatabases(),
$this->sort,
$this->startCursor,
$this->pageSize,
);
}

public function sortByLastEditedTime(SortDirection $direction): self
{
$sort = Sort::create()->byLastEditedTime();

return new self(
$this->query,
$this->filter,
$direction === SortDirection::Ascending ? $sort->ascending() : $sort->descending(),
$this->startCursor,
$this->pageSize,
);
}

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

public function changePageSize(int $pageSize): self
{
return new self(
$this->query,
$this->filter,
$this->sort,
$this->startCursor,
$pageSize,
);
}

/**
* @internal
*
* @return array{
* query?: string,
* filter?: array{ value: string, property: string },
* sort?: array{ direction: string, timestamp: string },
* start_cursor?: string,
* page_size?: int
* }
*/
public function toArray(): array
{
$array = [];

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

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

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

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

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

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

namespace Notion\Search;

use Notion\Databases\Database;
use Notion\Pages\Page;

/**
* @psalm-immutable
*
* @psalm-import-type PageJson from \Notion\Pages\Page
* @psalm-import-type DatabaseJson from \Notion\Databases\Database
*
* @psalm-type ResultJson = array{
* object: string,
* results: list{PageJson, DatabaseJson},
* next_cursor: string|null,
* has_more: bool
* }
*/
class Result
{
/** @param list{Page, Database} $results */
private function __construct(
public readonly array $results,
public readonly string|null $nextCursor,
public readonly bool $hasMore,
) {
}

/**
* @psalm-param ResultJson $array
*/
public static function fromArray(array $array): self
{
$results = [];
foreach ($array["results"] as $result) {
if ($result["object"] === "page") {
/** @psalm-var PageJson $result */
$results[] = Page::fromArray($result);
}

if ($result["object"] === "database") {
/** @psalm-var DatabaseJson $result */
$results[] = Database::fromArray($result);
}
}

/** @psalm-var list{Page, Database} $results */
return new self(
$results,
$array["next_cursor"],
$array["has_more"],
);
}
}
Loading

0 comments on commit 5c2a4bd

Please sign in to comment.