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? | ||||
|   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 | ||||
| } | ||||
|  | ||||
							
								
								
									
										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: { | ||||
| 			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, | ||||
| 			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 } }); | ||||
|  | ||||
							
								
								
									
										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 { 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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
							
								
								
									
										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 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} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user