feat: MeiliSearch service

This commit is contained in:
Andras Bacsai 2022-04-02 23:08:27 +02:00
parent 5f27fc0770
commit c55505af6c
13 changed files with 262 additions and 2 deletions

View File

@ -0,0 +1,12 @@
-- CreateTable
CREATE TABLE "MeiliSearch" (
"id" TEXT NOT NULL PRIMARY KEY,
"masterKey" TEXT NOT NULL,
"serviceId" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
CONSTRAINT "MeiliSearch_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "Service" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);
-- CreateIndex
CREATE UNIQUE INDEX "MeiliSearch_serviceId_key" ON "MeiliSearch"("serviceId");

View File

@ -283,6 +283,7 @@ model Service {
wordpress Wordpress?
ghost Ghost?
serviceSecret ServiceSecret[]
meiliSearch MeiliSearch?
}
model PlausibleAnalytics {
@ -352,3 +353,12 @@ model Ghost {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model MeiliSearch {
id String @id @default(cuid())
masterKey String
serviceId String @unique
service Service @relation(fields: [serviceId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

View File

@ -0,0 +1,45 @@
<script lang="ts">
export let isAbsolute = false;
</script>
<svg
viewBox="0 0 127 74"
class={isAbsolute ? 'w-10 h-10 absolute top-0 left-0 -m-5' : 'w-8 mx-auto'}
xmlns="http://www.w3.org/2000/svg"
><path
d="M.825 73.993l23.244-59.47A21.85 21.85 0 0144.42.625h14.014L35.19 60.096a21.85 21.85 0 01-20.352 13.897H.825z"
fill="url(#meilisearch_logo_svg__paint0_linear_0_6)"
/><path
d="M34.925 73.993l23.243-59.47A21.85 21.85 0 0178.52.626h14.013L69.29 60.096a21.85 21.85 0 01-20.351 13.897H34.925z"
fill="url(#meilisearch_logo_svg__paint1_linear_0_6)"
/><path
d="M69.026 73.993l23.244-59.47A21.85 21.85 0 01112.621.626h14.014l-23.244 59.47a21.851 21.851 0 01-20.352 13.897H69.026z"
fill="url(#meilisearch_logo_svg__paint2_linear_0_6)"
/><defs
><linearGradient
id="meilisearch_logo_svg__paint0_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
><linearGradient
id="meilisearch_logo_svg__paint1_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
><linearGradient
id="meilisearch_logo_svg__paint2_linear_0_6"
x1="126.635"
y1="-4.978"
x2="0.825"
y2="66.098"
gradientUnits="userSpaceOnUse"
><stop stop-color="#FF5CAA" /><stop offset="1" stop-color="#FF4E62" /></linearGradient
></defs
></svg
>

View File

@ -197,6 +197,16 @@ export const supportedServiceTypesAndVersions = [
ports: {
main: 2368
}
},
{
name: 'meilisearch',
fancyName: 'Meilisearch',
baseImage: 'getmeili/meilisearch',
images: [],
versions: ['latest'],
ports: {
main: 7700
}
}
];

View File

@ -22,7 +22,8 @@ export async function getService({ id, teamId }) {
vscodeserver: true,
wordpress: true,
ghost: true,
serviceSecret: true
serviceSecret: true,
meiliSearch: true
}
});
@ -50,6 +51,8 @@ export async function getService({ id, teamId }) {
body.ghost.mariadbRootUserPassword = decrypt(body.ghost.mariadbRootUserPassword);
if (body.ghost?.defaultPassword) body.ghost.defaultPassword = decrypt(body.ghost.defaultPassword);
if (body.meiliSearch?.masterKey) body.meiliSearch.masterKey = decrypt(body.meiliSearch.masterKey);
if (body?.serviceSecret.length > 0) {
body.serviceSecret = body.serviceSecret.map((s) => {
s.value = decrypt(s.value);
@ -165,6 +168,15 @@ export async function configureServiceType({ id, type }) {
}
}
});
} else if (type === 'meilisearch') {
const masterKey = encrypt(generatePassword(32));
await prisma.service.update({
where: { id },
data: {
type,
meiliSearch: { create: { masterKey } }
}
});
}
}
export async function setServiceVersion({ id, version }) {
@ -191,6 +203,9 @@ export async function updateService({ id, fqdn, name }) {
export async function updateLanguageToolService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateMeiliSearchService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
export async function updateVaultWardenService({ id, fqdn, name }) {
return await prisma.service.update({ where: { id }, data: { fqdn, name } });
}
@ -214,6 +229,7 @@ export async function updateGhostService({ id, fqdn, name, mariadbDatabase }) {
}
export async function removeService({ id }) {
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
await prisma.ghost.deleteMany({ where: { serviceId: id } });
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
await prisma.minio.deleteMany({ where: { serviceId: id } });

View File

@ -0,0 +1,19 @@
<script lang="ts">
import CopyPasswordField from '$lib/components/CopyPasswordField.svelte';
export let service;
</script>
<div class="flex space-x-1 py-5 font-bold">
<div class="title">MeiliSearch</div>
</div>
<div class="grid grid-cols-2 items-center px-10">
<label for="masterKey">Admin API key</label>
<CopyPasswordField
id="masterKey"
isPasswordField
readonly
disabled
name="masterKey"
value={service.meiliSearch.masterKey}
/>
</div>

View File

@ -11,6 +11,7 @@
import { errorNotification } from '$lib/form';
import { toast } from '@zerodevx/svelte-toast';
import Ghost from './_Ghost.svelte';
import MeiliSearch from './_MeiliSearch.svelte';
import MinIo from './_MinIO.svelte';
import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
import VsCodeServer from './_VSCodeServer.svelte';
@ -145,6 +146,8 @@
<Wordpress bind:service {isRunning} {readOnly} />
{:else if service.type === 'ghost'}
<Ghost bind:service {readOnly} />
{:else if service.type === 'meilisearch'}
<MeiliSearch bind:service />
{/if}
</div>
</form>

View File

@ -41,6 +41,7 @@
import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
const { id } = $page.params;
const from = $page.url.searchParams.get('from');
@ -86,6 +87,8 @@
<UptimeKuma isAbsolute />
{:else if type.name === 'ghost'}
<Ghost isAbsolute />
{:else if type.name === 'meilisearch'}
<MeiliSearch isAbsolute />
{/if}{type.fancyName}
</button>
</form>

View File

@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
if (fqdn) fqdn = fqdn.toLowerCase();
try {
await db.updateLanguageToolService({ id, fqdn, name });
await db.updateMeiliSearchService({ id, fqdn, name });
return { status: 201 };
} catch (error) {
return ErrorHandler(error);

View File

@ -0,0 +1,21 @@
import { getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
let { name, fqdn } = await event.request.json();
if (fqdn) fqdn = fqdn.toLowerCase();
try {
await db.updateLanguageToolService({ id, fqdn, name });
return { status: 201 };
} catch (error) {
return ErrorHandler(error);
}
};

View File

@ -0,0 +1,83 @@
import { asyncExecShell, createDirectories, getEngine, getUserDetails } from '$lib/common';
import * as db from '$lib/database';
import { promises as fs } from 'fs';
import yaml from 'js-yaml';
import type { RequestHandler } from '@sveltejs/kit';
import { ErrorHandler, getServiceImage } from '$lib/database';
import { makeLabelForServices } from '$lib/buildPacks/common';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const {
meiliSearch: { masterKey }
} = service;
const { type, version, destinationDockerId, destinationDocker, serviceSecret } = service;
const network = destinationDockerId && destinationDocker.network;
const host = getEngine(destinationDocker.engine);
const { workdir } = await createDirectories({ repository: type, buildId: id });
const image = getServiceImage(type);
const config = {
image: `${image}:${version}`,
volume: `${id}-datams:/data.ms`,
environmentVariables: {
MEILI_MASTER_KEY: masterKey
}
};
if (serviceSecret.length > 0) {
serviceSecret.forEach((secret) => {
config.environmentVariables[secret.name] = secret.value;
});
}
const composeFile = {
version: '3.8',
services: {
[id]: {
container_name: id,
image: config.image,
networks: [network],
environment: config.environmentVariables,
restart: 'always',
volumes: [config.volume],
labels: makeLabelForServices('meilisearch')
}
},
networks: {
[network]: {
external: true
}
},
volumes: {
[config.volume.split(':')[0]]: {
name: config.volume.split(':')[0]
}
}
};
const composeFileDestination = `${workdir}/docker-compose.yaml`;
await fs.writeFile(composeFileDestination, yaml.dump(composeFile));
try {
if (version === 'latest') {
await asyncExecShell(
`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} pull`
);
}
await asyncExecShell(`DOCKER_HOST=${host} docker compose -f ${composeFileDestination} up -d`);
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
} catch (error) {
return ErrorHandler(error);
}
};

View File

@ -0,0 +1,35 @@
import { getUserDetails, removeDestinationDocker } from '$lib/common';
import * as db from '$lib/database';
import { ErrorHandler } from '$lib/database';
import { checkContainer } from '$lib/haproxy';
import type { RequestHandler } from '@sveltejs/kit';
export const post: RequestHandler = async (event) => {
const { teamId, status, body } = await getUserDetails(event);
if (status === 401) return { status, body };
const { id } = event.params;
try {
const service = await db.getService({ id, teamId });
const { destinationDockerId, destinationDocker, fqdn } = service;
if (destinationDockerId) {
const engine = destinationDocker.engine;
try {
const found = await checkContainer(engine, id);
if (found) {
await removeDestinationDocker({ id, engine });
}
} catch (error) {
console.error(error);
}
}
return {
status: 200
};
} catch (error) {
return ErrorHandler(error);
}
};

View File

@ -11,6 +11,7 @@
import N8n from '$lib/components/svg/services/N8n.svelte';
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
import Ghost from '$lib/components/svg/services/Ghost.svelte';
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
export let services;
async function newService() {
@ -67,6 +68,8 @@
<UptimeKuma isAbsolute />
{:else if service.type === 'ghost'}
<Ghost isAbsolute />
{:else if service.type === 'meilisearch'}
<MeiliSearch isAbsolute />
{/if}
<div class="font-bold text-xl text-center truncate">
{service.name}