diff --git a/frontends/dashboard/src/pocketbase-client/PocketbaseClient.ts b/frontends/dashboard/src/pocketbase-client/PocketbaseClient.ts index 2a12e167..75fd5be2 100644 --- a/frontends/dashboard/src/pocketbase-client/PocketbaseClient.ts +++ b/frontends/dashboard/src/pocketbase-client/PocketbaseClient.ts @@ -1,5 +1,8 @@ import { CreateInstancePayloadSchema, + DeleteInstancePayload, + DeleteInstancePayloadSchema, + DeleteInstanceResult, RestCommands, RestMethods, UpdateInstancePayload, @@ -104,6 +107,12 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => { UpdateInstancePayloadSchema, ) + const deleteInstance = mkRest( + RestCommands.Instance, + RestMethods.Delete, + DeleteInstancePayloadSchema, + ) + const getInstanceById = ( id: InstanceId, ): Promise => @@ -271,5 +280,6 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => { getAllInstancesById, resendVerificationEmail, updateInstance, + deleteInstance, } } diff --git a/frontends/dashboard/src/routes/app/instances/[instanceId]/+page.svelte b/frontends/dashboard/src/routes/app/instances/[instanceId]/+page.svelte index a7098000..bf261196 100644 --- a/frontends/dashboard/src/routes/app/instances/[instanceId]/+page.svelte +++ b/frontends/dashboard/src/routes/app/instances/[instanceId]/+page.svelte @@ -6,6 +6,7 @@ import Code from './Code.svelte' import AdminSync from './Danger/AdminSync.svelte' import DangerZoneTitle from './Danger/DangerZoneTitle.svelte' + import DeleteInstance from './Danger/DeleteInstance.svelte' import Maintenance from './Danger/Maintenance.svelte' import RenameInstance from './Danger/RenameInstance.svelte' import VersionChange from './Danger/VersionChange/VersionChange.svelte' @@ -87,4 +88,6 @@ + + diff --git a/frontends/dashboard/src/routes/app/instances/[instanceId]/Danger/DeleteInstance.svelte b/frontends/dashboard/src/routes/app/instances/[instanceId]/Danger/DeleteInstance.svelte new file mode 100644 index 00000000..53db71f2 --- /dev/null +++ b/frontends/dashboard/src/routes/app/instances/[instanceId]/Danger/DeleteInstance.svelte @@ -0,0 +1,108 @@ + + + + + Delete Instance + + +
+ Deleting your instance will immediately and permanently delete your + instance: +
    +
  • Your subdomain
  • +
  • pb_data/*
  • +
  • pb_public/*
  • +
  • pb_migrations/*
  • +
  • pb_static/*
  • +
+ If you are storing files on S3, you must delete them separately. +
+ + + +
+ +
+
+ + diff --git a/src/mothership-app/pb_hooks/src/instances-delete.pb.js b/src/mothership-app/pb_hooks/src/instances-delete.pb.js new file mode 100644 index 00000000..ee0201d5 --- /dev/null +++ b/src/mothership-app/pb_hooks/src/instances-delete.pb.js @@ -0,0 +1,66 @@ +/// + +/* +{ + "id": "kz4ngg77eaw1ho0", + "fields": { + "maintenance": true + "name": '', + "version": '' + } +} +*/ +routerAdd( + 'DELETE', + '/api/instance/:id', + (c) => { + console.log(`***TOP OF DELETE`) + let data = new DynamicModel({ + id: '', + }) + + c.bind(data) + console.log(`***After bind`) + + // This is necessary for destructuring to work correctly + data = JSON.parse(JSON.stringify(data)) + + const id = c.pathParam('id') + + console.log( + `***vars`, + JSON.stringify({ + id, + }), + ) + + const authRecord = c.get('authRecord') // empty if not authenticated as regular auth record + console.log(`***authRecord`, JSON.stringify(authRecord)) + + if (!authRecord) { + throw new BadRequestError(`Expected authRecord here`) + } + + const record = $app.dao().findRecordById('instances', id) + if (!record) { + throw new BadRequestError(`Instance ${id} not found.`) + } + if (record.get('uid') !== authRecord.id) { + throw new BadRequestError(`Not authorized`) + } + + if (record.get('status') !== 'idle') { + throw new BadRequestError(`Instance must be shut down first.`) + } + + const path = [$os.getenv('DATA_ROOT'), id].join('/') + console.log(`***path ${path}`) + const res = $os.removeAll(path) + console.log(`***res`, res) + + $app.dao().deleteRecord(record) + + return c.json(200, { status: 'ok' }) + }, + $apis.requireRecordAuth(), +) diff --git a/src/server.ts b/src/server.ts index 4d884e28..fcf1b9b3 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,4 +1,5 @@ import { + DATA_ROOT, DEBUG, DefaultSettingsService, MOTHERSHIP_ADMIN_PASSWORD, @@ -70,6 +71,9 @@ global.EventSource = EventSource name: MOTHERSHIP_NAME(), slug: MOTHERSHIP_NAME(), port: MOTHERSHIP_PORT(), + env: { + DATA_ROOT: DATA_ROOT(), + }, extraBinds: [ `${MOTHERSHIP_HOOKS_DIR()}:/home/pocketbase/pb_hooks`, `${MOTHERSHIP_MIGRATIONS_DIR()}:/home/pocketbase/pb_migrations`, diff --git a/src/shared/schema/Rest/DeleteInstance.ts b/src/shared/schema/Rest/DeleteInstance.ts new file mode 100644 index 00000000..18c743bd --- /dev/null +++ b/src/shared/schema/Rest/DeleteInstance.ts @@ -0,0 +1,21 @@ +import { JSONSchemaType } from 'ajv' +import { InstanceId } from '../types' + +export type DeleteInstancePayload = { + id: InstanceId +} + +export type DeleteInstanceResult = { + status: 'ok' | 'error' + message?: string +} + +export const DeleteInstancePayloadSchema: JSONSchemaType = + { + type: 'object', + properties: { + id: { type: 'string' }, + }, + required: ['id'], + additionalProperties: false, + } diff --git a/src/shared/schema/Rest/index.ts b/src/shared/schema/Rest/index.ts index 26d924f4..f8d555b8 100644 --- a/src/shared/schema/Rest/index.ts +++ b/src/shared/schema/Rest/index.ts @@ -5,6 +5,7 @@ export enum RestMethods { Get = 'GET', Post = 'POST', Put = 'PUT', + Delete = 'DELETE', } export enum RestCommands { @@ -17,5 +18,6 @@ export type RestPayloadBase = JsonObject export const ajv = new Ajv() export * from './CreateInstance' +export * from './DeleteInstance' export * from './GetUserTokenInfo' export * from './UpdateInstance'