mirror of
https://github.com/pockethost/pockethost.git
synced 2025-05-20 05:46:41 +00:00
feat: update pocketbase semver
This commit is contained in:
parent
465b95b5bc
commit
7b1d2d473c
@ -22,6 +22,7 @@
|
|||||||
- [Overview](development/overview.md)
|
- [Overview](development/overview.md)
|
||||||
- [Running Just the Frontend](development/frontend.md)
|
- [Running Just the Frontend](development/frontend.md)
|
||||||
- [Running Everything](development/full-stack/index.md)
|
- [Running Everything](development/full-stack/index.md)
|
||||||
|
- [Creating RPC Calls](development/rpc.md)
|
||||||
- [Production Deployment](development/production.md)
|
- [Production Deployment](development/production.md)
|
||||||
|
|
||||||
## Release History
|
## Release History
|
||||||
|
19
gitbook/development/rpc.md
Normal file
19
gitbook/development/rpc.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Creating RPC Calls
|
||||||
|
|
||||||
|
For security, PocketHost does not allow modification of records by the frontend PocketBase client. Instead, the frontend must send an rpc request which the backend will securely process. This allows for many security vulnerabilities to be addressed which PocketBase admin security rules cannot. In particular, PocketBase admin security rules fall short in these scenarios:
|
||||||
|
|
||||||
|
- When the incoming data cannot be validated declaratively
|
||||||
|
- When multiple records and/or tables must be updated as a transaction
|
||||||
|
- When side-effects (ie, other mutations) are required under specific conditions
|
||||||
|
|
||||||
|
Therefore, PocketHost uses an RPC pattern instead.
|
||||||
|
|
||||||
|
## Creating a new RPC Call
|
||||||
|
|
||||||
|
1. Create a new RPC call in `./packages/common/schema/Rpc`
|
||||||
|
2. Add frontend support in `./packages/pockethost.io/src/pocketbase/PocketbaseClient.ts` using the `mkRpc` command
|
||||||
|
3. Add backend support in (for example) `./packages/daemon/src/services/InstanceService/InstanceService.ts` using `registerCommand`
|
||||||
|
|
||||||
|
## Getting the result from an RPC call
|
||||||
|
|
||||||
|
RPC results are currently not supported. RPC commands are run asynchronously.
|
@ -39,6 +39,7 @@ export const createRpcHelper = (config: RpcHelperConfig) => {
|
|||||||
const validator = new Ajv().compile(schema)
|
const validator = new Ajv().compile(schema)
|
||||||
return safeCatch(
|
return safeCatch(
|
||||||
cmd,
|
cmd,
|
||||||
|
logger(),
|
||||||
async (
|
async (
|
||||||
payload: TPayload,
|
payload: TPayload,
|
||||||
cb?: (data: RecordSubscription<ConcreteRpcRecord>) => void
|
cb?: (data: RecordSubscription<ConcreteRpcRecord>) => void
|
||||||
|
@ -9,7 +9,8 @@ export type SaveSecretsPayload = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SaveSecretsResult = {
|
export type SaveSecretsResult = {
|
||||||
status: 'saved'
|
status: 'ok' | 'error'
|
||||||
|
message?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SECRET_KEY_REGEX = /^[A-Z][A-Z0-9_]*$/
|
export const SECRET_KEY_REGEX = /^[A-Z][A-Z0-9_]*$/
|
||||||
|
24
packages/common/src/schema/Rpc/SaveVersion.ts
Normal file
24
packages/common/src/schema/Rpc/SaveVersion.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { JSONSchemaType } from 'ajv'
|
||||||
|
import { InstanceId, Semver } from '../types'
|
||||||
|
|
||||||
|
export type SaveVersionPayload = {
|
||||||
|
instanceId: InstanceId
|
||||||
|
version: Semver
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SaveVersionResult = {
|
||||||
|
status: 'ok' | 'error'
|
||||||
|
message?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SaveVersionPayloadSchema: JSONSchemaType<SaveVersionPayload> = {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
instanceId: { type: 'string' },
|
||||||
|
version: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
required: ['instanceId', 'version'],
|
||||||
|
additionalProperties: false,
|
||||||
|
}
|
@ -6,12 +6,14 @@ export const RPC_COLLECTION = 'rpc'
|
|||||||
export enum RpcCommands {
|
export enum RpcCommands {
|
||||||
CreateInstance = 'create-instance',
|
CreateInstance = 'create-instance',
|
||||||
SaveSecrets = 'save-secrets',
|
SaveSecrets = 'save-secrets',
|
||||||
|
SaveVersion = 'save-version',
|
||||||
// gen:enum
|
// gen:enum
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RPC_COMMANDS = [
|
export const RPC_COMMANDS = [
|
||||||
RpcCommands.CreateInstance,
|
RpcCommands.CreateInstance,
|
||||||
RpcCommands.SaveSecrets,
|
RpcCommands.SaveSecrets,
|
||||||
|
RpcCommands.SaveVersion,
|
||||||
]
|
]
|
||||||
|
|
||||||
export enum RpcStatus {
|
export enum RpcStatus {
|
||||||
@ -46,4 +48,5 @@ export const ajv = new Ajv()
|
|||||||
|
|
||||||
export * from './CreateInstance'
|
export * from './CreateInstance'
|
||||||
export * from './SaveSecrets'
|
export * from './SaveSecrets'
|
||||||
|
export * from './SaveVersion'
|
||||||
// gen:export
|
// gen:export
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
export type RecordId = string
|
export type RecordId = string
|
||||||
export type UserId = RecordId
|
export type UserId = RecordId
|
||||||
export type InstanceId = RecordId
|
export type InstanceId = RecordId
|
||||||
|
export type Semver = string
|
||||||
export type InternalInstanceId = RecordId
|
export type InternalInstanceId = RecordId
|
||||||
export type Subdomain = string
|
export type Subdomain = string
|
||||||
export type Port = number
|
export type Port = number
|
||||||
|
@ -22,6 +22,9 @@ import {
|
|||||||
SaveSecretsPayload,
|
SaveSecretsPayload,
|
||||||
SaveSecretsPayloadSchema,
|
SaveSecretsPayloadSchema,
|
||||||
SaveSecretsResult,
|
SaveSecretsResult,
|
||||||
|
SaveVersionPayload,
|
||||||
|
SaveVersionPayloadSchema,
|
||||||
|
SaveVersionResult,
|
||||||
SingletonBaseConfig,
|
SingletonBaseConfig,
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
import { forEachRight, map } from '@s-libs/micro-dash'
|
import { forEachRight, map } from '@s-libs/micro-dash'
|
||||||
@ -75,6 +78,22 @@ export const instanceService = mkSingleton(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const SEMVER_RE =
|
||||||
|
/^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+)?$/
|
||||||
|
registerCommand<SaveVersionPayload, SaveVersionResult>(
|
||||||
|
RpcCommands.SaveVersion,
|
||||||
|
SaveVersionPayloadSchema,
|
||||||
|
async (rpc) => {
|
||||||
|
const { payload } = rpc
|
||||||
|
const { instanceId, version } = payload
|
||||||
|
if (version.match(SEMVER_RE) === null) {
|
||||||
|
return { status: `error`, message: `Semver must be a regex` }
|
||||||
|
}
|
||||||
|
await client.updateInstance(instanceId, { version })
|
||||||
|
return { status: 'ok' }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
registerCommand<SaveSecretsPayload, SaveSecretsResult>(
|
registerCommand<SaveSecretsPayload, SaveSecretsResult>(
|
||||||
RpcCommands.SaveSecrets,
|
RpcCommands.SaveSecrets,
|
||||||
SaveSecretsPayloadSchema,
|
SaveSecretsPayloadSchema,
|
||||||
@ -82,7 +101,7 @@ export const instanceService = mkSingleton(
|
|||||||
const { payload } = job
|
const { payload } = job
|
||||||
const { instanceId, secrets } = payload
|
const { instanceId, secrets } = payload
|
||||||
await client.updateInstance(instanceId, { secrets })
|
await client.updateInstance(instanceId, { secrets })
|
||||||
return { status: 'saved' }
|
return { status: 'ok' }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -9,12 +9,15 @@ import {
|
|||||||
RpcCommands,
|
RpcCommands,
|
||||||
safeCatch,
|
safeCatch,
|
||||||
SaveSecretsPayloadSchema,
|
SaveSecretsPayloadSchema,
|
||||||
|
SaveVersionPayloadSchema,
|
||||||
type CreateInstancePayload,
|
type CreateInstancePayload,
|
||||||
type CreateInstanceResult,
|
type CreateInstanceResult,
|
||||||
type InstanceFields,
|
type InstanceFields,
|
||||||
type InstanceId,
|
type InstanceId,
|
||||||
type SaveSecretsPayload,
|
type SaveSecretsPayload,
|
||||||
type SaveSecretsResult,
|
type SaveSecretsResult,
|
||||||
|
type SaveVersionPayload,
|
||||||
|
type SaveVersionResult,
|
||||||
type UserFields,
|
type UserFields,
|
||||||
type WorkerLogFields
|
type WorkerLogFields
|
||||||
} from '@pockethost/common'
|
} from '@pockethost/common'
|
||||||
@ -123,6 +126,11 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
SaveSecretsPayloadSchema
|
SaveSecretsPayloadSchema
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const saveVersion = mkRpc<SaveVersionPayload, SaveVersionResult>(
|
||||||
|
RpcCommands.SaveVersion,
|
||||||
|
SaveVersionPayloadSchema
|
||||||
|
)
|
||||||
|
|
||||||
const getInstanceById = safeCatch(
|
const getInstanceById = safeCatch(
|
||||||
`getInstanceById`,
|
`getInstanceById`,
|
||||||
_logger,
|
_logger,
|
||||||
@ -291,6 +299,7 @@ export const createPocketbaseClient = (config: PocketbaseClientConfig) => {
|
|||||||
user,
|
user,
|
||||||
watchInstanceById,
|
watchInstanceById,
|
||||||
getAllInstancesById,
|
getAllInstancesById,
|
||||||
resendVerificationEmail
|
resendVerificationEmail,
|
||||||
|
saveVersion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import ProvisioningStatus from '$components/ProvisioningStatus.svelte'
|
import ProvisioningStatus from '$components/ProvisioningStatus.svelte'
|
||||||
import { PUBLIC_APP_DOMAIN, PUBLIC_APP_PROTOCOL } from '$src/env'
|
import { PUBLIC_APP_DOMAIN, PUBLIC_APP_PROTOCOL } from '$src/env'
|
||||||
import type { InstanceFields } from '@pockethost/common'
|
import type { InstanceFields } from '@pockethost/common'
|
||||||
|
import RunningStatus from './RunningStatus.svelte'
|
||||||
|
|
||||||
export let instance: InstanceFields
|
export let instance: InstanceFields
|
||||||
|
|
||||||
@ -13,9 +14,7 @@
|
|||||||
<h2>Overview</h2>
|
<h2>Overview</h2>
|
||||||
<ProvisioningStatus {status} />
|
<ProvisioningStatus {status} />
|
||||||
Usage: {Math.ceil(instance.secondsThisMonth / 60)} mins
|
Usage: {Math.ceil(instance.secondsThisMonth / 60)} mins
|
||||||
<div>
|
<RunningStatus {instance} />
|
||||||
Running {version}
|
|
||||||
</div>
|
|
||||||
<div>
|
<div>
|
||||||
Admin URL: <a href={`${url}/_`} target="_blank">{`${url}/_`}</a>
|
Admin URL: <a href={`${url}/_`} target="_blank">{`${url}/_`}</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { PUBLIC_APP_DOMAIN, PUBLIC_APP_PROTOCOL } from '$src/env'
|
||||||
|
import { client } from '$src/pocketbase'
|
||||||
|
import type { InstanceFields } from '@pockethost/common'
|
||||||
|
|
||||||
|
export let instance: InstanceFields
|
||||||
|
|
||||||
|
const { subdomain, status, version } = instance
|
||||||
|
const url = `${PUBLIC_APP_PROTOCOL}://${subdomain}.${PUBLIC_APP_DOMAIN}`
|
||||||
|
|
||||||
|
let msg = ''
|
||||||
|
let _oldVersion = version
|
||||||
|
let _version = version
|
||||||
|
let editMode = false
|
||||||
|
const startEdit = () => {
|
||||||
|
_oldVersion = _version
|
||||||
|
editMode = true
|
||||||
|
}
|
||||||
|
const cancelEdit = () => {
|
||||||
|
_version = _oldVersion
|
||||||
|
editMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveEdit = () => {
|
||||||
|
client()
|
||||||
|
.saveVersion({ instanceId: instance.id, version: _version })
|
||||||
|
.then(() => {
|
||||||
|
editMode = false
|
||||||
|
msg = 'saved'
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
msg = e.message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Running {#if !editMode}
|
||||||
|
{_version}
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-primary"
|
||||||
|
style="--bs-btn-padding-y: .05rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;"
|
||||||
|
on:click={startEdit}
|
||||||
|
>
|
||||||
|
edit
|
||||||
|
</button>
|
||||||
|
{msg}
|
||||||
|
{/if}
|
||||||
|
{#if editMode}
|
||||||
|
<input type="text" bind:value={_version} />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-success"
|
||||||
|
style="--bs-btn-padding-y: .05rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;"
|
||||||
|
on:click={saveEdit}
|
||||||
|
>
|
||||||
|
save
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-danger"
|
||||||
|
style="--bs-btn-padding-y: .05rem; --bs-btn-padding-x: .5rem; --bs-btn-font-size: .75rem;"
|
||||||
|
on:click={cancelEdit}
|
||||||
|
>
|
||||||
|
cancel
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
Loading…
x
Reference in New Issue
Block a user