Last active
August 2, 2024 10:24
-
-
Save thekid/d6ebd435998e0b8bd70a85c9dcc517c6 to your computer and use it in GitHub Desktop.
Atlas Search
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
composer.lock | |
vendor/ | |
static/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use com\mongodb\Collection; | |
use util\profiling\Timer; | |
use web\Application; | |
use web\frontend\{Frontend, AssetsFrom, Handlebars, Get, View, Param}; | |
class Atlas extends Application { | |
use Connection; | |
/** @return [:web.Handler] */ | |
public function routes() { | |
$db= self::connect($this->environment->variable('MONGO_URI'), $this->environment->variable('MONGO_DB') ?? 'search'); | |
$impl= new class($db->collection(self::$COLLECTION), self::$INDEX) { | |
public function __construct(private Collection $collection, private string $index) { } | |
#[Get] | |
public function search(#[Param('q')] $query= '') { | |
$view= View::named('search'); | |
if ('' === $query) return $view; | |
// Ranking: 1) Direct hit on title, 2) Phrase in title, 3) Phrase in overview, 4) Fuzzy matching | |
$search= [ | |
'should' => [ | |
['text' => ['query' => $query, 'path' => 'title', 'score' => ['boost' => ['value' => 5.0]]]], | |
['phrase' => ['query' => $query, 'path' => 'title', 'score' => ['boost' => ['value' => 2.0]]]], | |
['phrase' => ['query' => $query, 'path' => 'overview', 'score' => ['boost' => ['value' => 1.0]]]], | |
['text' => [ | |
'query' => $query, | |
'path' => ['title', 'overview', 'genres'], | |
'fuzzy' => ['maxEdits' => 1], | |
'score' => ['boost' => ['value' => 0.5]], | |
]], | |
], | |
]; | |
$timer= (new Timer())->start(); | |
$results= $this->collection->aggregate([ | |
['$search' => ['index' => $this->index, 'compound' => $search]], | |
['$limit' => 20], | |
['$addFields' => ['meta' => '$$SEARCH_META']], | |
]); | |
$timer->stop(); | |
return $view->with([ | |
'meta' => ['elapsed' => (int)($timer->elapsedTime() * 1000)] + ($results->first()['meta'] ?? []), | |
'results' => $results->all(), | |
]); | |
} | |
}; | |
return [ | |
'/static' => new AssetsFrom($this->environment->webroot()), | |
'/' => new Frontend($impl, new Handlebars('.', [[ | |
'date' => fn($node, $context, $options) => date($options[1] ?? 'Y-m-d', $options[0]), | |
]])) | |
]; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"require": { | |
"xp-forge/handlebars-templates": "^3.1", | |
"xp-forge/frontend": "^6.3", | |
"xp-forge/mongodb": "^2.3", | |
"php": ">=8.0.0" | |
}, | |
"scripts": { | |
"dev": "xp -supervise web -m develop Atlas", | |
"serve": "xp -supervise web Atlas", | |
"post-update-cmd": "xp bundle && cp icon.svg static/search.svg" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use com\mongodb\{MongoConnection, Database}; | |
trait Connection { | |
private static $COLLECTION= 'movies', $INDEX= 'full'; | |
private static function connect(string $uri, string $database): Database { | |
return (new MongoConnection($uri))->database($database); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use lang\Environment; | |
use peer\http\HttpConnection; | |
use text\json\StreamInput; | |
use util\cmd\Console; | |
class ImportMovies { | |
use Connection; | |
/** @param string[] $args */ | |
public static function main($args): int { | |
$db= self::connect(Environment::variable('MONGO_URI'), Environment::variable('MONGO_DB', 'search')); | |
$collection= $db->collection(self::$COLLECTION); | |
$source= $args[0] ?? 'https://www.meilisearch.com/movies.json'; | |
Console::writeLine('Importing ', $source, ' -> ', $collection); | |
$response= (new HttpConnection($source))->get(); | |
if (200 !== $response->statusCode()) { | |
Console::writeLine('Unexpected ', $response); | |
return 1; | |
} | |
// Stream response, upserting movies in collection while going | |
$input= new StreamInput($response->in(), 'utf-8'); | |
$records= 0; | |
try { | |
foreach ($input->elements() as $element) { | |
$element['_id']= $element['id']; | |
unset($element['id']); | |
$collection->upsert($element['_id'], $element); | |
Console::writef("\e[2K\r=> [%07d] \e[34m%s\e[0m%s", $element['_id'], $element['title'], $records % 1000 ? '' : "\n"); | |
$records++; | |
} | |
Console::writeLine(); | |
} finally { | |
$input->close(); | |
} | |
Console::writeLine('Finished, ', $records, ' record(s) imported'); | |
return 0; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"dependencies": { | |
"htmx.org": "^1.9" | |
}, | |
"bundles": { | |
"vendor": {"htmx.org": ["dist/htmx.min.js"], "fonts://display=swap": "Montserrat:wght@400"} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use com\mongodb\MongoConnection; | |
use lang\Environment; | |
use util\cmd\Console; | |
class SearchIndex { | |
use Connection; | |
/** @param string[] $args */ | |
public static function main($args) { | |
$db= self::connect(Environment::variable('MONGO_URI'), Environment::variable('MONGO_DB', 'search')); | |
$collection= $db->collection(self::$COLLECTION); | |
// Quoting https://www.mongodb.com/docs/manual/reference/command/createSearchIndexes/: | |
// This command can only be run on a deployment hosted on MongoDB Atlas, and requires an | |
// Atlas cluster tier of at least M10. In the free tier, go to https://cloud.mongodb.com/ | |
// and create the indexes using the frontend | |
$result= $collection->run('createSearchIndexes', ['indexes' => [[ | |
'name' => self::$INDEX, | |
'definition' => ['mappings' => ['dynamic' => true]], | |
]]]); | |
Console::writeLine('Created index => ', $result); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Setup instructions after cloning:
Finally, run
xp serve
(orxp dev
for development mode) and open http://localhost:8080/