Skip to content

Commit

Permalink
Merge pull request #212 from Aunali321/feat/add-meilisearch-for-deno
Browse files Browse the repository at this point in the history
feat(deno): add `sync with meilisearch template`
  • Loading branch information
loks0n authored Oct 12, 2023
2 parents 6daac3a + 1c87d33 commit 954c0c0
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 0 deletions.
106 changes: 106 additions & 0 deletions deno/sync-with-meilisearch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# ⚡ Sync Appwrite to 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 | Deno (1.35) |
| Entrypoint | `src/main.ts` |
| Build Commands | `deno cache src/main.ts` |
| 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) |
78 changes: 78 additions & 0 deletions deno/sync-with-meilisearch/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import {
Client,
Databases,
Query,
} from "https://deno.land/x/[email protected]/mod.ts";
import { getStaticFile, interpolate, throwIfMissing } from "./utils.ts";
import { MeiliSearch } from "https://esm.sh/[email protected]";

export default async ({ req, res, log, error }: any) => {
throwIfMissing(Deno.env.toObject(), [
"APPWRITE_API_KEY",
"APPWRITE_DATABASE_ID",
"APPWRITE_COLLECTION_ID",
"MEILISEARCH_ENDPOINT",
"MEILISEARCH_INDEX_NAME",
"MEILISEARCH_ADMIN_API_KEY",
"MEILISEARCH_SEARCH_API_KEY",
]);

if (req.method === "GET") {
const html = interpolate(await getStaticFile("index.html"), {
MEILISEARCH_ENDPOINT: Deno.env.get("MEILISEARCH_ENDPOINT"),
MEILISEARCH_INDEX_NAME: Deno.env.get("MEILISEARCH_INDEX_NAME"),
MEILISEARCH_SEARCH_API_KEY: Deno.env.get("MEILISEARCH_SEARCH_API_KEY"),
});

return new res.send(html, 200, {
"Content-Type": "text/html; charset=utf-8",
});
}

const client = new Client()
.setEndpoint(
Deno.env.get("APPWRITE_ENDPOINT") ?? "https://cloud.appwrite.io/v1",
)
.setProject(Deno.env.get("APPWRITE_FUNCTION_PROJECT_ID") ?? "")
.setKey(Deno.env.get("APPWRITE_API_KEY") ?? "");

const databases = new Databases(client);

const meilisearch = new MeiliSearch({
host: Deno.env.get("MEILISEARCH_ENDPOINT") ?? "",
apiKey: Deno.env.get("MEILISEARCH_ADMIN_API_KEY") ?? "",
});

const index = meilisearch.index(Deno.env.get("MEILISEARCH_INDEX_NAME") ?? "");

let cursor = null;

do {
const queries = [Query.limit(100)];

if (cursor) {
queries.push(Query.cursorAfter(cursor));
}

const { documents } = await databases.listDocuments(
Deno.env.get("APPWRITE_DATABASE_ID") ?? "",
Deno.env.get("APPWRITE_COLLECTION_ID") ?? "",
queries,
);

if (documents.length > 0) {
cursor = documents[documents.length - 1].$id;
} else {
log(`No more documents found.`);
cursor = null;
break;
}

log(`Syncing chunk of ${documents.length} documents ...`);
await index.addDocuments(documents, { primaryKey: "$id" });
} while (cursor !== null);

log("Sync finished.");

return res.send("Sync finished.", 200);
};
33 changes: 33 additions & 0 deletions deno/sync-with-meilisearch/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Throws an error if any of the keys are missing from the object
*/
export function throwIfMissing(obj: any, keys: string[]) {
const missing: string[] = [];
for (let key of keys) {
if (!(key in obj) || !obj[key]) {
missing.push(key);
}
}
if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(", ")}`);
}
}

/**
* Returns the contents of a file in the static folder
*/
export function getStaticFile(fileName: string): string {
return Deno.readTextFileSync(
new URL(import.meta.resolve(`../static/${fileName}`)),
);
}

/**
* Returns the template with the values interpolated.
*/
export function interpolate(
template: string,
values: Record<string, string | undefined>,
): string {
return template.replace(/{{([^}]+)}}/g, (_, key) => values[key] || "");
}
72 changes: 72 additions & 0 deletions deno/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>
12 changes: 12 additions & 0 deletions deno/sync-with-meilisearch/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "ES2016",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}

0 comments on commit 954c0c0

Please sign in to comment.