initial v0.8 pre-release

This commit is contained in:
Gani Georgiev
2022-10-30 10:28:14 +02:00
parent 9cbb2e750e
commit 90dba45d7c
388 changed files with 21580 additions and 13603 deletions
@@ -0,0 +1,221 @@
<script>
import { scale, slide } from "svelte/transition";
import { Collection } from "pocketbase";
import { errors } from "@/stores/errors";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import CommonHelper from "@/utils/CommonHelper";
import MultipleValueInput from "@/components/base/MultipleValueInput.svelte";
import Accordion from "@/components/base/Accordion.svelte";
export let collection = new Collection();
$: if (collection.isAuth && CommonHelper.isEmpty(collection.options)) {
collection.options = {
allowEmailAuth: true,
allowUsernameAuth: true,
allowOAuth2Auth: true,
minPasswordLength: 8,
};
}
$: hasUsernameErrors = false;
$: hasEmailErrors =
!CommonHelper.isEmpty($errors?.options?.allowEmailAuth) ||
!CommonHelper.isEmpty($errors?.options?.onlyEmailDomains) ||
!CommonHelper.isEmpty($errors?.options?.exceptEmailDomains);
$: hasOAuth2Errors = !CommonHelper.isEmpty($errors?.options?.allowOAuth2Auth);
</script>
<h4 class="section-title">Auth methods</h4>
<div class="accordions">
<Accordion single>
<svelte:fragment slot="header">
<div class="inline-flex">
<i class="ri-user-star-line" />
<span class="txt">Username/Password</span>
</div>
<div class="flex-fill" />
{#if collection.options.allowUsernameAuth}
<span class="label label-success">Enabled</span>
{:else}
<span class="label">Disabled</span>
{/if}
{#if hasUsernameErrors}
<i
class="ri-error-warning-fill txt-danger"
transition:scale={{ duration: 150, start: 0.7 }}
use:tooltip={{ text: "Has errors", position: "left" }}
/>
{/if}
</svelte:fragment>
<Field class="form-field form-field-toggle m-b-0" name="options.allowUsernameAuth" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={collection.options.allowUsernameAuth} />
<label for={uniqueId}>Enable</label>
</Field>
</Accordion>
<Accordion single>
<svelte:fragment slot="header">
<div class="inline-flex">
<i class="ri-mail-star-line" />
<span class="txt">Email/Password</span>
</div>
<div class="flex-fill" />
{#if collection.options.allowEmailAuth}
<span class="label label-success">Enabled</span>
{:else}
<span class="label">Disabled</span>
{/if}
{#if hasEmailErrors}
<i
class="ri-error-warning-fill txt-danger"
transition:scale={{ duration: 150, start: 0.7 }}
use:tooltip={{ text: "Has errors", position: "left" }}
/>
{/if}
</svelte:fragment>
<Field class="form-field form-field-toggle m-0" name="options.allowEmailAuth" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={collection.options.allowEmailAuth} />
<label for={uniqueId}>Enable</label>
</Field>
{#if collection.options.allowEmailAuth}
<div class="grid grid-sm p-t-sm" transition:slide|local={{ duration: 150 }}>
<div class="col-lg-6">
<Field
class="form-field {!CommonHelper.isEmpty(collection.options.onlyEmailDomains)
? 'disabled'
: ''}"
name="options.exceptEmailDomains"
let:uniqueId
>
<label for={uniqueId}>
<span class="txt">Except domains</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: 'Email domains that are NOT allowed to sign up. \n This field is disabled if "Only domains" is set.',
position: "top",
}}
/>
</label>
<MultipleValueInput
id={uniqueId}
disabled={!CommonHelper.isEmpty(collection.options.onlyEmailDomains)}
bind:value={collection.options.exceptEmailDomains}
/>
<div class="help-block">Use comma as separator.</div>
</Field>
</div>
<div class="col-lg-6">
<Field
class="form-field {!CommonHelper.isEmpty(collection.options.exceptEmailDomains)
? 'disabled'
: ''}"
name="options.onlyEmailDomains"
let:uniqueId
>
<label for={uniqueId}>
<span class="txt">Only domains</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: 'Email domains that are ONLY allowed to sign up. \n This field is disabled if "Except domains" is set.',
position: "top",
}}
/>
</label>
<MultipleValueInput
id={uniqueId}
disabled={!CommonHelper.isEmpty(collection.options.exceptEmailDomains)}
bind:value={collection.options.onlyEmailDomains}
/>
<div class="help-block">Use comma as separator.</div>
</Field>
</div>
</div>
{/if}
</Accordion>
<Accordion single>
<svelte:fragment slot="header">
<div class="inline-flex">
<i class="ri-shield-star-line" />
<span class="txt">OAuth2</span>
</div>
<div class="flex-fill" />
{#if collection.options.allowOAuth2Auth}
<span class="label label-success">Enabled</span>
{:else}
<span class="label">Disabled</span>
{/if}
{#if hasOAuth2Errors}
<i
class="ri-error-warning-fill txt-danger"
transition:scale={{ duration: 150, start: 0.7 }}
use:tooltip={{ text: "Has errors", position: "left" }}
/>
{/if}
</svelte:fragment>
<Field class="form-field form-field-toggle m-b-0" name="options.allowOAuth2Auth" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={collection.options.allowOAuth2Auth} />
<label for={uniqueId}>Enable</label>
</Field>
{#if collection.options.allowOAuth2Auth}
<div class="block" transition:slide|local={{ duration: 150 }}>
<div class="flex p-t-base">
<a href="/_/#/settings/auth-providers" target="_blank" class="btn btn-sm btn-outline">
<span class="txt">Manage OAuth2 providers</span>
</a>
</div>
</div>
{/if}
</Accordion>
</div>
<hr />
<h4 class="section-title">General</h4>
<Field class="form-field required" name="options.minPasswordLength" let:uniqueId>
<label for={uniqueId}>Minimum password length</label>
<input
type="number"
id={uniqueId}
required
min="6"
max="72"
bind:value={collection.options.minPasswordLength}
/>
</Field>
<Field class="form-field form-field-toggle m-b-sm" name="options.requireEmail" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={collection.options.requireEmail} />
<label for={uniqueId}>
<span class="txt">Always require email</span>
<i
class="ri-information-line txt-sm link-hint"
use:tooltip={{
text: "The constraint is applied only for new records.\nAlso note that some OAuth2 providers (like Twitter), don't return an email and the authentication may fail if the email field is required.",
position: "right",
}}
/>
</label>
</Field>
@@ -0,0 +1,156 @@
<script>
import { Collection } from "pocketbase";
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
const baseTabs = {
list: {
label: "List/Search",
component: import("@/components/collections/docs/ListApiDocs.svelte"),
},
view: {
label: "View",
component: import("@/components/collections/docs/ViewApiDocs.svelte"),
},
create: {
label: "Create",
component: import("@/components/collections/docs/CreateApiDocs.svelte"),
},
update: {
label: "Update",
component: import("@/components/collections/docs/UpdateApiDocs.svelte"),
},
delete: {
label: "Delete",
component: import("@/components/collections/docs/DeleteApiDocs.svelte"),
},
realtime: {
label: "Realtime",
component: import("@/components/collections/docs/RealtimeApiDocs.svelte"),
},
};
const authTabs = {
"auth-with-password": {
label: "Auth with password",
component: import("@/components/collections/docs/AuthWithPasswordDocs.svelte"),
},
"auth-with-oauth2": {
label: "Auth with OAuth2",
component: import("@/components/collections/docs/AuthWithOAuth2Docs.svelte"),
},
refresh: {
label: "Auth refresh",
component: import("@/components/collections/docs/AuthRefreshDocs.svelte"),
},
"request-verification": {
label: "Request verification",
component: import("@/components/collections/docs/RequestVerificationDocs.svelte"),
},
"confirm-verification": {
label: "Confirm verification",
component: import("@/components/collections/docs/ConfirmVerificationDocs.svelte"),
},
"request-password-reset": {
label: "Request password reset",
component: import("@/components/collections/docs/RequestPasswordResetDocs.svelte"),
},
"confirm-password-reset": {
label: "Confirm password reset",
component: import("@/components/collections/docs/ConfirmPasswordResetDocs.svelte"),
},
"request-email-change": {
label: "Request email change",
component: import("@/components/collections/docs/RequestEmailChangeDocs.svelte"),
},
"confirm-email-change": {
label: "Confirm email change",
component: import("@/components/collections/docs/ConfirmEmailChangeDocs.svelte"),
},
"list-auth-methods": {
label: "List auth methods",
component: import("@/components/collections/docs/AuthMethodsDocs.svelte"),
},
"list-linked-accounts": {
label: "List OAuth2 accounts",
component: import("@/components/collections/docs/ListExternalAuthsDocs.svelte"),
},
"unlink-account": {
label: "Unlink OAuth2 account",
component: import("@/components/collections/docs/UnlinkExternalAuthDocs.svelte"),
},
};
let docsPanel;
let collection = new Collection();
let activeTab;
let tabs = [];
$: if (collection.isAuth) {
tabs = Object.assign({}, baseTabs, authTabs);
if (!collection?.options.allowUsernameAuth && !collection?.options.allowEmailAuth) {
delete tabs["auth-with-password"];
}
if (!collection?.options.allowOAuth2Auth) {
delete tabs["auth-with-oauth2"];
}
} else {
tabs = Object.assign({}, baseTabs);
}
// reset active tab on tabs list change
if (tabs.length) {
activeTab = Object.keys(tabs)[0];
}
export function show(model) {
collection = model;
changeTab(Object.keys(tabs)[0]);
return docsPanel?.show();
}
export function hide() {
return docsPanel?.hide();
}
export function changeTab(newTab) {
activeTab = newTab;
}
</script>
<OverlayPanel bind:this={docsPanel} on:hide on:show class="docs-panel">
<div class="docs-content-wrapper">
<aside class="docs-sidebar" class:compact={collection?.isAuth}>
<nav class="sidebar-content">
{#each Object.entries(tabs) as [key, tab] (key)}
<button
type="button"
class="sidebar-item"
class:active={activeTab === key}
on:click={() => changeTab(key)}
>
{tab.label}
</button>
{/each}
</nav>
</aside>
<div class="docs-content">
{#each Object.entries(tabs) as [key, tab] (key)}
{#if activeTab === key}
{#await tab.component then { default: TabComponent }}
<TabComponent {collection} />
{/await}
{/if}
{/each}
</div>
</div>
<!-- visible only on small screens -->
<svelte:fragment slot="footer">
<button type="button" class="btn btn-secondary" on:click={() => hide()}>
<span class="txt">Close</span>
</button>
</svelte:fragment>
</OverlayPanel>
@@ -2,10 +2,40 @@
import { SchemaField } from "pocketbase";
import FieldAccordion from "@/components/collections/FieldAccordion.svelte";
const reservedNames = ["id", "created", "updated"];
export let collection = {};
const baseReservedNames = [
"id",
"created",
"updated",
"collectionId",
"collectionName",
"expand",
"true",
"false",
"null",
];
let reservedNames = [];
$: if (collection.isAuth) {
reservedNames = baseReservedNames.concat([
"username",
"email",
"emailVisibility",
"verified",
"tokenKey",
"passwordHash",
"lastResetSentAt",
"lastVerificationSentAt",
"password",
"passwordConfirm",
"oldPassword",
]);
} else {
reservedNames = baseReservedNames.slice(0);
}
$: if (typeof collection?.schema === "undefined") {
collection = collection || {};
collection.schema = [];
@@ -58,15 +88,68 @@
return result;
}
// ---------------------------------------------------------------
// fields drag&drop handling
// ---------------------------------------------------------------
function onFieldDrag(event, i) {
if (!event) {
return;
}
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.dropEffect = "move";
event.dataTransfer.setData("text/plain", i);
}
function onFieldDrop(event, target) {
if (!event) {
return;
}
event.dataTransfer.dropEffect = "move";
const start = parseInt(event.dataTransfer.getData("text/plain"));
const newSchema = collection.schema;
if (start < target) {
newSchema.splice(target + 1, 0, newSchema[start]);
newSchema.splice(start, 1);
} else {
newSchema.splice(target, 0, newSchema[start]);
newSchema.splice(start + 1, 1);
}
collection.schema = newSchema;
}
</script>
<div class="block m-b-25">
<p class="txt-sm">
System fields:
<code class="txt-sm">id</code> ,
<code class="txt-sm">created</code> ,
<code class="txt-sm">updated</code>
{#if collection.isAuth}
,
<code class="txt-sm">username</code> ,
<code class="txt-sm">email</code> ,
<code class="txt-sm">emailVisibility</code> ,
<code class="txt-sm">verified</code>
{/if}
.
</p>
</div>
<div class="accordions">
{#each collection.schema as field, i (i)}
{#each collection.schema as field, i (i + field.id)}
<FieldAccordion
bind:field
key={i}
excludeNames={reservedNames.concat(getSiblingsFieldNames(field))}
on:remove={() => removeField(i)}
on:dragstart={(e) => onFieldDrag(e?.detail, i)}
on:drop={(e) => onFieldDrop(e?.detail, i)}
/>
{/each}
</div>
@@ -75,7 +158,7 @@
<button
type="button"
class="btn btn-block {collection.schema?.length ? 'btn-secondary' : 'btn-success'}"
class="btn btn-block {collection.schema?.length ? 'btn-secondary' : 'btn-warning'}"
on:click={newField}
>
<i class="ri-add-line" />
@@ -1,52 +1,18 @@
<script>
import { onMount, tick } from "svelte";
import { slide } from "svelte/transition";
import { Collection } from "pocketbase";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import RuleField from "@/components/collections/RuleField.svelte";
export let collection = new Collection();
let tempValues = {};
let showFiltersInfo = false;
let editorRefs = {};
let ruleInputComponent;
let isRuleComponentLoading = false;
// all supported collection rules in "collection_rule_prop: label" format
const ruleProps = {
listRule: "List Action",
viewRule: "View Action",
createRule: "Create Action",
updateRule: "Update Action",
deleteRule: "Delete Action",
};
function isAdminOnly(propVal) {
return propVal === null;
}
async function loadEditorComponent() {
isRuleComponentLoading = true;
try {
ruleInputComponent = (await import("@/components/base/FilterAutocompleteInput.svelte")).default;
} catch (err) {
console.warn(err);
ruleInputComponent = null;
}
isRuleComponentLoading = false;
}
onMount(() => {
loadEditorComponent();
});
</script>
<div class="block m-b-base">
<div class="flex">
<div class="flex txt-sm m-b-5">
<p>
All rules follow the
<a href={import.meta.env.PB_RULES_SYNTAX_DOCS} target="_blank" rel="noopener">
<a href={import.meta.env.PB_RULES_SYNTAX_DOCS} target="_blank" rel="noopener noreferrer">
PocketBase filter syntax and operators
</a>.
</p>
@@ -85,7 +51,7 @@
<code>@request.method</code>
<code>@request.query.*</code>
<code>@request.data.*</code>
<code>@request.user.*</code>
<code>@request.auth.*</code>
</div>
<hr class="m-t-10 m-b-5" />
@@ -104,7 +70,7 @@
<p>
Example rule:
<br />
<code>@request.user.id!="" && created>"2022-01-01 00:00:00"</code>
<code>@request.auth.id != "" && created > "2022-01-01 00:00:00"</code>
</p>
</div>
</div>
@@ -112,78 +78,37 @@
{/if}
</div>
{#if isRuleComponentLoading}
<div class="txt-center">
<span class="loader" />
</div>
{:else}
{#each Object.entries(ruleProps) as [prop, label] (prop)}
<hr class="m-t-sm m-b-sm" />
<div class="rule-block">
{#if isAdminOnly(collection[prop])}
<button
type="button"
class="rule-toggle-btn btn btn-circle btn-outline btn-success"
use:tooltip={"Unlock and set custom rule"}
on:click={async () => {
collection[prop] = tempValues[prop] || "";
await tick();
editorRefs[prop]?.focus();
}}
>
<i class="ri-lock-unlock-line" />
</button>
{:else}
<button
type="button"
class="rule-toggle-btn btn btn-circle btn-outline"
use:tooltip={"Lock and set to Admins only"}
on:click={() => {
tempValues[prop] = collection[prop];
collection[prop] = null;
}}
>
<i class="ri-lock-line" />
</button>
{/if}
<RuleField label="List/Search action" formKey="listRule" {collection} bind:rule={collection.listRule} />
<Field
class="form-field rule-field m-0 {isAdminOnly(collection[prop]) ? 'disabled' : ''}"
name={prop}
let:uniqueId
>
<label for={uniqueId}>
{label} - {isAdminOnly(collection[prop]) ? "Admins only" : "Custom rule"}
</label>
<hr class="m-t-sm m-b-sm" />
<RuleField label="View action" formKey="viewRule" {collection} bind:rule={collection.viewRule} />
<svelte:component
this={ruleInputComponent}
id={uniqueId}
bind:this={editorRefs[prop]}
bind:value={collection[prop]}
baseCollection={collection}
disabled={isAdminOnly(collection[prop])}
/>
<hr class="m-t-sm m-b-sm" />
<RuleField label="Create action" formKey="createRule" {collection} bind:rule={collection.createRule} />
<div class="help-block">
{#if isAdminOnly(collection[prop])}
Only admins will be able to access (unlock to change)
{:else}
Leave empty to grant everyone access
{/if}
</div>
</Field>
</div>
{/each}
<hr class="m-t-sm m-b-sm" />
<RuleField label="Update action" formKey="updateRule" {collection} bind:rule={collection.updateRule} />
<hr class="m-t-sm m-b-sm" />
<RuleField label="Delete action" formKey="deleteRule" {collection} bind:rule={collection.deleteRule} />
{#if collection?.isAuth}
<hr class="m-t-sm m-b-sm" />
<RuleField
label="Manage action"
formKey="options.manageRule"
{collection}
bind:rule={collection.options.manageRule}
>
<svelte:fragment>
<p>
This API rule gives admin-like permissions to allow fully managing the auth record(s), eg.
changing the password without requiring to enter the old one, directly updating the verified
state or email, etc.
</p>
<p>
This rule is executed in addition to the <code>create</code> and <code>update</code> API rules.
</p>
</svelte:fragment>
</RuleField>
{/if}
<style>
.rule-block {
display: flex;
align-items: flex-start;
gap: var(--xsSpacing);
}
.rule-toggle-btn {
margin-top: 15px;
}
</style>
@@ -7,17 +7,27 @@
import { errors, setErrors } from "@/stores/errors";
import { confirm } from "@/stores/confirmation";
import { addSuccessToast } from "@/stores/toasts";
import { addCollection, removeCollection, activeCollection } from "@/stores/collections";
import { addCollection, removeCollection } from "@/stores/collections";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import Toggler from "@/components/base/Toggler.svelte";
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import CollectionFieldsTab from "@/components/collections/CollectionFieldsTab.svelte";
import CollectionRulesTab from "@/components/collections/CollectionRulesTab.svelte";
import CollectionAuthOptionsTab from "@/components/collections/CollectionAuthOptionsTab.svelte";
import CollectionUpdateConfirm from "@/components/collections/CollectionUpdateConfirm.svelte";
const TAB_FIELDS = "fields";
const TAB_RULES = "api_rules";
const TAB_OPTIONS = "options";
const TYPE_BASE = "base";
const TYPE_AUTH = "auth";
const collectionTypes = {};
collectionTypes[TYPE_BASE] = "Base";
collectionTypes[TYPE_AUTH] = "Auth";
const dispatch = createEventDispatcher();
let collectionPanel;
@@ -42,6 +52,11 @@
$: canSave = collection.isNew || hasChanges;
$: if (activeTab === TAB_OPTIONS && collection.type !== TYPE_AUTH) {
// reset selected tab
changeTab(TAB_FIELDS);
}
export function changeTab(newTab) {
activeTab = newTab;
}
@@ -111,11 +126,10 @@
);
addCollection(result);
if (collection.isNew) {
$activeCollection = result;
}
dispatch("save", result);
dispatch("save", {
isNew: collection.isNew,
collection: result,
});
})
.catch((err) => {
ApiClient.errorResponseHandler(err);
@@ -163,11 +177,15 @@
function calculateFormHash(m) {
return JSON.stringify(m);
}
function setCollectionType(t) {
collection.type = t;
}
</script>
<OverlayPanel
bind:this={collectionPanel}
class="overlay-panel-lg colored-header compact-header collection-panel"
class="overlay-panel-lg colored-header collection-panel"
beforeHide={() => {
if (hasChanges && confirmClose) {
confirm("You have unsaved changes. Do you really want to close the panel?", () => {
@@ -191,7 +209,11 @@
<button type="button" class="btn btn-sm btn-circle btn-secondary flex-gap-0">
<i class="ri-more-line" />
<Toggler class="dropdown dropdown-right m-t-5">
<button type="button" class="dropdown-item closable" on:click={() => deleteConfirm()}>
<button
type="button"
class="dropdown-item txt-danger closable"
on:click|preventDefault|stopPropagation={() => deleteConfirm()}
>
<i class="ri-delete-bin-7-line" />
<span class="txt">Delete</span>
</button>
@@ -206,11 +228,12 @@
}}
>
<Field
class="form-field required m-b-0 {isSystemUpdate ? 'disabled' : ''}"
class="form-field collection-field-name required m-b-0 {isSystemUpdate ? 'disabled' : ''}"
name="name"
let:uniqueId
>
<label for={uniqueId}>Name</label>
<!-- svelte-ignore a11y-autofocus -->
<input
type="text"
@@ -226,6 +249,35 @@
e.target.value = collection.name;
}}
/>
<div class="form-field-addon">
<button
type="button"
class="btn btn-sm p-r-10 p-l-10 {collection.isNew ? 'btn-hint' : 'btn-secondary'}"
disabled={!collection.isNew}
>
<!-- empty span for alignment -->
<span />
<span class="txt">Type: {collectionTypes[collection.type] || "N/A"}</span>
{#if collection.isNew}
<i class="ri-arrow-down-s-fill" />
<Toggler class="dropdown dropdown-right dropdown-nowrap m-t-5">
{#each Object.entries(collectionTypes) as [type, label]}
<button
type="button"
class="dropdown-item closable"
class:selected={type == collection.type}
on:click={() => setCollectionType(type)}
>
<i class={CommonHelper.getCollectionTypeIcon(type)} />
<span class="txt">{label} collection</span>
</button>
{/each}
</Toggler>
{/if}
</button>
</div>
{#if collection.system}
<div class="help-block">System collection</div>
{/if}
@@ -258,7 +310,7 @@
on:click={() => changeTab(TAB_RULES)}
>
<span class="txt">API Rules</span>
{#if !CommonHelper.isEmpty($errors?.listRule) || !CommonHelper.isEmpty($errors?.viewRule) || !CommonHelper.isEmpty($errors?.createRule) || !CommonHelper.isEmpty($errors?.updateRule) || !CommonHelper.isEmpty($errors?.deleteRule)}
{#if !CommonHelper.isEmpty($errors?.listRule) || !CommonHelper.isEmpty($errors?.viewRule) || !CommonHelper.isEmpty($errors?.createRule) || !CommonHelper.isEmpty($errors?.updateRule) || !CommonHelper.isEmpty($errors?.deleteRule) || !CommonHelper.isEmpty($errors?.options?.manageRule)}
<i
class="ri-error-warning-fill txt-danger"
transition:scale|local={{ duration: 150, start: 0.7 }}
@@ -266,6 +318,24 @@
/>
{/if}
</button>
{#if collection.isAuth}
<button
type="button"
class="tab-item"
class:active={activeTab === TAB_OPTIONS}
on:click={() => changeTab(TAB_OPTIONS)}
>
<span class="txt">Options</span>
{#if !CommonHelper.isEmpty($errors?.options) && !$errors?.options?.manageRule}
<i
class="ri-error-warning-fill txt-danger"
transition:scale|local={{ duration: 150, start: 0.7 }}
use:tooltip={"Has errors"}
/>
{/if}
</button>
{/if}
</div>
</svelte:fragment>
@@ -280,6 +350,12 @@
<CollectionRulesTab bind:collection />
</div>
{/if}
{#if collection.isAuth}
<div class="tab-item" class:active={activeTab === TAB_OPTIONS}>
<CollectionAuthOptionsTab bind:collection />
</div>
{/if}
</div>
<svelte:fragment slot="footer">
@@ -302,6 +378,6 @@
<style>
.tabs-content {
z-index: 3; /* autocomplete dropdown overlay fix */
z-index: auto; /* autocomplete dropdown overlay fix */
}
</style>
@@ -1,4 +1,6 @@
<script>
import { link } from "svelte-spa-router";
import CommonHelper from "@/utils/CommonHelper";
import { hideControls } from "@/stores/app";
import { collections, activeCollection } from "@/stores/collections";
import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte";
@@ -12,15 +14,27 @@
$: filteredCollections = $collections.filter((collection) => {
return (
collection.name != import.meta.env.PB_PROFILE_COLLECTION &&
(collection.id == searchTerm ||
collection.name.replace(/\s+/g, "").toLowerCase().includes(normalizedSearch))
collection.id == searchTerm ||
collection.name.replace(/\s+/g, "").toLowerCase().includes(normalizedSearch)
);
});
$: if ($collections) {
scrollIntoView();
}
function selectCollection(collection) {
$activeCollection = collection;
}
function scrollIntoView() {
setTimeout(() => {
const activeItem = document.querySelector(".collection-sidebar .sidebar-list-item.active");
if (activeItem) {
activeItem?.scrollIntoView({ block: "nearest" });
}
}, 0);
}
</script>
<aside class="page-sidebar collection-sidebar">
@@ -42,21 +56,18 @@
<hr class="m-t-5 m-b-xs" />
<div class="sidebar-content">
<div class="sidebar-content" class:sidebar-content-compact={filteredCollections.length > 20}>
{#each filteredCollections as collection (collection.id)}
<div
tabindex="0"
<a
href="/collections?collectionId={collection.id}"
class="sidebar-list-item"
class:active={$activeCollection?.id === collection.id}
on:click={() => selectCollection(collection)}
use:link
>
{#if $activeCollection?.id === collection.id}
<i class="ri-folder-open-line" />
{:else}
<i class="ri-folder-2-line" />
{/if}
<i class={CommonHelper.getCollectionTypeIcon(collection.type)} />
<span class="txt">{collection.name}</span>
</div>
</a>
{:else}
{#if normalizedSearch.length}
<p class="txt-hint m-t-10 m-b-10 txt-center">No collections found.</p>
@@ -74,4 +85,11 @@
{/if}
</aside>
<CollectionUpsertPanel bind:this={collectionPanel} />
<CollectionUpsertPanel
bind:this={collectionPanel}
on:save={(e) => {
if (e.detail?.isNew && e.detail.collection) {
selectCollection(e.detail.collection);
}
}}
/>
@@ -121,9 +121,15 @@
on:expand
on:collapse
on:toggle
on:dragenter
on:dragleave
on:dragstart
on:drop
draggable
single
{interactive}
class={disabled || field.toDelete || field.system ? "field-accordion disabled" : "field-accordion"}
{...$$restProps}
>
<svelte:fragment slot="header">
<div class="inline-flex">
@@ -144,7 +150,7 @@
<span class="label" class:label-warning={interactive && !field.toDelete}>New</span>
{/if}
{#if field.required}
<span class="label label-success">Required</span>
<span class="label label-success">Nonempty</span>
{/if}
{#if field.unique}
<span class="label label-success">Unique</span>
@@ -177,6 +183,11 @@
<form
class="field-form"
on:dragstart={(e) => {
e.stopPropagation();
e.preventDefault();
e.stopImmediatePropagation();
}}
on:submit|preventDefault={() => {
canBeStored && collapse();
}}
@@ -192,6 +203,7 @@
<FieldTypeSelect id={uniqueId} disabled={field.id} bind:value={field.type} />
</Field>
</div>
<div class="col-sm-6">
<Field
class="
@@ -257,7 +269,18 @@
<div class="col-sm-4 flex">
<Field class="form-field form-field-toggle m-0" name="requried" let:uniqueId>
<input type="checkbox" id={uniqueId} bind:checked={field.required} />
<label for={uniqueId}>Required</label>
<label for={uniqueId}>
<span class="txt">Nonempty</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: `Requires the field value to be nonempty\n(aka. not ${CommonHelper.zeroDefaultStr(
field
)}).`,
position: "right",
}}
/>
</label>
</Field>
</div>
@@ -0,0 +1,121 @@
<script context="module">
let cachedRuleComponent;
</script>
<script>
import { tick } from "svelte";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
export let collection = null;
export let rule = null;
export let label = "Rule";
export let formKey = "rule";
export let required = false;
let editorRef = null;
let tempValue = null;
let ruleInputComponent = cachedRuleComponent;
let isRuleComponentLoading = false;
$: isAdminOnly = rule === null;
async function loadEditorComponent() {
if (ruleInputComponent || isRuleComponentLoading) {
return; // already loaded or in the process
}
isRuleComponentLoading = true;
ruleInputComponent = (await import("@/components/base/FilterAutocompleteInput.svelte")).default;
cachedRuleComponent = ruleInputComponent;
isRuleComponentLoading = false;
}
loadEditorComponent();
</script>
{#if isRuleComponentLoading}
<div class="txt-center">
<span class="loader" />
</div>
{:else}
<div class="rule-block">
{#if isAdminOnly}
<button
type="button"
class="rule-toggle-btn btn btn-circle btn-outline btn-success"
use:tooltip={{
text: "Unlock and set custom rule",
position: "left",
}}
on:click={async () => {
rule = tempValue || "";
await tick();
editorRef?.focus();
}}
>
<i class="ri-lock-unlock-line" />
</button>
{:else}
<button
type="button"
class="rule-toggle-btn btn btn-circle btn-outline"
use:tooltip={{
text: "Lock and set to Admins only",
position: "left",
}}
on:click={() => {
tempValue = rule;
rule = null;
}}
>
<i class="ri-lock-line" />
</button>
{/if}
<Field
class="form-field rule-field m-0 {required ? 'requied' : ''} {isAdminOnly ? 'disabled' : ''}"
name={formKey}
let:uniqueId
>
<label for={uniqueId}>
{label} - {isAdminOnly ? "Admins only" : "Custom rule"}
</label>
<svelte:component
this={ruleInputComponent}
id={uniqueId}
bind:this={editorRef}
bind:value={rule}
baseCollection={collection}
disabled={isAdminOnly}
/>
<div class="help-block">
<slot {isAdminOnly}>
<p>
{#if isAdminOnly}
Only admins will be able to perform this action (unlock to change)
{:else}
Leave empty to grant everyone access
{/if}
</p>
</slot>
</div>
</Field>
</div>
{/if}
<style>
.rule-block {
display: flex;
align-items: flex-start;
gap: var(--xsSpacing);
}
.rule-toggle-btn {
margin-top: 15px;
}
</style>
@@ -0,0 +1,110 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 200,
body: `
{
"usernamePassword": true,
"emailPassword": true,
"authProviders": [
{
"name": "github",
"state": "3Yd8jNkK_6PJG6hPWwBjLqKwse6Ejd",
"codeVerifier": "KxFDWz1B3fxscCDJ_9gHQhLuh__ie7",
"codeChallenge": "NM1oVexB6Q6QH8uPtOUfK7tq4pmu4Jz6lNDIwoxHZNE=",
"codeChallengeMethod": "S256",
"authUrl": "https://github.com/login/oauth/authorize?client_id=demo&code_challenge=NM1oVexB6Q6QH8uPtOUfK7tq4pmu4Jz6lNDIwoxHZNE%3D&code_challenge_method=S256&response_type=code&scope=user&state=3Yd8jNkK_6PJG6hPWwBjLqKwse6Ejd&redirect_uri="
},
{
"name": "gitlab",
"state": "NeQSbtO5cShr_mk5__3CUukiMnymeb",
"codeVerifier": "ahTFHOgua8mkvPAlIBGwCUJbWKR_xi",
"codeChallenge": "O-GATkTj4eXDCnfonsqGLCd6njvTixlpCMvy5kjgOOg=",
"codeChallengeMethod": "S256",
"authUrl": "https://gitlab.com/oauth/authorize?client_id=demo&code_challenge=O-GATkTj4eXDCnfonsqGLCd6njvTixlpCMvy5kjgOOg%3D&code_challenge_method=S256&response_type=code&scope=read_user&state=NeQSbtO5cShr_mk5__3CUukiMnymeb&redirect_uri="
},
{
"name": "google",
"state": "zB3ZPifV1TW2GMuvuFkamSXfSNkHPQ",
"codeVerifier": "t3CmO5VObGzdXqieakvR_fpjiW0zdO",
"codeChallenge": "KChwoQPKYlz2anAdqtgsSTdIo8hdwtc1fh2wHMwW2Yk=",
"codeChallengeMethod": "S256",
"authUrl": "https://accounts.google.com/o/oauth2/auth?client_id=demo&code_challenge=KChwoQPKYlz2anAdqtgsSTdIo8hdwtc1fh2wHMwW2Yk%3D&code_challenge_method=S256&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&state=zB3ZPifV1TW2GMuvuFkamSXfSNkHPQ&redirect_uri="
}
]
}
`,
},
];
</script>
<h3 class="m-b-sm">List auth methods ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Returns a public list with all allowed <strong>{collection.name}</strong> authentication methods.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const result = await pb.collection('${collection?.name}').listAuthMethods();
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final result = await pb.collection('${collection?.name}').listAuthMethods();
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-info">
<strong class="label label-primary">GET</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/auth-methods
</p>
</div>
</div>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -0,0 +1,169 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 200,
body: JSON.stringify(
{
token: "JWT_TOKEN",
record: CommonHelper.dummyCollectionRecord(collection),
},
null,
2
),
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"identity": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
{
code: 401,
body: `
{
"code": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}
`,
},
{
code: 403,
body: `
{
"code": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}
`,
},
];
</script>
<h3 class="m-b-sm">Auth refresh ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>
Returns a new auth response (token and account data) for an
<strong>already authenticated record</strong>.
</p>
<p>
<em>
This method is usually called by users on page/screen reload to ensure that the previously stored
data in <code>pb.authStore</code> is still valid and up-to-date.
</em>
</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const authData = await pb.collection('${collection?.name}').authRefresh();
// after the above you can also access the refreshed auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final authData = await pb.collection('${collection?.name}').authRefresh();
// after the above you can also access the refreshed auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.model.id);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/auth-refresh
</p>
</div>
<p class="txt-hint txt-sm txt-right">Requires record <code>Authorization:TOKEN</code> header</p>
</div>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>expand</td>
<td>
<span class="label">String</span>
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -0,0 +1,261 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 200,
body: JSON.stringify(
{
token: "JWT_TOKEN",
record: CommonHelper.dummyCollectionRecord(collection),
meta: {
id: "abc123",
name: "John Doe",
username: "john.doe",
email: "test@example.com",
avatarUrl: "https://example.com/avatar.png",
},
},
null,
2
),
},
{
code: 400,
body: `
{
"code": 400,
"message": "An error occurred while submitting the form.",
"data": {
"provider": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
</script>
<h3 class="m-b-sm">Auth with OAuth2 ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Authenticate with an OAuth2 provider and returns a new auth token and account data.</p>
<p>This action usually should be called right after the provider login page redirect.</p>
<p>
You could also check the
<a href={import.meta.env.PB_OAUTH2_EXAMPLE} target="_blank" rel="noopener noreferrer">
OAuth2 web integration example
</a>.
</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const authData = await pb.collection('${collection?.name}').authWithOAuth2(
'google',
'CODE',
'VERIFIER',
'REDIRECT_URL',
// optional data that will be used for the new account on OAuth2 sign-up
{
'name': 'test',
},
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
// "logout" the last authenticated account
pb.authStore.clear();
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final authData = await pb.collection('${collection?.name}').authWithOAuth2(
'google',
'CODE',
'VERIFIER',
'REDIRECT_URL',
// optional data that will be used for the new account on OAuth2 sign-up
createData: {
'name': 'test',
},
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.model.id);
// "logout" the last authenticated account
pb.authStore.clear();
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/auth-with-oauth2
</p>
</div>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>provider</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The name of the OAuth2 client provider (eg. "google").</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>code</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The authorization code returned from the initial request.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>codeVerifier</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The code verifier sent with the initial request as part of the code_challenge.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>redirectUrl</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The redirect url sent with the initial request.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>createData</span>
</div>
</td>
<td>
<span class="label">Object</span>
</td>
<td>
<p>Optional data that will be used when creating the auth record on OAuth2 sign-up.</p>
<p>
The created auth record must comply with the same requirements and validations in the
regular <strong>create</strong> action.
<br />
<em>
The data can only be in <code>json</code>, aka. <code>multipart/form-data</code> and files
upload currently are not supported during OAuth2 sign-ups.
</em>
</p>
</td>
</tr>
</tbody>
</table>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>expand</td>
<td>
<span class="label">String</span>
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -0,0 +1,223 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: allowEmail = collection?.options?.allowEmailAuth;
$: allowUsername = collection?.options?.allowUsernameAuth;
$: exampleIdentityLabel =
allowUsername && allowEmail
? "YOUR_USERNAME_OR_EMAIL"
: allowUsername
? "YOUR_USERNAME"
: "YOUR_EMAIL";
$: responses = [
{
code: 200,
body: JSON.stringify(
{
token: "JWT_TOKEN",
record: CommonHelper.dummyCollectionRecord(collection),
},
null,
2
),
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"identity": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
</script>
<h3 class="m-b-sm">Auth with password ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>
Returns new auth token and account data by a combination of
<strong>
{#if allowUsername && allowEmail}
username/email
{:else if allowUsername}
username
{:else if allowEmail}
email
{/if}
</strong>
and <strong>password</strong>.
</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const authData = await pb.collection('${collection?.name}').authWithPassword(
'${exampleIdentityLabel}',
'YOUR_PASSWORD',
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
// "logout" the last authenticated account
pb.authStore.clear();
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final authData = await pb.collection('${collection?.name}').authWithPassword(
'${exampleIdentityLabel}',
'YOUR_PASSWORD',
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.model.id);
// "logout" the last authenticated account
pb.authStore.clear();
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/auth-with-password
</p>
</div>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>identity</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>
The
{#if allowUsername}
<strong>username</strong>
{/if}
{#if allowUsername && allowEmail}
or
{/if}
{#if allowEmail}
<strong>email</strong>
{/if}
of the record to authenticate.
</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>password</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The auth record password.</td>
</tr>
</tbody>
</table>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>expand</td>
<td>
<span class="label">String</span>
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -1,111 +0,0 @@
<script>
import { Collection } from "pocketbase";
import OverlayPanel from "@/components/base/OverlayPanel.svelte";
import ListApiDocs from "@/components/collections/docs/ListApiDocs.svelte";
import ViewApiDocs from "@/components/collections/docs/ViewApiDocs.svelte";
import CreateApiDocs from "@/components/collections/docs/CreateApiDocs.svelte";
import UpdateApiDocs from "@/components/collections/docs/UpdateApiDocs.svelte";
import DeleteApiDocs from "@/components/collections/docs/DeleteApiDocs.svelte";
import RealtimeApiDocs from "@/components/collections/docs/RealtimeApiDocs.svelte";
const tabs = [
{
id: "list",
label: "List",
component: ListApiDocs,
},
{
id: "view",
label: "View",
component: ViewApiDocs,
},
{
id: "create",
label: "Create",
component: CreateApiDocs,
},
{
id: "update",
label: "Update",
component: UpdateApiDocs,
},
{
id: "delete",
label: "Delete",
component: DeleteApiDocs,
},
{
id: "realtime",
label: "Realtime",
component: RealtimeApiDocs,
},
];
let collectionPanel;
let collection = new Collection();
let activeTab = tabs[0].id;
export function show(model) {
collection = model;
changeTab(tabs[0].id);
return collectionPanel?.show();
}
export function hide() {
return collectionPanel?.hide();
}
export function changeTab(newTab) {
activeTab = newTab;
}
function changeTabViaKey(e, newTab) {
if (e.code === "Enter" || e.code === "Space") {
e.preventDefault();
changeTab(newTab);
}
}
</script>
<OverlayPanel
bind:this={collectionPanel}
on:hide
on:show
class="overlay-panel-xl colored-header collection-panel"
>
<svelte:fragment slot="header">
<h4><strong>{collection.name}</strong> records API</h4>
<div class="tabs-header stretched">
{#each tabs as tab (tab.id)}
<button
tabindex="0"
class="tab-item"
class:active={activeTab === tab.id}
on:click={() => changeTab(tab.id)}
on:keydown|self={(e) => changeTabViaKey(e, tab.id)}
>
<span class="txt">{tab.label}</span>
</button>
{/each}
</div>
</svelte:fragment>
<div class="tabs-content">
{#each tabs as tab (tab.id)}
{#if activeTab === tab.id}
<div class="tab-item active">
<svelte:component this={tab.component} {collection} />
</div>
{/if}
{/each}
</div>
<svelte:fragment slot="footer">
<button type="button" class="btn btn-secondary" on:click={() => hide()}>
<span class="txt">Close</span>
</button>
</svelte:fragment>
</OverlayPanel>
@@ -0,0 +1,183 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 200,
body: JSON.stringify(
{
token: "JWT_TOKEN",
record: CommonHelper.dummyCollectionRecord(collection),
},
null,
2
),
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
</script>
<h3 class="m-b-sm">Confirm email change ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Confirms <strong>{collection.name}</strong> email change request.</p>
<p>Returns the refreshed auth data.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const authData = await pb.collection('${collection?.name}').confirmEmailChange(
'TOKEN',
'YOUR_PASSWORD',
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final authData = await pb.collection('${collection?.name}').confirmEmailChange(
'TOKEN',
'YOUR_PASSWORD',
);
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/confirm-email-change
</p>
</div>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>token</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The token from the change email request email.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>password</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The account password to confirm the email change.</td>
</tr>
</tbody>
</table>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>expand</td>
<td>
<span class="label">String</span>
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -0,0 +1,197 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 200,
body: JSON.stringify(
{
token: "JWT_TOKEN",
record: CommonHelper.dummyCollectionRecord(collection),
},
null,
2
),
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
</script>
<h3 class="m-b-sm">Confirm password reset ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Confirms <strong>{collection.name}</strong> password reset request.</p>
<p>Returns the refreshed auth data.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const authData = await pb.collection('${collection?.name}').confirmPasswordReset(
'TOKEN',
'NEW_PASSWORD',
'NEW_PASSWORD_CONFIRM',
);
// after the above you can also access the refreshed auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final authData = await pb.collection('${collection?.name}').confirmPasswordReset(
'TOKEN',
'NEW_PASSWORD',
'NEW_PASSWORD_CONFIRM',
);
// after the above you can also access the refreshed auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/confirm-password-reset
</p>
</div>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>token</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The token from the password reset request email.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>password</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The new password to set.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>passwordConfirm</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The new password confirmation.</td>
</tr>
</tbody>
</table>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>expand</td>
<td>
<span class="label">String</span>
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -0,0 +1,165 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 200,
body: JSON.stringify(
{
token: "JWT_TOKEN",
record: CommonHelper.dummyCollectionRecord(collection),
},
null,
2
),
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
</script>
<h3 class="m-b-sm">Confirm verification ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Confirms <strong>{collection.name}</strong> account verification request.</p>
<p>Returns the refreshed auth data.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const authData = await pb.collection('${collection?.name}').confirmVerification('TOKEN');
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final authData = await pb.collection('${collection?.name}').confirmVerification('TOKEN');
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/confirm-verification
</p>
</div>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>token</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The token from the verification request email.</td>
</tr>
</tbody>
</table>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>expand</td>
<td>
<span class="label">String</span>
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -9,6 +9,7 @@
let responseTab = 200;
let responses = [];
let baseData = {};
$: adminsOnly = collection?.createRule === null;
@@ -45,8 +46,66 @@
`,
},
];
$: if (collection.isAuth) {
baseData = {
username: "test_username",
email: "test@exampe.com",
emailVisibility: true,
password: "12345678",
passwordConfirm: "12345678",
};
} else {
baseData = {};
}
</script>
<h3 class="m-b-sm">Create ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Create a new <strong>{collection.name}</strong> record.</p>
<p>
Body parameters could be sent as <code>application/json</code> or
<code>multipart/form-data</code>.
</p>
<p>
File upload is supported only via <code>multipart/form-data</code>.
<br />
For more info and examples you could check the detailed
<a href={import.meta.env.PB_FILE_UPLOAD_DOCS} target="_blank" rel="noopener noreferrer">
Files upload and handling docs
</a>.
</p>
</div>
<!-- prettier-ignore -->
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
// example create data
const data = ${JSON.stringify(Object.assign({}, baseData, CommonHelper.dummyCollectionSchemaData(collection)), null, 4)};
const record = await pb.collection('${collection?.name}').create(data);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
// example create body
final body = <String, dynamic>${JSON.stringify(Object.assign({}, baseData, CommonHelper.dummyCollectionSchemaData(collection)), null, 2)};
final record = await pb.collection('${collection?.name}').create(body: body);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
@@ -55,49 +114,12 @@
</p>
</div>
{#if adminsOnly}
<p class="txt-hint txt-sm txt-right">Requires <code>Authorization: Admin TOKEN</code> header</p>
<p class="txt-hint txt-sm txt-right">Requires admin <code>Authorization:TOKEN</code> header</p>
{/if}
</div>
<div class="content m-b-base">
<p>Create a new <strong>{collection.name}</strong> record.</p>
<p>
Body parameters could be sent as <code>application/json</code> or
<code>multipart/form-data</code>.
</p>
<p>
File upload is supported only via <code>multipart/form-data</code>.
</p>
</div>
<div class="section-title">Client SDKs example</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const client = new PocketBase('${backendAbsUrl}');
...
const data = { ... };
const record = await client.records.create('${collection?.name}', data);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final client = PocketBase('${backendAbsUrl}');
...
final body = <String, dynamic>{ ... };
final record = await client.records.create('${collection?.name}', body: body);
`}
/>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -122,6 +144,100 @@
If not set, it will be auto generated.
</td>
</tr>
{#if collection?.isAuth}
<tr>
<td colspan="3" class="txt-hint">Auth fields</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>username</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>
The username of the auth record.
<br />
If not set, it will be auto generated.
</td>
</tr>
<tr>
<td>
<div class="inline-flex">
{#if collection?.options?.requireEmail}
<span class="label label-success">Required</span>
{:else}
<span class="label label-warning">Optional</span>
{/if}
<span>email</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>Auth record email address.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>emailVisibility</span>
</div>
</td>
<td>
<span class="label">Boolean</span>
</td>
<td>Whether to show/hide the auth record email when fetching the record data.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>password</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>Auth record password.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>passwordConfirm</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>Auth record password confirmation.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>verified</span>
</div>
</td>
<td>
<span class="label">Boolean</span>
</td>
<td>
Indicates whether the auth record is verified or not.
<br />
This field can be set only by admins or auth records with "Manage" access.
</td>
</tr>
<tr>
<td colspan="3" class="txt-hint">Schema fields</td>
</tr>
{/if}
{#each collection?.schema as field (field.name)}
<tr>
<td>
@@ -152,9 +268,7 @@
File object.<br />
Set to <code>null</code> to delete already uploaded file(s).
{:else if field.type === "relation"}
Relation record {field.options?.maxSelect > 1 ? "ids" : "id"}.
{:else if field.type === "user"}
User {field.options?.maxSelect > 1 ? "ids" : "id"}.
Relation record {field.options?.maxSelect === 1 ? "id" : "ids"}.
{/if}
</td>
</tr>
@@ -163,7 +277,7 @@
</table>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -179,15 +293,12 @@
</td>
<td>
Auto expand relations when returning the created record. Ex.:
<CodeBlock
content={`
?expand=rel1,rel2.subrel21.subrel22
`}
/>
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>@expand</code> property (eg. <code>{`"@expand": {"rel1": {...}, ...}`}</code>). Only the
relations that the user has permissions to <strong>view</strong> will be expanded.
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
@@ -59,6 +59,33 @@
}
</script>
<h3 class="m-b-sm">Delete ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Delete a single <strong>{collection.name}</strong> record.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').delete('RECORD_ID');
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').delete('RECORD_ID');
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-danger">
<strong class="label label-primary">DELETE</strong>
<div class="content">
@@ -67,38 +94,12 @@
</p>
</div>
{#if adminsOnly}
<p class="txt-hint txt-sm txt-right">Requires <code>Authorization: Admin TOKEN</code> header</p>
<p class="txt-hint txt-sm txt-right">Requires admin <code>Authorization:TOKEN</code> header</p>
{/if}
</div>
<div class="content m-b-base">
<p>Delete a single <strong>{collection.name}</strong> record.</p>
</div>
<div class="section-title">Client SDKs example</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const client = new PocketBase('${backendAbsUrl}');
...
await client.records.delete('${collection?.name}', 'RECORD_ID');
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final client = PocketBase('${backendAbsUrl}');
...
await client.records.delete('${collection?.name}', 'RECORD_ID');
`}
/>
<div class="section-title">Path parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -1,66 +1,86 @@
<p>
The syntax basically follows the format
<code>
<span class="txt-success">OPERAND</span>
<span class="txt-danger">OPERATOR</span>
<span class="txt-success">OPERAND</span></code
>, where:
</p>
<ul>
<li>
<code class="txt-success">OPERAND</code> - could be any of the above field literal, string (single or double
quoted), number, null, true, false
</li>
<li>
<code class="txt-danger">OPERATOR</code> - is one of:
<br />
<ul>
<li>
<code class="filter-op">{"="}</code>
<span class="txt-hint">Equal</span>
</li>
<li>
<code class="filter-op">{"!="}</code>
<span class="txt-hint">NOT equal</span>
</li>
<li>
<code class="filter-op">{">"}</code>
<span class="txt-hint">Greater than</span>
</li>
<li>
<code class="filter-op">{">="}</code>
<span class="txt-hint">Greater than or equal</span>
</li>
<li>
<code class="filter-op">{"<"}</code>
<span class="txt-hint">Less than or equal</span>
</li>
<li>
<code class="filter-op">{"<="}</code>
<span class="txt-hint">Less than or equal</span>
</li>
<li>
<code class="filter-op">{"~"}</code>
<span class="txt-hint">
Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for wildcard
match)
</span>
</li>
<li>
<code class="filter-op">{"!~"}</code>
<span class="txt-hint">
NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for
wildcard match)
</span>
</li>
</ul>
</li>
</ul>
<script>
let expanded = false;
<p>
To group and combine several expressions you could use brackets
<code>(...)</code>, <code>&&</code> (AND) and <code>||</code> (OR) tokens.
</p>
function toggle() {
expanded = !expanded;
}
</script>
<button class="btn btn-sm btn-secondary m-t-5" on:click={toggle}>
{#if expanded}
<span class="txt">Hide details</span>
<i class="ri-arrow-up-s-line" />
{:else}
<span class="txt">Show details</span>
<i class="ri-arrow-down-s-line" />
{/if}
</button>
{#if expanded}
<p>
The syntax basically follows the format
<code>
<span class="txt-success">OPERAND</span>
<span class="txt-danger">OPERATOR</span>
<span class="txt-success">OPERAND</span></code
>, where:
</p>
<ul>
<li>
<code class="txt-success">OPERAND</code> - could be any of the above field literal, string (single
or double quoted), number, null, true, false
</li>
<li>
<code class="txt-danger">OPERATOR</code> - is one of:
<br />
<ul>
<li>
<code class="filter-op">{"="}</code>
<span class="txt-hint">Equal</span>
</li>
<li>
<code class="filter-op">{"!="}</code>
<span class="txt-hint">NOT equal</span>
</li>
<li>
<code class="filter-op">{">"}</code>
<span class="txt-hint">Greater than</span>
</li>
<li>
<code class="filter-op">{">="}</code>
<span class="txt-hint">Greater than or equal</span>
</li>
<li>
<code class="filter-op">{"<"}</code>
<span class="txt-hint">Less than or equal</span>
</li>
<li>
<code class="filter-op">{"<="}</code>
<span class="txt-hint">Less than or equal</span>
</li>
<li>
<code class="filter-op">{"~"}</code>
<span class="txt-hint">
Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for
wildcard match)
</span>
</li>
<li>
<code class="filter-op">{"!~"}</code>
<span class="txt-hint">
NOT Like/Contains (if not specified auto wraps the right string OPERAND in a "%" for
wildcard match)
</span>
</li>
</ul>
</li>
</ul>
<p>
To group and combine several expressions you could use brackets
<code>(...)</code>, <code>&&</code> (AND) and <code>||</code> (OR) tokens.
</p>
{/if}
<style>
.filter-op {
@@ -22,6 +22,7 @@
{
page: 1,
perPage: 30,
totalPages: 1,
totalItems: 2,
items: [
CommonHelper.dummyCollectionRecord(collection),
@@ -70,6 +71,65 @@
}
</script>
<h3 class="m-b-sm">List/Search ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>
Fetch a paginated <strong>{collection.name}</strong> records list, supporting sorting and filtering.
</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
// fetch a paginated records list
const resultList = await pb.collection('${collection?.name}').getList(1, 50, {
filter: 'created >= "2022-01-01 00:00:00" && someFiled1 != someField2',
});
// you can also fetch all records at once via getFullList:
const records = await pb.collection('${collection?.name}').getFullList(200 /* batch size */, {
sort: '-created'
});
// or fetch only the first record that matches the specified filter
const record = await pb.collection('${collection?.name}').getFirstListItem('someField="test"', {
expand: 'relField1,relField2.subRelField',
});
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
// fetch a paginated records list
final result = await pb.collection('${collection?.name}').getList(
page: 1,
perPage: 50,
filter: 'created >= "2022-01-01 00:00:00" && someFiled1 != someField2',
);
// alternatively you can also fetch all records at once via getFullList:
final records = await pb.collection('${collection?.name}').getFullList(
batch: 200,
sort: '-created',
);
// or fetch only the first record that matches the specified filter
final record2 = await pb.collection('${collection?.name}').getFirstListItem(
'someField="test"',
expand: 'relField1,relField2.subRelField',
);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-info">
<strong class="label label-primary">GET</strong>
<div class="content">
@@ -78,55 +138,12 @@
</p>
</div>
{#if adminsOnly}
<p class="txt-hint txt-sm txt-right">Requires <code>Authorization: Admin TOKEN</code> header</p>
<p class="txt-hint txt-sm txt-right">Requires admin <code>Authorization:TOKEN</code> header</p>
{/if}
</div>
<div class="content m-b-base">
<p>Fetch a paginated <strong>{collection.name}</strong> records list.</p>
</div>
<div class="section-title">Client SDKs example</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const client = new PocketBase('${backendAbsUrl}');
...
// fetch a paginated records list
const resultList = await client.records.getList('${collection?.name}', 1, 50, {
filter: 'created >= "2022-01-01 00:00:00"',
});
// alternatively you can also fetch all records at once via getFullList:
const records = await client.records.getFullList('${collection?.name}', 200 /* batch size */, {
sort: '-created',
});
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final client = PocketBase('${backendAbsUrl}');
...
// fetch a paginated records list
final result = await client.records.getList(
'${collection?.name}',
page: 1,
perPage: 50,
filter: 'created >= "2022-01-01 00:00:00"',
);
// alternatively you can also fetch all records at once via getFullList:
final records = await client.records.getFullList('${collection?.name}', batch: 200, sort: '-created');
`}
/>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -188,15 +205,12 @@
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock
content={`
?expand=rel1,rel2.subrel21.subrel22
`}
/>
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to each individual record under the
<code>@expand</code> property (eg. <code>{`"@expand": {"rel1": {...}, ...}`}</code>). Only the
relations that the user has permissions to <strong>view</strong> will be expanded.
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
@@ -0,0 +1,162 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 200;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 200,
body: `
[
{
"id": "8171022dc95a4e8",
"created": "2022-09-01 10:24:18.434",
"updated": "2022-09-01 10:24:18.889",
"recordId": "e22581b6f1d44ea",
"collectionId": "${collection.id}",
"provider": "google",
"providerId": "2da15468800514p",
},
{
"id": "171022dc895a4e8",
"created": "2022-09-01 10:24:18.434",
"updated": "2022-09-01 10:24:18.889",
"recordId": "e22581b6f1d44ea",
"collectionId": "${collection.id}",
"provider": "twitter",
"providerId": "720688005140514",
}
]
`,
},
{
code: 401,
body: `
{
"code": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}
`,
},
{
code: 403,
body: `
{
"code": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}
`,
},
{
code: 404,
body: `
{
"code": 404,
"message": "The requested resource wasn't found.",
"data": {}
}
`,
},
];
</script>
<h3 class="m-b-sm">List OAuth2 accounts ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>
Returns a list with all OAuth2 providers linked to a single <strong>{collection.name}</strong>.
</p>
<p>Only admins and the account owner can access this action.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').authViaEmail('test@example.com', '123456');
const result = await pb.collection('${collection?.name}').listExternalAuths(
pb.authStore.model.id
);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').authViaEmail('test@example.com', '123456');
final result = await pb.collection('${collection?.name}').listExternalAuths(
pb.authStore.model.id,
);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-info">
<strong class="label label-primary">GET</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/records/<strong>:id</strong>/external-auths
</p>
</div>
<p class="txt-hint txt-sm txt-right">Requires <code>Authorization:TOKEN</code> header</p>
</div>
<div class="section-title">Path Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>
<span class="label">String</span>
</td>
<td>ID of the auth record.</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -10,6 +10,87 @@
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
</script>
<h3 class="m-b-sm">Realtime ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Subscribe to realtime changes via Server-Sent Events (SSE).</p>
<p>
Events are sent for <strong>create</strong>, <strong>update</strong>
and <strong>delete</strong> record operations (see "Event data format" section below).
</p>
</div>
<div class="alert alert-info m-t-10 m-b-sm">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="contet">
<p>
<strong>You could subscribe to a single record or to an entire collection.</strong>
</p>
<p>
When you subscribe to a <strong>single record</strong>, the collection's
<strong>ViewRule</strong> will be used to determine whether the subscriber has access to receive the
event message.
</p>
<p>
When you subscribe to an <strong>entire collection</strong>, the collection's
<strong>ListRule</strong> will be used to determine whether the subscriber has access to receive the
event message.
</p>
</div>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
// (Optionally) authenticate
await pb.collection('users').authWithPassword('test@example.com', '123456');
// Subscribe to changes in any record from the collection
pb.collection('${collection?.name}').subscribe(function (e) {
console.log(e.record);
});
// Subscribe to changes in a single record
pb.collection('${collection?.name}').subscribeOne('RECORD_ID', function (e) {
console.log(e.record);
});
// Unsubscribe
pb.collection('${collection?.name}').unsubscribe() // remove all collection subscriptions
pb.collection('${collection?.name}').unsubscribe('RECORD_ID') // remove only the record subscription
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
// (Optionally) authenticate
await pb.collection('users').authWithPassword('test@example.com', '123456');
// Subscribe to changes in any record from the collection
pb.collection('${collection?.name}').subscribe((e) {
print(e.record);
});
// Subscribe to changes in a single record
pb.collection('${collection?.name}').subscribeOne('RECORD_ID', (e) {
print(e.record);
});
// Unsubscribe
pb.collection('${collection?.name}').unsubscribe() // remove all collection subscriptions
pb.collection('${collection?.name}').unsubscribe('RECORD_ID') // remove only the record subscription
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert">
<strong class="label label-primary">SSE</strong>
<div class="content">
@@ -17,88 +98,6 @@
</div>
</div>
<div class="content m-b-base">
<p>Subscribe to realtime changes via Server-Sent Events (SSE).</p>
<p>
Events are sent for <strong>create</strong>, <strong>update</strong>
and <strong>delete</strong> record operations (see "Event data format" section below).
</p>
<div class="alert alert-info m-t-10">
<div class="icon">
<i class="ri-information-line" />
</div>
<div class="contet">
<p>
<strong>You could subscribe to a single record or to an entire collection.</strong>
</p>
<p>
When you subscribe to a <strong>single record</strong>, the collection's
<strong>ViewRule</strong> will be used to determine whether the subscriber has access to receive
the event message.
</p>
<p>
When you subscribe to an <strong>entire collection</strong>, the collection's
<strong>ListRule</strong> will be used to determine whether the subscriber has access to receive
the event message.
</p>
</div>
</div>
</div>
<div class="section-title">Client SDKs example</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const client = new PocketBase('${backendAbsUrl}');
...
// (Optionally) authenticate
client.users.authViaEmail('test@example.com', '123456');
// Subscribe to changes in any record from the collection
client.realtime.subscribe('${collection?.name}', function (e) {
console.log(e.record);
});
// Subscribe to changes in a single record
client.realtime.subscribe('${collection?.name}/RECORD_ID', function (e) {
console.log(e.record);
});
// Unsubscribe
client.realtime.unsubscribe() // remove all subscriptions
client.realtime.unsubscribe('${collection?.name}') // remove only the collection subscription
client.realtime.unsubscribe('${collection?.name}/RECORD_ID') // remove only the record subscription
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final client = PocketBase('${backendAbsUrl}');
...
// (Optionally) authenticate
client.users.authViaEmail('test@example.com', '123456');
// Subscribe to changes in any record from the collection
client.realtime.subscribe('${collection?.name}', (e) {
print(e.record);
});
// Subscribe to changes in a single record
client.realtime.subscribe('${collection?.name}/RECORD_ID', (e) {
print(e.record);
});
// Unsubscribe
client.realtime.unsubscribe() // remove all subscriptions
client.realtime.unsubscribe('${collection?.name}') // remove only the collection subscription
client.realtime.unsubscribe('${collection?.name}/RECORD_ID') // remove only the record subscription
`}
/>
<div class="section-title">Event data format</div>
<CodeBlock
content={JSON.stringify(
@@ -0,0 +1,144 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 204;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 204,
body: "null",
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"newEmail": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
{
code: 401,
body: `
{
"code": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}
`,
},
{
code: 403,
body: `
{
"code": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}
`,
},
];
</script>
<h3 class="m-b-sm">Request email change ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Sends <strong>{collection.name}</strong> email change request.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').authViaEmail('test@example.com', '123456');
await pb.collection('${collection?.name}').requestEmailChange('new@example.com');
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').authViaEmail('test@example.com', '123456');
await pb.collection('${collection?.name}').requestEmailChange('new@example.com');
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/confirm-email-change
</p>
</div>
<p class="txt-hint txt-sm txt-right">Requires record <code>Authorization:TOKEN</code> header</p>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>newEmail</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The new email address to send the change email request.</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -0,0 +1,119 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 204;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 204,
body: "null",
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
</script>
<h3 class="m-b-sm">Request password reset ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Sends <strong>{collection.name}</strong> password reset email request.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').requestPasswordReset('test@example.com');
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').requestPasswordReset('test@example.com');
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/request-password-reset
</p>
</div>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>email</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The auth record email address to send the password reset request (if exists).</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -0,0 +1,119 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 204;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 204,
body: "null",
},
{
code: 400,
body: `
{
"code": 400,
"message": "Failed to authenticate.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
</script>
<h3 class="m-b-sm">Request verification ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Sends <strong>{collection.name}</strong> verification email request.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').requestVerification('test@example.com');
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').requestVerification('test@example.com');
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-success">
<strong class="label label-primary">POST</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/request-password-reset
</p>
</div>
</div>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="50%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<div class="inline-flex">
<span class="label label-success">Required</span>
<span>email</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The auth record email address to send the verification request (if exists).</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -18,16 +18,18 @@
title: "JavaScript",
language: "javascript",
content: js,
url: import.meta.env.PB_JS_SDK_URL,
},
{
title: "Dart",
language: "dart",
content: dart,
url: import.meta.env.PB_DART_SDK_URL,
},
];
</script>
<div class="tabs sdk-tabs m-b-lg">
<div class="tabs sdk-tabs m-b-base">
<div class="tabs-header compact left">
{#each sdkExamples as example (example.language)}
<button
@@ -43,6 +45,13 @@
{#each sdkExamples as example (example.language)}
<div class="tab-item" class:active={activeTab === example.language}>
<CodeBlock language={example.language} content={example.content} />
<div class="txt-right">
<em class="txt-sm txt-hint">
<a href={example.url} target="_blank" rel="noopener noreferrer">
{example.title} SDK
</a>
</em>
</div>
</div>
{/each}
</div>
@@ -0,0 +1,154 @@
<script>
import { Collection } from "pocketbase";
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import CodeBlock from "@/components/base/CodeBlock.svelte";
import SdkTabs from "@/components/collections/docs/SdkTabs.svelte";
export let collection = new Collection();
let responseTab = 204;
let responses = [];
$: backendAbsUrl = CommonHelper.getApiExampleUrl(ApiClient.baseUrl);
$: responses = [
{
code: 204,
body: "null",
},
{
code: 401,
body: `
{
"code": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}
`,
},
{
code: 403,
body: `
{
"code": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}
`,
},
{
code: 404,
body: `
{
"code": 404,
"message": "The requested resource wasn't found.",
"data": {}
}
`,
},
];
</script>
<h3 class="m-b-sm">Unlink OAuth2 account ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>
Unlink a single external OAuth2 provider from <strong>{collection.name}</strong> record.
</p>
<p>Only admins and the account owner can access this action.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').authViaEmail('test@example.com', '123456');
await pb.collection('${collection?.name}').unlinkExternalAuth(
pb.authStore.model.id,
'google'
);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
await pb.collection('${collection?.name}').authViaEmail('test@example.com', '123456');
await pb.collection('${collection?.name}').unlinkExternalAuth(
pb.authStore.model.id,
'google',
);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-danger">
<strong class="label label-primary">DELETE</strong>
<div class="content">
<p>
/api/collections/<strong>{collection.name}</strong>/records/<strong>:id</strong
>/external-auths/<strong>:provider</strong>
</p>
</div>
<p class="txt-hint txt-sm txt-right">Requires <code>Authorization:TOKEN</code> header</p>
</div>
<div class="section-title">Path Parameters</div>
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
<th>Type</th>
<th width="60%">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>id</td>
<td>
<span class="label">String</span>
</td>
<td>ID of the auth record.</td>
</tr>
<tr>
<td>provider</td>
<td>
<span class="label">String</span>
</td>
<td>
The name of the auth provider to unlink, eg. <code>google</code>, <code>twitter</code>,
<code>github</code>, etc.
</td>
</tr>
</tbody>
</table>
<div class="section-title">Responses</div>
<div class="tabs">
<div class="tabs-header compact left">
{#each responses as response (response.code)}
<button
class="tab-item"
class:active={responseTab === response.code}
on:click={() => (responseTab = response.code)}
>
{response.code}
</button>
{/each}
</div>
<div class="tabs-content">
{#each responses as response (response.code)}
<div class="tab-item" class:active={responseTab === response.code}>
<CodeBlock content={response.body} />
</div>
{/each}
</div>
</div>
@@ -9,6 +9,7 @@
let responseTab = 200;
let responses = [];
let baseData = {};
$: adminsOnly = collection?.updateRule === null;
@@ -55,8 +56,66 @@
`,
},
];
$: if (collection.isAuth) {
baseData = {
username: "test_username_update",
emailVisibility: false,
password: "87654321",
passwordConfirm: "87654321",
oldPassword: "12345678",
};
} else {
baseData = {};
}
</script>
<h3 class="m-b-sm">Update ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Update a single <strong>{collection.name}</strong> record.</p>
<p>
Body parameters could be sent as <code>application/json</code> or
<code>multipart/form-data</code>.
</p>
<p>
File upload is supported only via <code>multipart/form-data</code>.
<br />
For more info and examples you could check the detailed
<a href={import.meta.env.PB_FILE_UPLOAD_DOCS} target="_blank" rel="noopener noreferrer">
Files upload and handling docs
</a>.
</p>
</div>
<!-- prettier-ignore -->
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
// example update data
const data = ${JSON.stringify(Object.assign({}, baseData, CommonHelper.dummyCollectionSchemaData(collection)), null, 4)};
const record = await pb.collection('${collection?.name}').update('RECORD_ID', data);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
// example update body
final body = <String, dynamic>${JSON.stringify(Object.assign({}, baseData, CommonHelper.dummyCollectionSchemaData(collection)), null, 2)};
final record = await pb.collection('${collection?.name}').update('RECORD_ID', body: body);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-warning">
<strong class="label label-primary">PATCH</strong>
<div class="content">
@@ -65,49 +124,12 @@
</p>
</div>
{#if adminsOnly}
<p class="txt-hint txt-sm txt-right">Requires <code>Authorization: Admin TOKEN</code> header</p>
<p class="txt-hint txt-sm txt-right">Requires admin <code>Authorization:TOKEN</code> header</p>
{/if}
</div>
<div class="content m-b-base">
<p>Update a single <strong>{collection.name}</strong> record.</p>
<p>
Body parameters could be sent as <code>application/json</code> or
<code>multipart/form-data</code>.
</p>
<p>
File upload is supported only via <code>multipart/form-data</code>.
</p>
</div>
<div class="section-title">Client SDKs example</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const client = new PocketBase('${backendAbsUrl}');
...
const data = { ... };
const record = await client.records.update('${collection?.name}', 'RECORD_ID', data);
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final client = PocketBase('${backendAbsUrl}');
...
final body = <String, dynamic>{ ... };
final record = await client.records.update('${collection?.name}', 'RECORD_ID', body: body);
`}
/>
<div class="section-title">Path parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -127,7 +149,7 @@
</table>
<div class="section-title">Body Parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -136,6 +158,114 @@
</tr>
</thead>
<tbody>
{#if collection?.isAuth}
<tr>
<td colspan="3" class="txt-hint">Auth fields</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>username</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>The username of the auth record.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>email</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>
The auth record email address.
<br />
This field can be updated only by admins or auth records with "Manage" access.
<br />
Regular accounts can update their email by calling "Request email change".
</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>emailVisibility</span>
</div>
</td>
<td>
<span class="label">Boolean</span>
</td>
<td>Whether to show/hide the auth record email when fetching the record data.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>oldPassword</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>
Old auth record password.
<br />
This field is required only when changing the record password. Admins and auth records with
"Manage" access can skip this field.
</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>password</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>New auth record password.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>passwordConfirm</span>
</div>
</td>
<td>
<span class="label">String</span>
</td>
<td>New auth record password confirmation.</td>
</tr>
<tr>
<td>
<div class="inline-flex">
<span class="label label-warning">Optional</span>
<span>verified</span>
</div>
</td>
<td>
<span class="label">Boolean</span>
</td>
<td>
Indicates whether the auth record is verified or not.
<br />
This field can be set only by admins or auth records with "Manage" access.
</td>
</tr>
<tr>
<td colspan="3" class="txt-hint">Schema fields</td>
</tr>
{/if}
{#each collection?.schema as field (field.name)}
<tr>
<td>
@@ -193,15 +323,11 @@
</td>
<td>
Auto expand relations when returning the updated record. Ex.:
<CodeBlock
content={`
?expand=rel1,rel2.subrel21.subrel22
`}
/>
<CodeBlock content={`?expand=relField1,relField2.subRelField21`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>@expand</code> property (eg. <code>{`"@expand": {"rel1": {...}, ...}`}</code>). Only the
relations that the user has permissions to <strong>view</strong> will be expanded.
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>). Only
the relations that the user has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
@@ -46,6 +46,37 @@
}
</script>
<h3 class="m-b-sm">View ({collection.name})</h3>
<div class="content txt-lg m-b-sm">
<p>Fetch a single <strong>{collection.name}</strong> record.</p>
</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const pb = new PocketBase('${backendAbsUrl}');
...
const record1 = await pb.collection('${collection?.name}').getOne('RECORD_ID', {
expand: 'relField1,relField2.subRelField',
});
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${backendAbsUrl}');
...
final record1 = await pb.collection('${collection?.name}').getOne('RECORD_ID',
'expand': 'relField1,relField2.subRelField',
);
`}
/>
<h6 class="m-b-xs">API details</h6>
<div class="alert alert-info">
<strong class="label label-primary">GET</strong>
<div class="content">
@@ -54,42 +85,12 @@
</p>
</div>
{#if adminsOnly}
<p class="txt-hint txt-sm txt-right">Requires <code>Authorization: Admin TOKEN</code> header</p>
<p class="txt-hint txt-sm txt-right">Requires admin <code>Authorization:TOKEN</code> header</p>
{/if}
</div>
<div class="content m-b-base">
<p>Fetch a single <strong>{collection.name}</strong> record.</p>
</div>
<div class="section-title">Client SDKs example</div>
<SdkTabs
js={`
import PocketBase from 'pocketbase';
const client = new PocketBase('${backendAbsUrl}');
...
const record = await client.records.getOne('${collection?.name}', 'RECORD_ID', {
expand: 'some_relation'
});
`}
dart={`
import 'package:pocketbase/pocketbase.dart';
final client = PocketBase('${backendAbsUrl}');
...
final record = await client.records.getOne('${collection?.name}', 'RECORD_ID', query: {
'expand': 'some_relation',
});
`}
/>
<div class="section-title">Path Parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -109,7 +110,7 @@
</table>
<div class="section-title">Query parameters</div>
<table class="table-compact table-border m-b-lg">
<table class="table-compact table-border m-b-base">
<thead>
<tr>
<th>Param</th>
@@ -125,15 +126,12 @@
</td>
<td>
Auto expand record relations. Ex.:
<CodeBlock
content={`
?expand=rel1,rel2.subrel21.subrel22
`}
/>
<CodeBlock content={`?expand=relField1,relField2.subRelField`} />
Supports up to 6-levels depth nested relations expansion. <br />
The expanded relations will be appended to the record under the
<code>@expand</code> property (eg. <code>{`"@expand": {"rel1": {...}, ...}`}</code>). Only the
relations that the user has permissions to <strong>view</strong> will be expanded.
<code>expand</code> property (eg. <code>{`"expand": {"relField1": {...}, ...}`}</code>).
<br />
Only the relations to which the account has permissions to <strong>view</strong> will be expanded.
</td>
</tr>
</tbody>
@@ -39,7 +39,7 @@
icon: CommonHelper.getFieldTypeIcon("date"),
},
{
label: "Multiple choices",
label: "Select",
value: "select",
icon: CommonHelper.getFieldTypeIcon("select"),
},
@@ -58,17 +58,11 @@
value: "relation",
icon: CommonHelper.getFieldTypeIcon("relation"),
},
{
label: "User",
value: "user",
icon: CommonHelper.getFieldTypeIcon("user"),
},
];
</script>
<ObjectSelect
class="field-type-select {classes}"
searchable
items={types}
bind:keyOfSelected={value}
{...$$restProps}
@@ -1,8 +1,10 @@
<script>
import ApiClient from "@/utils/ApiClient";
import CommonHelper from "@/utils/CommonHelper";
import tooltip from "@/actions/tooltip";
import Field from "@/components/base/Field.svelte";
import ObjectSelect from "@/components/base/ObjectSelect.svelte";
import CollectionUpsertPanel from "@/components/collections/CollectionUpsertPanel.svelte";
export let key = "";
export let options = {};
@@ -14,6 +16,7 @@
let isLoading = false;
let collections = [];
let upsertPanel = null;
// load defaults
$: if (CommonHelper.isEmpty(options)) {
@@ -26,19 +29,20 @@
loadCollections();
function loadCollections() {
async function loadCollections() {
isLoading = true;
ApiClient.collections.getFullList(200, { sort: "-created" })
.then((items) => {
collections = items;
})
.catch((err) => {
ApiClient.errorResponseHandler(err);
})
.finally(() => {
isLoading = false;
try {
const result = await ApiClient.collections.getFullList(200, {
sort: "created",
});
collections = CommonHelper.sortCollections(result);
} catch (err) {
ApiClient.errorResponseHandler(err);
}
isLoading = false;
}
</script>
@@ -53,13 +57,32 @@
selectionKey="id"
items={collections}
bind:keyOfSelected={options.collectionId}
/>
>
<svelte:fragment slot="afterOptions">
<button
type="button"
class="btn btn-warning btn-block btn-sm m-t-5"
on:click={() => upsertPanel?.show()}
>
<span class="txt">New collection</span>
</button>
</svelte:fragment>
</ObjectSelect>
</Field>
</div>
<div class="col-sm-3">
<Field class="form-field required" name="schema.{key}.options.maxSelect" let:uniqueId>
<label for={uniqueId}>Max select</label>
<input type="number" id={uniqueId} step="1" min="1" required bind:value={options.maxSelect} />
<Field class="form-field" name="schema.{key}.options.maxSelect" let:uniqueId>
<label for={uniqueId}>
<span class="txt">Max select</span>
<i
class="ri-information-line link-hint"
use:tooltip={{
text: "Leave empty for no limit.",
position: "top",
}}
/>
</label>
<input type="number" id={uniqueId} step="1" min="1" bind:value={options.maxSelect} />
</Field>
</div>
<div class="col-sm-12">
@@ -69,3 +92,13 @@
</Field>
</div>
</div>
<CollectionUpsertPanel
bind:this={upsertPanel}
on:save={(e) => {
if (e?.detail?.collection?.id) {
options.collectionId = e.detail.collection.id;
}
loadCollections();
}}
/>