mirror of
https://github.com/svemagie/indiekit-endpoint-microsub.git
synced 2026-04-02 15:35:00 +02:00
Restores complete implementation from feat/endpoint-microsub branch: - Reader UI with views (reader.njk, channel.njk, feeds.njk, etc.) - Feed polling, parsing, and normalization - WebSub subscriber - SSE realtime updates - Redis caching - Search indexing - Media proxy - Webmention processing
218 lines
4.6 KiB
JavaScript
218 lines
4.6 KiB
JavaScript
/**
|
|
* Input validation utilities for Microsub
|
|
* @module utils/validation
|
|
*/
|
|
|
|
import { IndiekitError } from "@indiekit/error";
|
|
|
|
/**
|
|
* Valid Microsub actions
|
|
*/
|
|
export const VALID_ACTIONS = [
|
|
"channels",
|
|
"timeline",
|
|
"follow",
|
|
"unfollow",
|
|
"search",
|
|
"preview",
|
|
"mute",
|
|
"unmute",
|
|
"block",
|
|
"unblock",
|
|
"events",
|
|
];
|
|
|
|
/**
|
|
* Valid channel methods
|
|
*/
|
|
export const VALID_CHANNEL_METHODS = ["delete", "order"];
|
|
|
|
/**
|
|
* Valid timeline methods
|
|
*/
|
|
export const VALID_TIMELINE_METHODS = ["mark_read", "mark_unread", "remove"];
|
|
|
|
/**
|
|
* Valid exclude types for channel filtering
|
|
*/
|
|
export const VALID_EXCLUDE_TYPES = [
|
|
"like",
|
|
"repost",
|
|
"bookmark",
|
|
"reply",
|
|
"checkin",
|
|
];
|
|
|
|
/**
|
|
* Validate action parameter
|
|
* @param {string} action - Action to validate
|
|
* @throws {IndiekitError} If action is invalid
|
|
*/
|
|
export function validateAction(action) {
|
|
if (!action) {
|
|
throw new IndiekitError("Missing required parameter: action", {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
if (!VALID_ACTIONS.includes(action)) {
|
|
throw new IndiekitError(`Invalid action: ${action}`, {
|
|
status: 400,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate channel UID
|
|
* @param {string} channel - Channel UID to validate
|
|
* @param {boolean} [required] - Whether channel is required
|
|
* @throws {IndiekitError} If channel is invalid
|
|
*/
|
|
export function validateChannel(channel, required = true) {
|
|
if (required && !channel) {
|
|
throw new IndiekitError("Missing required parameter: channel", {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
if (channel && typeof channel !== "string") {
|
|
throw new IndiekitError("Invalid channel parameter", {
|
|
status: 400,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate URL parameter
|
|
* @param {string} url - URL to validate
|
|
* @param {string} [paramName] - Parameter name for error message
|
|
* @param parameterName
|
|
* @throws {IndiekitError} If URL is invalid
|
|
*/
|
|
export function validateUrl(url, parameterName = "url") {
|
|
if (!url) {
|
|
throw new IndiekitError(`Missing required parameter: ${parameterName}`, {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
try {
|
|
new URL(url);
|
|
} catch {
|
|
throw new IndiekitError(`Invalid URL: ${url}`, {
|
|
status: 400,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate entry/entries parameter
|
|
* @param {string|Array} entry - Entry ID(s) to validate
|
|
* @returns {Array} Array of entry IDs
|
|
* @throws {IndiekitError} If entry is invalid
|
|
*/
|
|
export function validateEntries(entry) {
|
|
if (!entry) {
|
|
throw new IndiekitError("Missing required parameter: entry", {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
// Normalize to array
|
|
const entries = Array.isArray(entry) ? entry : [entry];
|
|
|
|
if (entries.length === 0) {
|
|
throw new IndiekitError("Entry parameter cannot be empty", {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
return entries;
|
|
}
|
|
|
|
/**
|
|
* Validate channel name
|
|
* @param {string} name - Channel name to validate
|
|
* @throws {IndiekitError} If name is invalid
|
|
*/
|
|
export function validateChannelName(name) {
|
|
if (!name || typeof name !== "string") {
|
|
throw new IndiekitError("Missing required parameter: name", {
|
|
status: 400,
|
|
});
|
|
}
|
|
|
|
if (name.length > 100) {
|
|
throw new IndiekitError("Channel name must be 100 characters or less", {
|
|
status: 400,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate exclude types array
|
|
* @param {Array} types - Array of exclude types
|
|
* @returns {Array} Validated exclude types
|
|
*/
|
|
export function validateExcludeTypes(types) {
|
|
if (!types || !Array.isArray(types)) {
|
|
return [];
|
|
}
|
|
|
|
return types.filter((type) => VALID_EXCLUDE_TYPES.includes(type));
|
|
}
|
|
|
|
/**
|
|
* Validate regex pattern
|
|
* @param {string} pattern - Regex pattern to validate
|
|
* @returns {string|null} Valid pattern or null
|
|
*/
|
|
export function validateExcludeRegex(pattern) {
|
|
if (!pattern || typeof pattern !== "string") {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
new RegExp(pattern);
|
|
return pattern;
|
|
} catch {
|
|
return;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parse array parameter from request
|
|
* Handles both array[] and array[0], array[1] formats
|
|
* @param {object} body - Request body
|
|
* @param {string} paramName - Parameter name
|
|
* @param parameterName
|
|
* @returns {Array} Parsed array
|
|
*/
|
|
export function parseArrayParameter(body, parameterName) {
|
|
// Direct array
|
|
if (Array.isArray(body[parameterName])) {
|
|
return body[parameterName];
|
|
}
|
|
|
|
// Single value
|
|
if (body[parameterName]) {
|
|
return [body[parameterName]];
|
|
}
|
|
|
|
// Indexed values (param[0], param[1], ...)
|
|
const result = [];
|
|
let index = 0;
|
|
while (body[`${parameterName}[${index}]`] !== undefined) {
|
|
result.push(body[`${parameterName}[${index}]`]);
|
|
index++;
|
|
}
|
|
|
|
// Array notation (param[])
|
|
if (body[`${parameterName}[]`]) {
|
|
const values = body[`${parameterName}[]`];
|
|
return Array.isArray(values) ? values : [values];
|
|
}
|
|
|
|
return result;
|
|
}
|