merge newui branch

This commit is contained in:
Gani Georgiev
2026-04-18 16:29:34 +03:00
parent 58f605e90c
commit 4c44044c0c
804 changed files with 58660 additions and 56663 deletions

View File

@@ -0,0 +1,228 @@
window.app = window.app || {};
window.app.modals = window.app.modals || {};
window.app.modals.openApiPreview = function(collection, settings = {
onbeforeopen: null,
onafteropen: null,
onbeforeclose: null,
onafterclose: null,
}) {
const modal = apiPreviewModal(collection, settings);
if (!modal) {
return;
}
document.body.appendChild(modal);
app.modals.open(modal);
};
function apiPreviewModal(collection, settings) {
if (!collection) {
console.warn("[apiPreviewModal] missing required collection");
return;
}
let modal;
const data = store({
activeTab: "List/Search",
tabEl: null,
isLoading: false,
});
const docs = {
"List/Search": async (collection) => {
const { docsList } = await import("./docsList");
return data.tabEl = docsList(collection);
},
"View": async (collection) => {
const { docsView } = await import("./docsView");
return data.tabEl = docsView(collection);
},
};
if (collection.type != "view") {
docs["Create"] = async (collection) => {
const { docsCreate } = await import("./docsCreate");
return data.tabEl = docsCreate(collection);
};
docs["Update"] = async (collection) => {
const { docsUpdate } = await import("./docsUpdate");
return data.tabEl = docsUpdate(collection);
};
docs["Delete"] = async (collection) => {
const { docsDelete } = await import("./docsDelete");
return data.tabEl = docsDelete(collection);
};
docs["Realtime"] = async (collection) => {
const { docsRealtime } = await import("./docsRealtime");
return data.tabEl = docsRealtime(collection);
};
docs["Batch"] = async (collection) => {
const { docsBatch } = await import("./docsBatch");
return data.tabEl = docsBatch(collection);
};
}
if (collection.type == "auth") {
docs[""] = null; // hr
docs["List auth methods"] = async (collection) => {
const { docsListAuthMethods } = await import("./docsListAuthMethods");
return data.tabEl = docsListAuthMethods(collection);
};
docs["Auth with password"] = collection.passwordAuth?.enabled
? async (collection) => {
const { docsAuthWithPassword } = await import("./docsAuthWithPassword");
return data.tabEl = docsAuthWithPassword(collection);
}
: null;
if (collection.name != "_superusers") {
docs["Auth with OAuth2"] = collection.oauth2?.enabled
? async (collection) => {
const { docsAuthWithOAuth2 } = await import("./docsAuthWithOAuth2");
return data.tabEl = docsAuthWithOAuth2(collection);
}
: null;
}
docs["Auth with OTP"] = collection.otp?.enabled
? async (collection) => {
const { docsAuthWithOTP } = await import("./docsAuthWithOTP");
return data.tabEl = docsAuthWithOTP(collection);
}
: null;
docs["Auth refresh"] = async (collection) => {
const { docsAuthRefresh } = await import("./docsAuthRefresh");
return data.tabEl = docsAuthRefresh(collection);
};
if (collection.name != "_superusers") {
docs["Verification"] = async (collection) => {
const { docsVerification } = await import("./docsVerification");
return data.tabEl = docsVerification(collection);
};
}
docs["Password reset"] = async (collection) => {
const { docsPasswordReset } = await import("./docsPasswordReset");
return data.tabEl = docsPasswordReset(collection);
};
docs["Email change"] = async (collection) => {
const { docsEmailChange } = await import("./docsEmailChange");
return data.tabEl = docsEmailChange(collection);
};
}
const watchers = [
watch(() => data.activeTab, async () => {
data.isLoading = true;
await docs[data.activeTab]?.(collection);
data.isLoading = false;
}),
];
modal = t.div(
{
pbEvent: "apiPreviewModal",
className: "modal api-preview-modal",
onbeforeopen: (el) => {
return settings.onbeforeopen?.(el);
},
onafteropen: (el) => {
settings.onafteropen?.(el);
},
onbeforeclose: (el) => {
return settings.onbeforeclose?.(el);
},
onafterclose: (el) => {
settings.onafterclose?.(el);
watchers.forEach((w) => w?.unwatch());
el?.remove();
},
onmount: (el) => {
},
onunmount: (el) => {
watchers.forEach((w) => w?.unwatch());
},
},
t.div(
{ className: "modal-content" },
t.aside(
{ className: "api-preview-sidebar" },
t.nav(
{ className: "api-preview-nav" },
() => {
const items = [];
for (let title in docs) {
if (!title) {
items.push(t.hr());
continue;
}
const isDisabled = !docs[title];
items.push(
t.button(
{
type: "button",
className: () => `nav-item ${data.activeTab == title ? "active" : ""}`,
disabled: isDisabled,
ariaDescription: app.attrs.tooltip(
() => isDisabled ? "Not enabled for the collection" : "",
"left",
),
onclick: () => {
if (!isDisabled) {
data.activeTab = title;
}
},
},
title,
),
);
}
return items;
},
),
),
t.div(
{
className: () => `api-preview-content ${data.isLoading ? "faded" : ""}`,
// always scroll to the top of the new doc
scrollTop: () => data.activeTab ? 0 : null,
},
t.header(
{ className: "api-preview-content-header" },
t.h6(null, () => data.activeTab + ` (${collection.name})`),
t.button(
{
type: "button",
className: () =>
`btn sm circle transparent secondary m-l-auto preview-close-btn ${
data.isLoading ? "loading" : ""
}`,
title: "Close",
onclick: () => app.modals.close(modal),
},
t.i({ className: "ri-close-line", ariaHidden: true }),
),
),
() => data.tabEl,
),
),
);
return modal;
}

View File

@@ -0,0 +1,179 @@
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
export function docsAuthRefresh(collection) {
const baseURL = app.utils.getApiExampleURL();
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
{
token: "...JWT...",
record: Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
},
null,
2,
),
},
{
title: 401,
value: `
{
"status": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}
`,
},
{
title: 403,
value: `
{
"status": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}
`,
},
{
title: 404,
value: `
{
"status": 404,
"message": "Missing auth record context.",
"data": {}
}
`,
},
];
return t.div(
{
pbEvent: "apiPreviewAuthRefresh",
className: "content",
},
// description
t.p(null, "Returns a new auth response (token and record data) for an already authenticated record."),
t.p(
null,
"This method is usually called by users on page/screen reload to ensure that the previously stored data in ",
t.code(null, "pb.authStore"),
" is still valid and up-to-date.",
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
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.record.id);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
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.record.id);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl -X POST \\
-H 'Authorization:TOKEN' \\
'${baseURL}/api/collections/${collection.name}/auth-refresh'
`,
},
],
}),
// api
t.div({ className: "m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/auth-refresh`),
t.small({ className: "extra" }, "Requires", t.br(), "Authorization:TOKEN header"),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,261 @@
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
export function docsAuthWithOAuth2(collection) {
const baseURL = app.utils.getApiExampleURL();
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
{
token: "...JWT...",
record: Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
meta: {
"id": "abc123",
"name": "John Doe",
"username": "john.doe",
"email": "test@example.com",
"avatarURL": "https://example.com/avatar.png",
"accessToken": "...",
"refreshToken": "...",
"expiry": "2022-01-01 10:00:00.123Z",
"isNew": false,
"rawUser": {},
},
},
null,
2,
),
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while submitting the form.",
"data": {
"provider": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return t.div(
{
pbEvent: "apiPreviewAuthWithOAuth2",
className: "content",
},
// description
t.p(null, "Authenticate with an OAuth2 provider and returns a new auth token and record data."),
t.p(
null,
"For more details please check the ",
t.a({
href: import.meta.env.PB_OAUTH2_DOCS,
target: "_blank",
rel: "noopener noreferrer",
textContent: "OAuth2 integration documentation",
}),
".",
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
// OAuth2 authentication with a single realtime call.
//
// Make sure to register ${baseURL}/api/oauth2-redirect
// as redirect url in the OAuth2 app configuration.
const authData = await pb.collection('${collection.name}').authWithOAuth2({ provider: 'google' });
// OR authenticate with manual OAuth2 code exchange
// const authData = await pb.collection('${collection.name}').authWithOAuth2Code(...);
// 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.record.id);
// "logout"
pb.authStore.clear();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
import 'package:url_launcher/url_launcher.dart';
final pb = PocketBase('${baseURL}');
...
// OAuth2 authentication with a single realtime call.
//
// Make sure to register ${baseURL}/api/oauth2-redirect
// as redirect url in the OAuth2 app configuration.
final authData = await pb.collection('${collection.name}').authWithOAuth2('google', (url) async {
await launchUrl(url);
});
// OR authenticate with manual OAuth2 code exchange
// final authData = await pb.collection('${collection.name}').authWithOAuth2Code(...);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
# authenticate with manual OAuth2 code exchange
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "provider":"google", "code":"OAUTH2_CODE", "codeVerifier":"...", "redirectURL":"..." }' \\
'${baseURL}/api/collections/${collection.name}/auth-with-oauth2'
`,
},
],
}),
// api
t.div({ className: "m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/auth-with-password`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "provider ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, `The name of the OAuth2 client provider (eg. "google").`),
),
t.tr(
null,
t.td({ className: "min-width" }, "code ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The authorization code returned from the initial request."),
),
t.tr(
null,
t.td({ className: "min-width" }, "codeVerifier ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The code verifier sent with the initial request as part of the code_challenge."),
),
t.tr(
null,
t.td({ className: "min-width" }, "redirectURL ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The redirect url sent with the initial request."),
),
t.tr(
null,
t.td({ className: "min-width" }, "createData ", t.em(null, "(optional)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(
null,
t.p(null, "Optional data that will be used when creating the auth record on OAuth2 sign-up."),
t.p(
null,
"The created auth record must comply with the same requirements and validations in the regular create action.",
),
t.p(
null,
"The data can only be in JSON, aka. user uploaded files currently are not supported during OAuth2 sign-ups.",
),
),
),
),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,324 @@
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
export function docsAuthWithOTP(collection) {
const baseURL = app.utils.getApiExampleURL();
const actionTabs = [
{ title: "OTP request", content: otpRequest },
{ title: "OTP auth", content: otpAuth },
];
const data = store({
activeActionIndex: 0,
});
return t.div(
{
pbEvent: "apiPreviewAuthWithOTP",
className: "content",
},
// description
t.p(null, "Authenticate with an one-time/short-lived password (OTP)."),
t.p(
null,
"Note that when requesting an OTP we return an ",
t.code(null, "otpId"),
" even if a user with the provided email doesn't exist as a very basic enumeration protection.",
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
// send OTP email to the provided auth record
const req = await pb.collection('${collection.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
const authData = await pb.collection('${collection.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// 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.record.id);
// "logout"
pb.authStore.clear();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
// send OTP email to the provided auth record
final req = await pb.collection('${collection.name}').requestOTP('test@example.com');
// ... show a screen/popup to enter the password from the email ...
// authenticate with the requested OTP id and the email password
final authData = await pb.collection('${collection.name}').authWithOTP(
req.otpId,
"YOUR_OTP",
);
// after the above you can also access the auth data from the authStore
print(pb.authStore.isValid);
print(pb.authStore.token);
print(pb.authStore.record.id);
// "logout"
pb.authStore.clear();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
# OTP request (sends email to the user if exists)
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "email":"..." }' \\
'${baseURL}/api/collections/${collection.name}/request-otp'
# OTP auth
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "otpId":"...", "password":"..." }' \\
'${baseURL}/api/collections/${collection.name}/auth-with-otp'
`,
},
],
}),
t.nav(
{ className: "btns m-t-base m-b-sm" },
() => {
return actionTabs.map((tab, i) => {
return t.button({
type: "button",
className: () => `btn sm expanded ${data.activeActionIndex == i ? "active" : "secondary"}`,
textContent: () => tab.title,
onclick: () => data.activeActionIndex = i,
});
});
},
),
() => actionTabs[data.activeActionIndex]?.content?.(collection),
);
}
function otpRequest(collection) {
const responses = [
{
title: 200,
value: `
{
"otpId": "njvv1b1lkdbpp3m"
}
`,
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_is_email",
"message": "Must be a valid email address."
}
}
}
`,
},
{
title: 429,
value: `
{
"status": 429,
"message": "You've send too many OTP requests, please try again later.",
"data": {}
}
`,
},
];
return [
// api
t.div(null, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/request-otp`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "email ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The auth record email address to send the OTP request (if exists)."),
),
),
),
// responses
t.div({ className: "m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}
function otpAuth(collection) {
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
{
token: "...JWT...",
record: Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
},
null,
2,
),
},
{
title: 400,
value: `
{
"status": 400,
"message": "Failed to authenticate.",
"data": {
"otpId": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return [
// api
t.div(null, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/auth-with-otp`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "otpId ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The id of the OTP request."),
),
t.tr(
null,
t.td({ className: "min-width" }, "password ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The one-time/short-lived password from the OTP request."),
),
),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}

View File

@@ -0,0 +1,225 @@
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
export function docsAuthWithPassword(collection) {
const baseURL = app.utils.getApiExampleURL();
const identityFields = collection.passwordAuth?.identityFields || [];
const exampleIdentityLabel = identityFields.length == 0
? "NONE"
: "YOUR_" + identityFields.join("_OR_").toUpperCase();
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
{
token: "...JWT...",
record: Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
},
null,
2,
),
},
{
title: 400,
value: `
{
"status": 400,
"message": "Failed to authenticate.",
"data": {
"identity": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return t.div(
{
pbEvent: "apiPreviewAuthWithPassword",
className: "content",
},
// description
t.p(
null,
"Authenticate with combination of ",
t.strong(null, identityFields.join("/")),
" and ",
t.strong(null, "password"),
".",
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
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.record.id);
// "logout"
pb.authStore.clear();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
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.record.id);
// "logout"
pb.authStore.clear();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "identity":"${exampleIdentityLabel}", "password":"YOUR_PASSWORD" }' \\
'${baseURL}/api/collections/${collection.name}/auth-with-password'
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/auth-with-password`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "identity ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(
null,
app.utils.sentenize(identityFields.join(" or "), false),
" of the record to authenticate.",
),
),
t.tr(
null,
t.td({ className: "min-width" }, "identityField ", t.em(null, "(optional)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(
null,
"In case of multiple identity fields, explicitly set the field name to use when searching for the auth record.",
t.br(),
"Leave it empty for auto detection.",
),
),
t.tr(
null,
t.td({ className: "min-width" }, "password ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The auth record password."),
),
),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,263 @@
export function docsBatch(collection) {
const baseURL = app.utils.getApiExampleURL();
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
[
{
status: 200,
body: Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
},
{
status: 200,
body: Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
},
],
null,
2,
),
},
{
title: 400,
value: `
{
"status": 400,
"message": "Batch transaction failed.",
"data": {
"requests": {
"1": {
"code": "batch_request_failed",
"message": "Batch request failed.",
"response": {
"status": 400,
"message": "Failed to create record.",
"data": {
"id": {
"code": "validation_min_text_constraint",
"message": "Must be at least 3 character(s).",
"params": { "min": 3 }
}
}
}
}
}
}
}
`,
},
{
title: 403,
value: `
{
"status": 403,
"message": "Batch requests are not allowed.",
"data": {}
}
`,
},
];
return t.div(
{ pbEvent: "apiPreviewBatch", className: "content" },
// description
t.p(null, `Batch and transactional create/update/upsert/delete of multiple records in a single request.`),
t.div(
{ className: "alert warning" },
t.p(
{ className: "txt-bold" },
"The batch Web API need to be explicitly enabled and configured from the ",
t.a({
href: "#/settings",
target: "_blank",
title: "Open in new tab",
textContent: "App settings",
}),
".",
),
t.p(
null,
"Because this endpoint process the requests in a single DB transaction it could degrade the performance of your application if not used with proper care and configuration (use smaller max processing and body size limits, avoid large file uploads over slow S3 networks and custom hooks that communicate with slow external APIs).",
),
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
const batch = pb.createBatch();
batch.collection('${collection.name}').create({ ... });
batch.collection('${collection.name}').update('RECORD_ID', { ... });
batch.collection('${collection.name}').delete('RECORD_ID');
batch.collection('${collection.name}').upsert({ ... });
const result = await batch.send();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
final batch = pb.createBatch();
batch.collection('${collection.name}').create(body: { ... });
batch.collection('${collection.name}').update('RECORD_ID', body: { ... });
batch.collection('${collection.name}').delete('RECORD_ID');
batch.collection('${collection.name}').upsert(body: { ... });
final result = await batch.send();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl -X POST \\
-H 'Authorization:TOKEN' \\
-H 'Content-Type:application/json' \\
-d '{ "requests": [...] }' \\
'${baseURL}/api/batch'
`,
},
],
}),
// api
t.div({ className: "block m-t-sm" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, "/api/batch"),
),
t.p(
null,
"The request accepts only 1 required ",
t.code(null, "requests: Array<Request>"),
" parameter that defines the list of the batch requests to process.",
),
t.p(
null,
"When using the official SDKs the batch requests are transparently constructed by their service handler.",
),
t.p(null, "For the cases when you don't use the SDKs, the she supported batch request actions are:"),
t.ul(
null,
t.li(null, "record create - ", t.code(null, "POST /api/collections/{collection}/records")),
t.li(null, "record update - ", t.code(null, "PATCH /api/collections/{collection}/records")),
t.li(
null,
"record upsert - ",
t.code(null, "PUT /api/collections/{collection}/records"),
t.br(),
t.small({ className: "txt-hint" }, `(the body must have an "id" field)`),
),
t.li(null, "record delete - ", t.code(null, "DELETE /api/collections/{collection}/records/{id}")),
),
t.p(null, "Each batch ", t.em(null, "Request"), " element has the following properties:"),
t.ul(
null,
t.li(null, t.code(null, "url"), t.em(null, " (could include query parameters)")),
t.li(null, t.code(null, "method"), t.em(null, " (GET, POST, PUT, PATCH, DELETE)")),
t.li(
null,
t.code(null, "headers"),
t.br(),
t.em(
null,
"(custom per-request Authorization header is not supported at the moment, aka. all batch requests have the same auth state)",
),
),
t.li(
null,
t.code(null, "body"),
t.br(),
"When the batch request is send as ",
t.code(null, "multipart/form-data"),
", the regular batch action fields are expected to be submitted as serialized json under the ",
t.code(null, "@jsonPayload"),
" field and file keys need to follow the pattern ",
t.code(null, "requests.N.fileField"),
" or ",
t.code(null, "requests[N].fileField"),
".",
t.br(),
"Again this is handled transparently by the official SDKs, but for example if you prefer to manually construct a JS ",
t.code(null, "FormData"),
" body, then it could look something like:",
app.components.codeBlock({
className: "m-t-10",
value: `
const batchBody = new FormData();
batchBody.append("@jsonPayload", JSON.stringify({
requests: [
// create
{
url: "/api/collections/users/records?expand=someRelField",
method: "POST",
body: { someField: "test1" }
},
// update
{
url: "/api/collections/users/records/RECORD_ID",
method: "PATCH",
body: { someField: "test2" }
}
]
}))
// bind file to the first request
batchBody.append("requests.0.someFileField", new File(...))
// bind file to the second request
batchBody.append("requests.1.someFileField", new File(...))
`,
}),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,404 @@
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
import { filterSyntax } from "./filterSyntax";
export function docsCreate(collection) {
const baseURL = app.utils.getApiExampleURL();
const isSuperusersOnly = collection.createRule === null;
const isAuth = collection.type === "auth";
const excludedTableFields = isAuth ? ["password", "verified", "email", "emailVisibility"] : [];
const tableFields =
collection.fields?.filter((f) => !f.hidden && f.type != "autodate" && !excludedTableFields.includes(f.name))
|| [];
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
null,
2,
),
},
{
title: 400,
value: `
{
"status": 400,
"message": "Failed to create record.",
"data": {
"${isAuth ? "email" : tableFields.find((f) => !f.primaryKey)?.name || "someField"}": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
if (isSuperusersOnly) {
responses.push({
title: 403,
value: `
{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}
`,
});
}
return t.div(
{ pbEvent: "apiPreviewCreate", className: "content" },
// description
t.p(null, `Creates a new ${collection.name} record.`),
t.p(
null,
"Body parameters could be sent as ",
t.code(null, "application/json"),
" or ",
t.code(null, "multipart/form-data"),
".",
),
t.p(
null,
"File upload is supported only via ",
t.code(null, "multipart/form-data"),
". For more info and examples you could check the detailed ",
t.a({
href: import.meta.env.PB_FILE_UPLOAD_DOCS,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Files upload and handling docs",
}),
".",
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
// dprint-ignore
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
// example create body
const body = ${replaceDummyPayloadPlaceholder(JSON.stringify(fullDummyPayload(collection), null, 2))};
const record = await pb.collection('${collection.name}').create(body);
`+ (isAuth ? `
// (optional) send an email verification request
await pb.collection('${collection?.name}').requestVerification('test@example.com');
` : ""),
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
// dprint-ignore
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
// example create body
final body = <String, dynamic>${JSON.stringify(primitivesDummyPayload(collection), null, 2)};
final record = await pb.collection('${collection.name}').create(body: body, files: []);
` + (isAuth ? `
// (optional) send an email verification request
await pb.collection('${collection?.name}').requestVerification('test@example.com');
` : ""),
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl -X POST \\
-H 'Authorization:TOKEN' \\
-H 'Content-Type:application/json' \\
-d '{ ... }' \\
'${baseURL}/api/collections/${collection.name}/records/RECORD_ID'
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/records`),
() => {
if (isSuperusersOnly) {
return t.small({ className: "extra" }, "Requires superuser Authorization:TOKEN header");
}
},
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
() => {
if (!isAuth) {
return;
}
return [
t.tr(
null,
t.th(
{ colSpan: 99 },
"Auth specific fields",
),
),
t.tr(
null,
t.td(
{ className: "min-width" },
"email ",
() => {
if (collection.fields?.find((f) => f.name == "email")?.required) {
return t.em(null, "(required)");
}
return t.em(null, "(optional)");
},
),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "Auth record email address."),
),
t.tr(
null,
t.td(
{ className: "min-width" },
"emailVisibility ",
() => {
if (collection.fields?.find((f) => f.name == "emailVisibility")?.required) {
return t.em(null, "(required)");
}
return t.em(null, "(optional)");
},
),
t.td({ className: "min-width" }, t.span({ className: "label" }, "Boolean")),
t.td(
null,
"Whether to show/hide the auth record email when fetching the record data.",
t.br(),
"Superusers and the owner of the record always have access to the email address.",
),
),
t.tr(
null,
t.td(
{ className: "min-width" },
"password ",
t.em(null, "(required)"),
),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "Auth record password."),
),
t.tr(
null,
t.td(
{ className: "min-width" },
"passwordConfirm ",
t.em(null, "(required)"),
),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "Auth record password confirmation."),
),
t.tr(
null,
t.td(
{ className: "min-width" },
"verified ",
t.em(null, "(optional)"),
),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(
null,
t.p(null, "Indicates whether the auth record is verified or not."),
t.p(
null,
`This field can be set only by superusers or auth records with "Manage" access.`,
),
),
),
t.tr(
null,
t.th(
{ colSpan: 99 },
"Other fields",
),
),
];
},
() => {
return tableFields.map((f) => {
return t.tr(
null,
t.td(
{ className: "min-width" },
f.name,
t.em(null, f.required && !f.autogeneratePattern ? " (required)" : " (optional)"),
),
t.td(
{ className: "min-width" },
t.span(
{ className: "label" },
() => {
const dummyData = app.fieldTypes[f.type]?.dummyData(f, true);
const dummyDataType = typeof dummyData;
if (f.type == "file") return "File";
if (dummyDataType === "string") return "String";
if (dummyDataType == "number") return "Number";
if (dummyDataType == "bool") return "Boolean";
if (Array.isArray(dummyData)) return "Array";
if (app.utils.isObject(dummyData)) return "Object";
return "Mixed";
},
),
),
t.td(
null,
t.code(null, f.type),
" field type value.",
t.br(),
t.small(
{ className: "txt-hint" },
"For more details you could check the ",
t.a({
href: import.meta.env.PB_FIELDS_DOCS,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Fields docs",
}),
".",
),
),
);
});
},
),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}
export function replaceDummyPayloadPlaceholder(payloadStr) {
return payloadStr.replaceAll(`"[[`, "").replaceAll(`]]"`, "");
}
export function fullDummyPayload(collection, forUpdate = false) {
let payload = app.utils.getDummyFieldsData(collection, true);
delete payload.id;
if (collection.type == "auth") {
if (forUpdate) {
payload.oldPassword = "987654321";
delete payload.email;
}
payload.password = "123456789";
payload.passwordConfirm = "123456789";
delete payload.verified;
}
return payload;
}
export function primitivesDummyPayload(collection, forUpdate = false) {
const payload = fullDummyPayload(collection, forUpdate);
for (const prop in payload) {
const type = typeof payload[prop];
if (
// placeholder
payload[prop]?.startsWith?.("[[")
// not a primitive
|| (!["number", "string", "boolean"].includes(type) && !Array.isArray(payload[prop]))
) {
delete payload[prop];
}
}
return payload;
}

View File

@@ -0,0 +1,147 @@
export function docsDelete(collection) {
const baseURL = app.utils.getApiExampleURL();
const isSuperusersOnly = collection.deleteRule === null;
const responses = [
{
title: 204,
value: "null",
},
{
title: 400,
value: `
{
"status": 400,
"message": "Failed to delete record. Make sure that the record is not part of a required relation reference.",
"data": {}
}
`,
},
];
if (isSuperusersOnly) {
responses.push({
title: 403,
value: `
{
"status": 403,
"message": "Only superusers can access this action.",
"data": {}
}
`,
});
}
responses.push({
title: 404,
value: `
{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}
`,
});
return t.div(
{ pbEvent: "apiPreviewDelete", className: "content" },
// description
t.p(null, `Delete a single ${collection.name} record.`),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').delete('RECORD_ID');
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').delete('RECORD_ID');
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl -X DELETE \\
-H 'Authorization:TOKEN' \\
'${baseURL}/api/collections/${collection.name}/records/RECORD_ID'
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert danger api-preview-alert" },
t.span({ className: "label method" }, "DELETE"),
t.span({ className: "path" }, `/api/collections/${collection.name}/records/`, t.strong(null, ":id")),
() => {
if (isSuperusersOnly) {
return t.small({ className: "extra" }, "Requires superuser Authorization:TOKEN header");
}
},
),
t.table(
{ className: "api-preview-table path-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Path params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "id"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "ID of the record to delete."),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,284 @@
export function docsEmailChange(collection) {
const baseURL = app.utils.getApiExampleURL();
const actionTabs = [
{ title: "Request email change", content: request },
{ title: "Confirm email change", content: confirm },
];
const data = store({
activeActionIndex: 0,
});
return t.div(
{
pbEvent: "apiPreviewEmailChange",
className: "content",
},
// description
t.p(null, `Sends ${collection.name} email change request.`),
t.p(
null,
"On successful email change all previously issued auth tokens for the specific record will be automatically invalidated.",
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').authWithPassword(
'test@example.com',
'1234567890'
);
await pb.collection('${collection.name}').requestEmailChange('new@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('${collection.name}').confirmEmailChange(
'EMAIL_CHANGE_TOKEN',
'YOUR_PASSWORD',
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').authWithPassword(
'test@example.com',
'1234567890'
);
await pb.collection('${collection.name}').requestEmailChange('new@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('${collection.name}').confirmEmailChange(
'EMAIL_CHANGE_TOKEN',
'YOUR_PASSWORD',
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
# Request email change
curl -X POST \\
-H 'Authorization:TOKEN' \\
-H 'Content-Type:application/json' \\
-d '{ "newEmail":"..." }' \\
'${baseURL}/api/collections/${collection.name}/request-email-change'
# Confirm email change
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "token":"...", "password":"" }' \\
'${baseURL}/api/collections/${collection.name}/confirm-email-change'
`,
},
],
}),
t.nav(
{ className: "btns m-t-base m-b-sm" },
() => {
return actionTabs.map((tab, i) => {
return t.button({
type: "button",
className: () => `btn sm expanded ${data.activeActionIndex == i ? "active" : "secondary"}`,
textContent: () => tab.title,
onclick: () => data.activeActionIndex = i,
});
});
},
),
() => actionTabs[data.activeActionIndex]?.content?.(collection),
);
}
function request(collection) {
const responses = [
{
title: 204,
value: "null",
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"newEmail": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
{
title: 401,
value: `
{
"status": 401,
"message": "The request requires valid record authorization token to be set.",
"data": {}
}
`,
},
{
title: 403,
value: `
{
"status": 403,
"message": "The authorized record model is not allowed to perform this action.",
"data": {}
}
`,
},
];
return [
// api
t.div({ className: "block" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/request-email-change`),
t.small({ className: "extra" }, "Requires", t.br(), "Authorization:TOKEN header"),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "newEmail ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The new email address to send the change email request."),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}
function confirm(collection) {
const responses = [
{
title: 204,
value: "null",
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return [
// api
t.div({ className: "block" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/confirm-email-change`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "token ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The token from the change email request email."),
),
t.tr(
null,
t.td({ className: "min-width" }, "password ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The account password to confirm the email change."),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}

View File

@@ -0,0 +1,277 @@
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
import { filterSyntax } from "./filterSyntax";
export function docsList(collection) {
const baseURL = app.utils.getApiExampleURL();
const isSuperusersOnly = collection.listRule === null;
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
{
page: 1,
perPage: 30,
totalPages: 1,
totalItems: 2,
items: [
Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
],
},
null,
2,
),
},
{
title: 400,
value: `
{
"status": 400,
"message": "Something went wrong while processing your request.",
"data": {}
}
`,
},
];
if (isSuperusersOnly) {
responses.push({
title: 403,
value: `
{
"status": 403,
"message": "Only superusers can access this action.",
"data": {}
}
`,
});
}
return t.div(
{ pbEvent: "apiPreviewList", className: "content" },
// description
t.p(null, `Fetch a paginated ${collection.name} records list, supporting sorting and filtering.`),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
// fetch a paginated records list
const resultList = await pb.collection('${collection.name}').getList(1, 50, {
filter: 'someField1 != someField2',
});
// you can also fetch all records at once via getFullList
const records = await pb.collection('${collection.name}').getFullList({
sort: '-someField',
});
// 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' },
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
// fetch a paginated records list
final resultList = await pb.collection('${collection.name}').getList(
page: 1,
perPage: 50,
filter: 'someField1 != someField2',
);
// you can also fetch all records at once via getFullList
final records = await pb.collection('${collection.name}').getFullList(
sort: '-someField',
);
// or fetch only the first record that matches the specified filter
final record = await pb.collection('${collection.name}').getFirstListItem(
'someField="test"',
expand: 'relField1,relField2.subRelField',
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl \\
-H 'Authorization:TOKEN' \\
'${baseURL}/api/collections/${collection.name}/records?perPage=50'
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert info api-preview-alert" },
t.span({ className: "label method" }, "GET"),
t.span({ className: "path" }, `/api/collections/${collection.name}/records`),
() => {
if (isSuperusersOnly) {
return t.small({ className: "extra" }, "Requires superuser Authorization:TOKEN header");
}
},
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "page"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "Number")),
t.td(null, "The page (aka. offset) of the paginated list (default to 1)."),
),
t.tr(
null,
t.td({ className: "min-width" }, "perPage"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "Number")),
t.td(null, "Specify the max returned records per page (default to 30)."),
),
t.tr(
null,
t.td({ className: "min-width" }, "sort"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(
null,
t.p(
null,
"Specify the records order attribute(s).",
t.br(),
"Add -/+ (default) in front of the attribute for DESC / ASC order.",
),
t.p(
null,
"For example:",
app.components.codeBlock({
value: `// DESC by created and ASC by id\n?sort=-created,id`,
}),
),
t.p(
null,
"In addition to the collection non-hidden fields, the following special sort fields could be also used: ",
t.code(null, "@random"),
" ",
t.code({ hidden: () => collection.type == "view" }, "@rowid"),
".",
),
),
),
t.tr(
null,
t.td({ className: "min-width" }, "filter"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(
null,
t.p(null, "Filter the returned records. For example:"),
app.components.codeBlock({
value: `?filter=(id='abc' && created>'2022-01-01')`,
footnote: "All query params must be properly URL encoded (the SDKs do this automatically).",
}),
filterSyntax(),
),
),
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "skipTotal"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "Boolean")),
t.td(
null,
t.p(
null,
"If set to ",
t.code(null, "1/true"),
" the total counts query will be skipped and the response fields ",
t.code(null, "totalItems"),
" and ",
t.code(null, "totalPages"),
" will have -1 value.",
),
t.p(
null,
"This could drastically speed up the search queries when the total counters are not needed or cursor based pagination is used.",
" For optimization purposes, it is set by default in the ",
t.code(null, "getFirstListItem()"),
" and ",
t.code(null, "getFullList()"),
" SDKs methods.",
),
),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,144 @@
import { fieldsInfo } from "./fieldsInfo";
export function docsListAuthMethods(collection) {
const baseURL = app.utils.getApiExampleURL();
const data = store({
isLoading: false,
authMethods: [],
get responses() {
return [
{
title: 200,
value: data.isLoading ? "..." : JSON.stringify(data.authMethods, null, 2),
},
{
title: 404,
value: `
{
"status": 404,
"message": "Missing collection context.",
"data": {}
}
`,
},
];
},
});
async function listAuthMethods() {
data.isLoading = true;
try {
data.authMethods = await app.pb.collection(collection.name).listAuthMethods();
} catch (err) {
if (err.isAbort) {
app.pb.checkApiError(err);
}
}
data.isLoading = false;
}
return t.div(
{
pbEvent: "apiPreviewListAuthMethods",
className: "content",
onmount: () => {
listAuthMethods();
},
},
// description
t.p(null, `Returns a public list with all allowed ${collection.name} authentication methods.`),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
const result = await pb.collection('${collection.name}').listAuthMethods();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
final result = await pb.collection('${collection.name}').listAuthMethods();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl '${baseURL}/api/collections/${collection.name}/auth-methods'
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert info api-preview-alert" },
t.span({ className: "label method" }, "GET"),
t.span({ className: "path" }, `/api/collections/${collection.name}/auth-methods`),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: () => data.responses,
}),
);
}

View File

@@ -0,0 +1,260 @@
export function docsPasswordReset(collection) {
const baseURL = app.utils.getApiExampleURL();
const actionTabs = [
{ title: "Request password reset", content: request },
{ title: "Confirm password reset", content: confirm },
];
const data = store({
activeActionIndex: 0,
});
return t.div(
{
pbEvent: "apiPreviewPasswordReset",
className: "content",
},
// description
t.p(null, `Sends ${collection.name} password reset email request.`),
t.p(
null,
"On successful password reset all previously issued auth tokens for the specific record will be automatically invalidated.",
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').requestPasswordReset('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('${collection.name}').confirmPasswordReset(
'RESET_TOKEN',
'NEW_PASSWORD',
'NEW_PASSWORD_CONFIRM',
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').requestPasswordReset('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
// note: after this call all previously issued auth tokens are invalidated
await pb.collection('${collection.name}').confirmPasswordReset(
'RESET_TOKEN',
'NEW_PASSWORD',
'NEW_PASSWORD_CONFIRM',
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
# Request password reset
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "email":"..." }' \\
'${baseURL}/api/collections/${collection.name}/request-password-reset'
# Confirm password reset
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "token":"...", "password":"", "passwordConfirm":"" }' \\
'${baseURL}/api/collections/${collection.name}/confirm-password-reset'
`,
},
],
}),
t.nav(
{ className: "btns m-t-base m-b-sm" },
() => {
return actionTabs.map((tab, i) => {
return t.button({
type: "button",
className: () => `btn sm expanded ${data.activeActionIndex == i ? "active" : "secondary"}`,
textContent: () => tab.title,
onclick: () => data.activeActionIndex = i,
});
});
},
),
() => actionTabs[data.activeActionIndex]?.content?.(collection),
);
}
function request(collection) {
const responses = [
{
title: 204,
value: "null",
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return [
// api
t.div({ className: "block" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/request-password-reset`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "email ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The auth record email address to send the password reset request (if exists)."),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}
function confirm(collection) {
const responses = [
{
title: 204,
value: "null",
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return [
// api
t.div({ className: "block" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/confirm-password-reset`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "token ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The token from the password reset request email."),
),
t.tr(
null,
t.td({ className: "min-width" }, "password ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The new password to set."),
),
t.tr(
null,
t.td({ className: "min-width" }, "passwordConfirm ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "Confirmation of the new password."),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}

View File

@@ -0,0 +1,190 @@
export function docsRealtime(collection) {
const baseURL = app.utils.getApiExampleURL();
const dummyRecord = Object.assign({
collectionId: collection.id,
collectionName: collection.name,
}, app.utils.getDummyFieldsData(collection));
return t.div(
{ pbEvent: "apiPreviewRealtime", className: "content" },
// description
t.p(null, `Subscribe to realtime changes via Server-Sent Events (SSE).`),
t.p(
null,
"Events are sent for ",
t.strong(null, "create"),
", ",
t.strong(null, "update"),
" and ",
t.strong(null, "delete"),
` record operations (see "Event data format" below).`,
),
t.div(
{ className: "alert info" },
t.p({ className: "txt-bold" }, "You could subscribe to a single record or to an entire collection."),
t.p(
null,
"When you subscribe to a ",
t.strong(null, "single record"),
", the collection's ",
t.strong(null, "View rule"),
" will be used to determine whether the subscriber is allowed to receive the event message.",
),
t.p(
null,
"When you subscribe to an ",
t.strong(null, "entire collection"),
", the collection's ",
t.strong(null, "List/Search rule"),
" will be used to determine whether the subscriber is allowed to receive the event message.",
),
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
// (optionally) authenticate
await pb.collection('users').authWithPassword('test@example.com', '123456');
// subscribe to changes in any ${baseURL} record
pb.collection('${baseURL}').subscribe('*', function (e) {
console.log(e.action);
console.log(e.record);
}, { /* other options like: filter, expand, custom headers, etc. */ });
// subscribe to changes only in the specified record
pb.collection('${baseURL}').subscribe('RECORD_ID', function (e) {
console.log(e.action);
console.log(e.record);
}, { /* other options like: filter, expand, custom headers, etc. */ });
...
// unsubscribe - remove all 'RECORD_ID' subscriptions
pb.collection('${baseURL}').unsubscribe('RECORD_ID');
// unsubscribe - remove all '*' topic subscriptions
pb.collection('${baseURL}').unsubscribe('*');
// unsubscribe - remove all collection subscriptions
pb.collection('${baseURL}').unsubscribe();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
// (optionally) authenticate
await pb.collection('users').authWithPassword('test@example.com', '123456');
// subscribe to changes in any ${baseURL} record
pb.collection('${baseURL}').subscribe('*', (e) {
print(e.action);
print(e.record);
}, /* other options like: filter, expand, custom headers, etc. */);
// subscribe to changes only in the specified record
pb.collection('${baseURL}').subscribe('RECORD_ID', (e) {
print(e.action);
print(e.record);
}, /* other options like: filter, expand, custom headers, etc. */);
...
// unsubscribe - remove all 'RECORD_ID' subscriptions
pb.collection('${baseURL}').unsubscribe('RECORD_ID');
// unsubscribe - remove all '*' topic subscriptions
pb.collection('${baseURL}').unsubscribe('*');
// unsubscribe - remove all collection subscriptions
pb.collection('${baseURL}').unsubscribe();
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
# init an SSE connection and start listening for messages
# (the first message is always PB_CONNECT with the connection "clientId")
curl -N '${baseURL}/api/realtime'
# open a new terminal and submit the subscription topic(s)
# with the "clientId" from the initial PB_CONNECT message
curl -X POST \\
-H 'Authorization:TOKEN' \\
-H 'Content-Type:application/json' \\
-d '{ "clientId": "YOUR_CLIENT_ID", "subscriptions": ["${collection.name}/*"] }' \\
'${baseURL}/api/realtime'
# create/update/delete a record in the ${collection.name} collection and
# you should see the event message(s) in the first terminal
# (as long as your client satisfies the topic API rule)
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert api-preview-alert" },
t.span({ className: "label method" }, "GET/POST"),
t.span({ className: "path" }, "/api/realtime"),
t.div(
{ className: "extra" },
t.a({
href: import.meta.env.PB_REALTIME_DOCS,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Realtime docs",
}),
),
),
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Event data format")),
app.components.codeBlock({
value: JSON.stringify(
{
"action": "create",
"record": dummyRecord,
},
null,
2,
).replace(`"action": "create",`, "\"action\": \"create\", // create, update or delete"),
}),
);
}

View File

@@ -0,0 +1,242 @@
import { fullDummyPayload, primitivesDummyPayload, replaceDummyPayloadPlaceholder } from "./docsCreate";
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
export function docsUpdate(collection) {
const baseURL = app.utils.getApiExampleURL();
const isSuperusersOnly = collection.updateRule === null;
const isAuth = collection.type === "auth";
const excludedTableFields = isAuth ? ["id", "password", "verified", "email", "emailVisibility"] : ["id"];
const tableFields =
collection.fields?.filter((f) => !f.hidden && f.type != "autodate" && !excludedTableFields.includes(f.name))
|| [];
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
null,
2,
),
},
{
title: 400,
value: `
{
"status": 400,
"message": "Failed to create record.",
"data": {
"${tableFields.find((f) => !f.primaryKey)?.name || "someField"}": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
if (isSuperusersOnly) {
responses.push({
title: 403,
value: `
{
"status": 403,
"message": "Only superusers can perform this action.",
"data": {}
}
`,
});
}
responses.push({
title: 404,
value: `
{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}
`,
});
return t.div(
{ pbEvent: "apiPreviewUpdate", className: "content" },
// description
t.p(null, `Updates an existing ${collection.name} record.`),
t.p(
null,
"Body parameters could be sent as ",
t.code(null, "application/json"),
" or ",
t.code(null, "multipart/form-data"),
".",
),
t.p(
null,
"File upload is supported only via ",
t.code(null, "multipart/form-data"),
". For more info and examples you could check the detailed ",
t.a({
href: import.meta.env.PB_FILE_UPLOAD_DOCS,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Files upload and handling docs",
}),
".",
),
t.p(
null,
t.em(
null,
"Note that in case of a password change all previously issued tokens for the current record will be automatically invalidated and if you want your user to remain signed in you need to reauthenticate manually after the update call.",
),
),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
// dprint-ignore
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
// example update body
const body = ${replaceDummyPayloadPlaceholder(JSON.stringify(fullDummyPayload(collection, true), null, 2))};
const record = await pb.collection('${collection.name}').update('RECORD_ID', body);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
// dprint-ignore
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
// example update body
final body = <String, dynamic>${JSON.stringify(primitivesDummyPayload(collection, true), null, 2)};
final record = await pb.collection('${collection.name}').update(
'RECORD_ID',
body: body,
files: [],
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl -X PATCH \\
-H 'Authorization:TOKEN' \\
-H 'Content-Type:application/json' \\
-d '{ ... }' \\
'${baseURL}/api/collections/${collection.name}/records/RECORD_ID'
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert warning api-preview-alert" },
t.span({ className: "label method" }, "PATCH"),
t.span({ className: "path" }, `/api/collections/${collection.name}/records/`, t.strong(null, ":id")),
() => {
if (isSuperusersOnly) {
return t.small({ className: "extra" }, "Requires superuser Authorization:TOKEN header");
}
},
),
t.table(
{ className: "api-preview-table path-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Path params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "id"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "ID of the record to update."),
),
),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,234 @@
export function docsVerification(collection) {
const baseURL = app.utils.getApiExampleURL();
const actionTabs = [
{ title: "Request verification", content: request },
{ title: "Confirm verification", content: confirm },
];
const data = store({
activeActionIndex: 0,
});
return t.div(
{
pbEvent: "apiPreviewVerification",
className: "content",
},
// description
t.p(null, `Sends ${collection.name} account verification request.`),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').requestVerification('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
await pb.collection('${collection.name}').confirmVerification('VERIFICATION_TOKEN');
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
await pb.collection('${collection.name}').requestVerification('test@example.com');
// ---
// (optional) in your custom confirmation page:
// ---
await pb.collection('${collection.name}').confirmVerification('VERIFICATION_TOKEN');
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
# Request verification
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "email":"..." }' \\
'${baseURL}/api/collections/${collection.name}/request-verification'
# Confirm verification
curl -X POST \\
-H 'Content-Type:application/json' \\
-d '{ "token":"..." }' \\
'${baseURL}/api/collections/${collection.name}/confirm-verification'
`,
},
],
}),
t.nav(
{ className: "btns m-t-base m-b-sm" },
() => {
return actionTabs.map((tab, i) => {
return t.button({
type: "button",
className: () => `btn sm expanded ${data.activeActionIndex == i ? "active" : "secondary"}`,
textContent: () => tab.title,
onclick: () => data.activeActionIndex = i,
});
});
},
),
() => actionTabs[data.activeActionIndex]?.content?.(collection),
);
}
function request(collection) {
const responses = [
{
title: 204,
value: "null",
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"email": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return [
// api
t.div({ className: "block" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/request-verification`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "email ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The auth record email address to send the verification request (if exists)."),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}
function confirm(collection) {
const responses = [
{
title: 204,
value: "null",
},
{
title: 400,
value: `
{
"status": 400,
"message": "An error occurred while validating the submitted data.",
"data": {
"token": {
"code": "validation_required",
"message": "Missing required value."
}
}
}
`,
},
];
return [
// api
t.div({ className: "block" }, t.strong(null, "API details")),
t.div(
{ className: "alert success api-preview-alert" },
t.span({ className: "label method" }, "POST"),
t.span({ className: "path" }, `/api/collections/${collection.name}/confirm-verification`),
),
t.table(
{ className: "api-preview-table body-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Body params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "token ", t.em(null, "(required)")),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "The token from the verification request email."),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
];
}

View File

@@ -0,0 +1,180 @@
import { expandInfo } from "./expandInfo";
import { fieldsInfo } from "./fieldsInfo";
export function docsView(collection) {
const baseURL = app.utils.getApiExampleURL();
const isSuperusersOnly = collection.viewRule === null;
const baseDummyRecord = {
collectionId: collection.id,
collectionName: collection.name,
};
const responses = [
{
title: 200,
value: JSON.stringify(
Object.assign(baseDummyRecord, app.utils.getDummyFieldsData(collection)),
null,
2,
),
},
];
if (isSuperusersOnly) {
responses.push({
title: 403,
value: `
{
"status": 403,
"message": "Only superusers can access this action.",
"data": {}
}
`,
});
}
responses.push({
title: 404,
value: `
{
"status": 404,
"message": "The requested resource wasn't found.",
"data": {}
}
`,
});
return t.div(
{ pbEvent: "apiPreviewView", className: "content" },
// description
t.p(null, `Fetch a single ${collection.name} record.`),
app.components.codeBlockTabs({
className: "sdk-examples m-t-sm",
historyKey: "pbLastSDK",
tabs: [
{
title: "JS SDK",
language: "js",
value: `
import PocketBase from 'pocketbase';
const pb = new PocketBase('${baseURL}');
...
const record = await pb.collection('${collection.name}').getOne('RECORD_ID', {
expand: 'relField1,relField2.subRelField',
});
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_JS_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "JS SDK docs",
}),
),
},
{
title: "Dart SDK",
language: "dart",
value: `
import 'package:pocketbase/pocketbase.dart';
final pb = PocketBase('${baseURL}');
...
final record = await pb.collection('${collection.name}').getOne('RECORD_ID',
expand: 'relField1,relField2.subRelField',
);
`,
footnote: t.div(
{ className: "txt-right" },
t.a({
href: import.meta.env.PB_DART_SDK_URL,
target: "_blank",
rel: "noopener noreferrer",
textContent: "Dart SDK docs",
}),
),
},
{
title: "curl",
language: "bash",
value: `
curl \\
-H 'Authorization:TOKEN' \\
'${baseURL}/api/collections/${collection.name}/records/RECORD_ID'
`,
},
],
}),
// api
t.div({ className: "block m-t-base" }, t.strong(null, "API details")),
t.div(
{ className: "alert info api-preview-alert" },
t.span({ className: "label method" }, "GET"),
t.span({ className: "path" }, `/api/collections/${collection.name}/records/`, t.strong(null, ":id")),
() => {
if (isSuperusersOnly) {
return t.small({ className: "extra" }, "Requires superuser Authorization:TOKEN header");
}
},
),
t.table(
{ className: "api-preview-table path-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "Path params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "id"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, "ID of the record to view."),
),
),
),
t.table(
{ className: "api-preview-table query-params" },
t.thead(
null,
t.tr(
null,
t.th({ className: "min-width txt-primary" }, "?query params"),
t.th({ className: "min-width" }, "Type"),
t.th(null, "Description"),
),
),
t.tbody(
null,
t.tr(
null,
t.td({ className: "min-width" }, "expand"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, expandInfo()),
),
t.tr(
null,
t.td({ className: "min-width" }, "fields"),
t.td({ className: "min-width" }, t.span({ className: "label" }, "String")),
t.td(null, fieldsInfo()),
),
),
),
// responses
t.div({ className: "block m-t-base m-b-sm" }, t.strong(null, "Example responses")),
app.components.codeBlockTabs({
tabs: responses,
}),
);
}

View File

@@ -0,0 +1,25 @@
export function expandInfo() {
return t.div(
{ className: "api-expand-info" },
t.p(null, "Auto expand record relations. For example:"),
app.components.codeBlock({
value: `?expand=relField1,relField2.subRelField`,
}),
t.p(
null,
"Supports up to 6-levels depth nested relations expansion.",
t.br(),
"The expanded relations will be appended to each individual record under the ",
t.code(null, "expand"),
" property (eg. ",
t.code(null, `"expand": {"relField1": {...}, ...}`),
").",
),
t.p(
null,
"Only the relations to which the request user has permissions to ",
t.strong(null, "view"),
" will be expanded.",
),
);
}

View File

@@ -0,0 +1,25 @@
export function fieldsInfo() {
return t.div(
{ className: "api-fields-info" },
t.p(
null,
"Comma separated string of the fields to return in the JSON response (by default returns all fields). For example:",
),
app.components.codeBlock({
value:
`// return all root level fields and only\n// "relField.someField" from expand\n?fields=*,expand.relField.someField`,
}),
t.p(null, "Use ", t.code(null, "*"), " to target all keys from the specific depth level."),
t.p(null, "In addition, the following field modifiers are also supported:"),
t.ul(
null,
t.li(
null,
t.code(null, ":excerpt(maxLength, withEllipsis?)"),
t.br(),
"Returns a short plain text version of the field string value. Ex.: ",
t.code(null, "?fields=*,someTextField:excerpt(200,true)"),
),
),
);
}

View File

@@ -0,0 +1,165 @@
export function filterSyntax() {
const data = store({
show: false,
});
return t.div(
{ className: "filter-details m-t-10" },
t.button(
{
type: "button",
className: "btn secondary sm",
onclick: () => data.show = !data.show,
},
() => {
if (data.show) {
return [
t.span({ className: "txt" }, "Hide details"),
t.i({ className: "ri-arrow-up-s-line" }),
];
}
return [
t.span({ className: "txt" }, "Show details"),
t.i({ className: "ri-arrow-down-s-line" }),
];
},
),
app.components.slide(
() => data.show,
t.div(
{ className: "block p-t-5" },
t.p(
null,
"The filter syntax follows the format ",
t.code(
null,
t.span({ className: "txt-success" }, "OPERAND"),
t.span({ className: "txt-danger" }, " OPERATOR "),
t.span({ className: "txt-success" }, "OPERAND"),
),
", where:",
),
t.ul(
null,
t.li(
null,
t.span({ className: "txt-code txt-success" }, "OPERAND"),
" could be any of the above field literal, function, string (single or double quoted), number, null, true, false",
),
t.li(
null,
t.span({ className: "txt-code txt-danger" }, "OPERATOR"),
" is one of:",
t.ul(
null,
t.li(null, t.code({ className: "filter-op" }, "="), " Equal"),
t.li(null, t.code({ className: "filter-op" }, "!="), " Not equal"),
t.li(null, t.code({ className: "filter-op" }, ">"), " Greater than"),
t.li(null, t.code({ className: "filter-op" }, ">="), " Greater than or equal"),
t.li(null, t.code({ className: "filter-op" }, "<"), " Less than"),
t.li(null, t.code({ className: "filter-op" }, "<="), " Less than or equal"),
t.li(
null,
t.code({ className: "filter-op" }, "~"),
" Like/Contains",
t.div(
{ className: "txt-sm txt-hint" },
t.em(
null,
"(auto wraps the right string OPERAND in a \"%\" for wildcard match if not explicitly set)",
),
),
),
t.li(
null,
t.code({ className: "filter-op" }, "!~"),
" NOT Like/Contains",
t.div(
{ className: "txt-sm txt-hint" },
t.em(
null,
"(auto wraps the right string OPERAND in a \"%\" for wildcard match if not explicitly set)",
),
),
),
// any/at-least-one-of
t.li(
null,
t.code({ className: "filter-op" }, "?="),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" Equal",
),
t.li(
null,
t.code({ className: "filter-op" }, "?!="),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" Not equal",
),
t.li(
null,
t.code({ className: "filter-op" }, "?>"),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" Greater than",
),
t.li(
null,
t.code({ className: "filter-op" }, "?>="),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" Greater than or equal",
),
t.li(
null,
t.code({ className: "filter-op" }, "?<"),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" Less than",
),
t.li(
null,
t.code({ className: "filter-op" }, "?<="),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" Less than or equal",
),
t.li(
null,
t.code({ className: "filter-op" }, "?~"),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" Like/Contains",
t.div(
{ className: "txt-sm txt-hint" },
t.em(
null,
"(auto wraps the right string OPERAND in a \"%\" for wildcard match if not explicitly set)",
),
),
),
t.li(
null,
t.code({ className: "filter-op" }, "?!~"),
t.span({ className: "txt-hint" }, " Any/At-least-one-of"),
" NOT Like/Contains",
t.div(
{ className: "txt-sm txt-hint" },
t.em(
null,
"(auto wraps the right string OPERAND in a \"%\" for wildcard match if not explicitly set)",
),
),
),
),
t.p(
null,
"To group and combine several expressions you could use brackets ",
t.code(null, "(...)"),
", ",
t.code(null, "&&"),
", (AND) and ",
t.code(null, "||"),
" (OR) tokens.",
),
),
),
),
),
);
}