Skip to content

Commit

Permalink
Merge pull request #233 from afzal442/feat-implement-sync-with-meilis…
Browse files Browse the repository at this point in the history
…earch-ruby

feat: sync with `meilisearch` for ruby
  • Loading branch information
loks0n authored Nov 2, 2023
2 parents 81c52e9 + 4fce47f commit 6dab35f
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 0 deletions.
4 changes: 4 additions & 0 deletions ruby/sync_with_meilisearch/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source "https://rubygems.org"

gem 'appwrite'
gem 'meilisearch'
106 changes: 106 additions & 0 deletions ruby/sync_with_meilisearch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ⚡ Ruby Sync with Meilisearch Function

Syncs documents in an Appwrite database collection to a Meilisearch index.

## 🧰 Usage

### GET /

Returns HTML page where search can be performed to test the indexing.

### POST /

Triggers indexing of the Appwrite database collection to Meilisearch.

**Response**

Sample `204` Response: No content.

## ⚙️ Configuration

| Setting | Value |
| ----------------- | ---------------- |
| Runtime | Ruby (3.0) |
| Entrypoint | `lib/main.rb` |
| Build Commands | `bundle install` |
| Permissions | `any` |
| Timeout (Seconds) | 15 |

## 🔒 Environment Variables

### APPWRITE_API_KEY

API Key to talk to Appwrite backend APIs.

| Question | Answer |
| ------------- | -------------------------------------------------------------------------------------------------- |
| Required | Yes |
| Sample Value | `d1efb...aec35` |
| Documentation | [Appwrite: Getting Started for Server](https://appwrite.io/docs/getting-started-for-server#apiKey) |

### APPWRITE_DATABASE_ID

The ID of the Appwrite database that contains the collection to sync.

| Question | Answer |
| ------------- | --------------------------------------------------------- |
| Required | Yes |
| Sample Value | `612a3...5b6c9` |
| Documentation | [Appwrite: Databases](https://appwrite.io/docs/databases) |

### APPWRITE_COLLECTION_ID

The ID of the collection in the Appwrite database to sync.

| Question | Answer |
| ------------- | ------------------------------------------------------------- |
| Required | Yes |
| Sample Value | `7c3e8...2a9f1` |
| Documentation | [Appwrite: Collections](https://appwrite.io/docs/databases#collection) |

### APPWRITE_ENDPOINT

The URL endpoint of the Appwrite server. If not provided, it defaults to the Appwrite Cloud server: `https://cloud.appwrite.io/v1`.

| Question | Answer |
| ------------ | ------------------------------ |
| Required | No |
| Sample Value | `https://cloud.appwrite.io/v1` |

### MEILISEARCH_ENDPOINT

The host URL of the Meilisearch server.

| Question | Answer |
| ------------ | ----------------------- |
| Required | Yes |
| Sample Value | `http://127.0.0.1:7700` |

### MEILISEARCH_ADMIN_API_KEY

The admin API key for Meilisearch.

| Question | Answer |
| ------------- | ------------------------------------------------------------------------ |
| Required | Yes |
| Sample Value | `masterKey1234` |
| Documentation | [Meilisearch: API Keys](https://docs.meilisearch.com/reference/api/keys) |

### MEILISEARCH_INDEX_NAME

Name of the Meilisearch index to which the documents will be synchronized.

| Question | Answer |
| ------------ | ---------- |
| Required | Yes |
| Sample Value | `my_index` |

### MEILISEARCH_SEARCH_API_KEY

API Key for Meilisearch search operations.

| Question | Answer |
| ------------- | ------------------------------------------------------------------------ |
| Required | Yes |
| Sample Value | `searchKey1234` |
| Documentation | [Meilisearch: API Keys](https://docs.meilisearch.com/reference/api/keys) |
72 changes: 72 additions & 0 deletions ruby/sync_with_meilisearch/lib/main.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
require 'appwrite'
require 'meilisearch'
require_relative 'utils'

def main(context)
throw_if_missing(ENV, [
'APPWRITE_API_KEY',
'APPWRITE_DATABASE_ID',
'APPWRITE_COLLECTION_ID',
'MEILISEARCH_ENDPOINT',
'MEILISEARCH_INDEX_NAME',
'MEILISEARCH_ADMIN_API_KEY',
'MEILISEARCH_SEARCH_API_KEY'
])

if context.req.method == 'GET'
html = interpolate(get_static_file('index.html'), {
'MEILISEARCH_ENDPOINT' => ENV['MEILISEARCH_ENDPOINT'],
'MEILISEARCH_INDEX_NAME' => ENV['MEILISEARCH_INDEX_NAME'],
'MEILISEARCH_SEARCH_API_KEY' => ENV['MEILISEARCH_SEARCH_API_KEY']
})

return context.res.send(html, 200, { 'Content-Type' => 'text/html; charset=utf-8' })
end

client = Appwrite::Client.new
client
.set_endpoint(ENV['APPWRITE_ENDPOINT'] || 'https://cloud.appwrite.io/v1')
.set_project(ENV['APPWRITE_FUNCTION_PROJECT_ID'])
.set_key(ENV['APPWRITE_API_KEY'])

databases = Appwrite::Databases.new(client)

meilisearch = MeiliSearch::Client.new(ENV['MEILISEARCH_ENDPOINT'], ENV['MEILISEARCH_ADMIN_API_KEY'])

index_name = ENV['MEILISEARCH_INDEX_NAME']

database = Appwrite::Database.new(client)

index = meilisearch.index(index_name)

cursor = nil

begin
queries = [Appwrite::Query.new.set_limit(100)]

if cursor
queries.push(Appwrite::Query.new.set_cursor(cursor))
end

documents = database.list_documents(
ENV['APPWRITE_DATABASE_ID'],
ENV['APPWRITE_COLLECTION_ID'],
queries
)

if documents['documents'].length > 0
cursor = documents['documents'].last['$id']
else
context.error('No more documents found.')
cursor = nil
break
end

context.log("Syncing chunk of #{documents['documents'].length} documents ...")
index.add_documents(documents['documents'], primary_key: '$id')
end while cursor

context.log('Sync finished.')

return context.res.send('Sync finished.', 200)
end
15 changes: 15 additions & 0 deletions ruby/sync_with_meilisearch/lib/utils.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
require 'pathname'

def throw_if_missing(hash, keys)
missing = keys.select { |key| !hash.key?(key) || hash[key].nil? }
raise "Missing required fields: #{missing.join(', ')}" if missing.any?
end

def get_static_file(file_name)
static_folder = File.join(File.dirname(__FILE__), '../static')
File.read(File.join(static_folder, file_name))
end

def interpolate(template, values)
template.gsub(/{{([^}]+)}}/) { |match| values[match[2..-3].strip] || match }
end
72 changes: 72 additions & 0 deletions ruby/sync_with_meilisearch/static/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Meilisearch Demo</title>

<script src="https://unpkg.com/[email protected]"></script>
<script src="https://unpkg.com/alpinejs" defer></script>

<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink" />
<link rel="stylesheet" href="https://unpkg.com/@appwrite.io/pink-icons" />
</head>
<body>
<main class="main-content">
<div class="top-cover u-padding-block-end-56">
<div class="container">
<div
class="u-flex u-gap-16 u-flex-justify-center u-margin-block-start-16"
>
<h1 class="heading-level-1">Meilisearch Demo</h1>
<code class="u-un-break-text"></code>
</div>
<p
class="body-text-1 u-normal u-margin-block-start-8"
style="max-width: 50rem"
>
Use this demo to verify that the sync between Appwrite Databases and
Meilisearch was successful. Search your Meilisearch index using the
input below.
</p>
</div>
</div>
<div
class="container u-margin-block-start-negative-56"
x-data="{ search: '', results: [ ] }"
x-init="$watch('search', async (value) => { results = await onSearch(value) })"
>
<div class="card u-flex u-gap-24 u-flex-vertical">
<div id="searchbox">
<div
class="input-text-wrapper is-with-end-button u-width-full-line"
>
<input x-model="search" type="search" placeholder="Search" />
<div class="icon-search" aria-hidden="true"></div>
</div>
</div>
<div id="hits" class="u-flex u-flex-vertical u-gap-12">
<template x-for="result in results">
<div class="card">
<pre x-text="JSON.stringify(result, null, '\t')"></pre>
</div>
</template>
</div>
</div>
</div>
</main>
<script>
const meilisearch = new MeiliSearch({
host: '{{MEILISEARCH_ENDPOINT}}',
apiKey: '{{MEILISEARCH_SEARCH_API_KEY}}',
});

const index = meilisearch.index('{{MEILISEARCH_INDEX_NAME}}');

window.onSearch = async function (prompt) {
return (await index.search(prompt)).hits;
};
</script>
</body>
</html>

0 comments on commit 6dab35f

Please sign in to comment.