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

feat: sync with meilisearch for ruby #233

Merged
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
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>