You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
145 lines
3.9 KiB
145 lines
3.9 KiB
import { config } from './env.mjs';
|
|
|
|
export class PocketBaseError extends Error {
|
|
constructor(status, code, message, data) {
|
|
super(message);
|
|
this.name = 'PocketBaseError';
|
|
this.status = status;
|
|
this.code = code;
|
|
this.data = data;
|
|
}
|
|
}
|
|
|
|
let superuserAuth = null;
|
|
|
|
export function fileUrl(collection, recordId, filename, thumb = '') {
|
|
const url = `${config.pocketBaseUrl}/api/files/${encodeURIComponent(collection)}/${encodeURIComponent(
|
|
recordId
|
|
)}/${encodeURIComponent(filename)}`;
|
|
return thumb ? `${url}?thumb=${encodeURIComponent(thumb)}` : url;
|
|
}
|
|
|
|
export async function pbPublic(path, options = {}) {
|
|
return pbRequest(path, options);
|
|
}
|
|
|
|
export async function pbUser(path, token, options = {}) {
|
|
return pbRequest(path, { ...options, token });
|
|
}
|
|
|
|
export async function pbSuper(path, options = {}) {
|
|
const token = await getSuperuserToken();
|
|
return pbRequest(path, { ...options, token });
|
|
}
|
|
|
|
export async function listRecords(collection, options = {}) {
|
|
const query = {
|
|
page: options.page || 1,
|
|
perPage: options.perPage || 50,
|
|
sort: options.sort,
|
|
filter: options.filter,
|
|
expand: options.expand
|
|
};
|
|
return pbSuper(`/api/collections/${collection}/records`, { query });
|
|
}
|
|
|
|
export async function firstRecord(collection, filter) {
|
|
const result = await listRecords(collection, { filter, perPage: 1 });
|
|
return result.items?.[0] || null;
|
|
}
|
|
|
|
export async function getRecord(collection, id) {
|
|
return pbSuper(`/api/collections/${collection}/records/${encodeURIComponent(id)}`);
|
|
}
|
|
|
|
export async function createRecord(collection, body) {
|
|
return pbSuper(`/api/collections/${collection}/records`, {
|
|
method: 'POST',
|
|
body
|
|
});
|
|
}
|
|
|
|
export async function updateRecord(collection, id, body) {
|
|
return pbSuper(`/api/collections/${collection}/records/${encodeURIComponent(id)}`, {
|
|
method: 'PATCH',
|
|
body
|
|
});
|
|
}
|
|
|
|
export async function updateRecordForm(collection, id, body) {
|
|
return pbSuper(`/api/collections/${collection}/records/${encodeURIComponent(id)}`, {
|
|
method: 'PATCH',
|
|
body
|
|
});
|
|
}
|
|
|
|
async function getSuperuserToken() {
|
|
if (superuserAuth && superuserAuth.expiresAt > Date.now() + 30_000) {
|
|
return superuserAuth.token;
|
|
}
|
|
const response = await pbRequest('/api/collections/_superusers/auth-with-password', {
|
|
method: 'POST',
|
|
body: {
|
|
identity: config.pocketBaseSuperuserEmail,
|
|
password: config.pocketBaseSuperuserPassword
|
|
}
|
|
});
|
|
superuserAuth = {
|
|
token: response.token,
|
|
expiresAt: tokenExpiresAt(response.token)
|
|
};
|
|
return response.token;
|
|
}
|
|
|
|
async function pbRequest(path, options = {}) {
|
|
const url = new URL(path, config.pocketBaseUrl);
|
|
for (const [key, value] of Object.entries(options.query || {})) {
|
|
if (value != null && value !== '') url.searchParams.set(key, String(value));
|
|
}
|
|
|
|
const headers = new Headers(options.headers || {});
|
|
if (options.token) headers.set('authorization', `Bearer ${options.token}`);
|
|
|
|
let body = options.body;
|
|
if (body && !(body instanceof FormData) && typeof body !== 'string') {
|
|
headers.set('content-type', 'application/json');
|
|
body = JSON.stringify(body);
|
|
}
|
|
|
|
const response = await fetch(url, {
|
|
method: options.method || (body ? 'POST' : 'GET'),
|
|
headers,
|
|
body
|
|
});
|
|
const text = await response.text();
|
|
const data = text ? parseJson(text) : null;
|
|
if (!response.ok) {
|
|
throw new PocketBaseError(
|
|
response.status,
|
|
data?.code || data?.status || 'pocketbase_error',
|
|
data?.message || `PocketBase request failed with ${response.status}.`,
|
|
data?.data || data
|
|
);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
function parseJson(text) {
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch {
|
|
return { message: text };
|
|
}
|
|
}
|
|
|
|
function tokenExpiresAt(token) {
|
|
const [, payload] = token.split('.');
|
|
if (!payload) return Date.now() + 5 * 60_000;
|
|
try {
|
|
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
return typeof decoded.exp === 'number' ? decoded.exp * 1000 : Date.now() + 5 * 60_000;
|
|
} catch {
|
|
return Date.now() + 5 * 60_000;
|
|
}
|
|
}
|