feat: MeiliSearch service
This commit is contained in:
parent
5f27fc0770
commit
c55505af6c
12
prisma/migrations/20220402210645_meilisearch/migration.sql
Normal file
12
prisma/migrations/20220402210645_meilisearch/migration.sql
Normal 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");
|
@ -283,6 +283,7 @@ model Service {
|
|||||||
wordpress Wordpress?
|
wordpress Wordpress?
|
||||||
ghost Ghost?
|
ghost Ghost?
|
||||||
serviceSecret ServiceSecret[]
|
serviceSecret ServiceSecret[]
|
||||||
|
meiliSearch MeiliSearch?
|
||||||
}
|
}
|
||||||
|
|
||||||
model PlausibleAnalytics {
|
model PlausibleAnalytics {
|
||||||
@ -352,3 +353,12 @@ model Ghost {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
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
|
||||||
|
}
|
||||||
|
45
src/lib/components/svg/services/MeiliSearch.svelte
Normal file
45
src/lib/components/svg/services/MeiliSearch.svelte
Normal 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
|
||||||
|
>
|
@ -197,6 +197,16 @@ export const supportedServiceTypesAndVersions = [
|
|||||||
ports: {
|
ports: {
|
||||||
main: 2368
|
main: 2368
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'meilisearch',
|
||||||
|
fancyName: 'Meilisearch',
|
||||||
|
baseImage: 'getmeili/meilisearch',
|
||||||
|
images: [],
|
||||||
|
versions: ['latest'],
|
||||||
|
ports: {
|
||||||
|
main: 7700
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -22,7 +22,8 @@ export async function getService({ id, teamId }) {
|
|||||||
vscodeserver: true,
|
vscodeserver: true,
|
||||||
wordpress: true,
|
wordpress: true,
|
||||||
ghost: 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);
|
body.ghost.mariadbRootUserPassword = decrypt(body.ghost.mariadbRootUserPassword);
|
||||||
if (body.ghost?.defaultPassword) body.ghost.defaultPassword = decrypt(body.ghost.defaultPassword);
|
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) {
|
if (body?.serviceSecret.length > 0) {
|
||||||
body.serviceSecret = body.serviceSecret.map((s) => {
|
body.serviceSecret = body.serviceSecret.map((s) => {
|
||||||
s.value = decrypt(s.value);
|
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 }) {
|
export async function setServiceVersion({ id, version }) {
|
||||||
@ -191,6 +203,9 @@ export async function updateService({ id, fqdn, name }) {
|
|||||||
export async function updateLanguageToolService({ id, fqdn, name }) {
|
export async function updateLanguageToolService({ id, fqdn, name }) {
|
||||||
return await prisma.service.update({ where: { id }, data: { 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 }) {
|
export async function updateVaultWardenService({ id, fqdn, name }) {
|
||||||
return await prisma.service.update({ where: { id }, data: { 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 }) {
|
export async function removeService({ id }) {
|
||||||
|
await prisma.meiliSearch.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
await prisma.ghost.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
await prisma.plausibleAnalytics.deleteMany({ where: { serviceId: id } });
|
||||||
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
await prisma.minio.deleteMany({ where: { serviceId: id } });
|
||||||
|
19
src/routes/services/[id]/_Services/_MeiliSearch.svelte
Normal file
19
src/routes/services/[id]/_Services/_MeiliSearch.svelte
Normal 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>
|
@ -11,6 +11,7 @@
|
|||||||
import { errorNotification } from '$lib/form';
|
import { errorNotification } from '$lib/form';
|
||||||
import { toast } from '@zerodevx/svelte-toast';
|
import { toast } from '@zerodevx/svelte-toast';
|
||||||
import Ghost from './_Ghost.svelte';
|
import Ghost from './_Ghost.svelte';
|
||||||
|
import MeiliSearch from './_MeiliSearch.svelte';
|
||||||
import MinIo from './_MinIO.svelte';
|
import MinIo from './_MinIO.svelte';
|
||||||
import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
|
import PlausibleAnalytics from './_PlausibleAnalytics.svelte';
|
||||||
import VsCodeServer from './_VSCodeServer.svelte';
|
import VsCodeServer from './_VSCodeServer.svelte';
|
||||||
@ -145,6 +146,8 @@
|
|||||||
<Wordpress bind:service {isRunning} {readOnly} />
|
<Wordpress bind:service {isRunning} {readOnly} />
|
||||||
{:else if service.type === 'ghost'}
|
{:else if service.type === 'ghost'}
|
||||||
<Ghost bind:service {readOnly} />
|
<Ghost bind:service {readOnly} />
|
||||||
|
{:else if service.type === 'meilisearch'}
|
||||||
|
<MeiliSearch bind:service />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -41,6 +41,7 @@
|
|||||||
import N8n from '$lib/components/svg/services/N8n.svelte';
|
import N8n from '$lib/components/svg/services/N8n.svelte';
|
||||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||||
|
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
|
||||||
|
|
||||||
const { id } = $page.params;
|
const { id } = $page.params;
|
||||||
const from = $page.url.searchParams.get('from');
|
const from = $page.url.searchParams.get('from');
|
||||||
@ -86,6 +87,8 @@
|
|||||||
<UptimeKuma isAbsolute />
|
<UptimeKuma isAbsolute />
|
||||||
{:else if type.name === 'ghost'}
|
{:else if type.name === 'ghost'}
|
||||||
<Ghost isAbsolute />
|
<Ghost isAbsolute />
|
||||||
|
{:else if type.name === 'meilisearch'}
|
||||||
|
<MeiliSearch isAbsolute />
|
||||||
{/if}{type.fancyName}
|
{/if}{type.fancyName}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -13,7 +13,7 @@ export const post: RequestHandler = async (event) => {
|
|||||||
if (fqdn) fqdn = fqdn.toLowerCase();
|
if (fqdn) fqdn = fqdn.toLowerCase();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.updateLanguageToolService({ id, fqdn, name });
|
await db.updateMeiliSearchService({ id, fqdn, name });
|
||||||
return { status: 201 };
|
return { status: 201 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return ErrorHandler(error);
|
return ErrorHandler(error);
|
||||||
|
21
src/routes/services/[id]/meilisearch/index.json.ts
Normal file
21
src/routes/services/[id]/meilisearch/index.json.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
83
src/routes/services/[id]/meilisearch/start.json.ts
Normal file
83
src/routes/services/[id]/meilisearch/start.json.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
35
src/routes/services/[id]/meilisearch/stop.json.ts
Normal file
35
src/routes/services/[id]/meilisearch/stop.json.ts
Normal 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);
|
||||||
|
}
|
||||||
|
};
|
@ -11,6 +11,7 @@
|
|||||||
import N8n from '$lib/components/svg/services/N8n.svelte';
|
import N8n from '$lib/components/svg/services/N8n.svelte';
|
||||||
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
import UptimeKuma from '$lib/components/svg/services/UptimeKuma.svelte';
|
||||||
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
import Ghost from '$lib/components/svg/services/Ghost.svelte';
|
||||||
|
import MeiliSearch from '$lib/components/svg/services/MeiliSearch.svelte';
|
||||||
|
|
||||||
export let services;
|
export let services;
|
||||||
async function newService() {
|
async function newService() {
|
||||||
@ -67,6 +68,8 @@
|
|||||||
<UptimeKuma isAbsolute />
|
<UptimeKuma isAbsolute />
|
||||||
{:else if service.type === 'ghost'}
|
{:else if service.type === 'ghost'}
|
||||||
<Ghost isAbsolute />
|
<Ghost isAbsolute />
|
||||||
|
{:else if service.type === 'meilisearch'}
|
||||||
|
<MeiliSearch isAbsolute />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="font-bold text-xl text-center truncate">
|
<div class="font-bold text-xl text-center truncate">
|
||||||
{service.name}
|
{service.name}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user