commits
* refactor: update imports for @singi-labs/barazo-lexicons rename
Package renamed from @singi-labs/lexicons to @singi-labs/barazo-lexicons
for consistency with @singi-labs/sifa-lexicons naming convention.
Updates all imports, package.json dependency, and Dockerfile filter.
* chore: regenerate lockfile for @singi-labs/barazo-lexicons rename
Three new tables: community_rules, community_rule_versions,
moderation_action_rules. Rules are versioned -- editing a rule creates
a new version row so historical warnings reference the original text.
API endpoints:
- GET /api/communities/:did/rules (public)
- POST /api/communities/:did/rules (admin)
- PUT /api/communities/:did/rules/:id (admin, creates new version)
- DELETE /api/communities/:did/rules/:id (admin, soft-delete)
- PUT /api/communities/:did/rules/reorder (admin)
- GET /api/communities/:did/rules/:id/versions (admin)
Includes 34 unit tests covering validation schemas and route handlers.
Closes singi-labs/barazo-workspace#97
The DELETE /api/mod-notes/:id handler was logging 'note_created' in the
audit trail instead of a deletion-specific action type. Added
'note_deleted' to the moderation_actions enum and use it for note
deletion audit entries.
The pushSchema SQL and seed data were not updated in dcc9c76, causing
CI to fail with "column site of relation topics does not exist".
- Topics: remove content_format, add site, rename created_at → published_at
- Replies: remove content_format
- Seed data: createdAt → publishedAt for topic inserts
Implements mod annotations for issue #89: mod notes, topic notices, and mod warnings.
Pre-existing integration test failure (topics.site column) also fails on main - unrelated to this PR.
- Content field now uses union object { $type, value } in PDS records
- Rename topic createdAt → publishedAt (DB column + all references)
- Remove contentFormat from topics and replies (DB + validation + routes)
- Add optional site field to topics
- Backward-compatible content extraction in firehose indexers
- Migration 0011 handles column rename, drop, and addition
- Update all test fixtures for new record format
Closes singi-labs/barazo-workspace#54
The lexicon source updated content from plain string to
{ $type, value } richtext object and renamed topic createdAt to
publishedAt, but the firehose indexers and tests still used the
old format. Docker builds from source, causing type errors that
blocked staging deploys.
- Extract content.value for sanitizeHtml calls in topic/reply indexers
- Use 'markdown' literal for contentFormat (derived from $type)
- Use record.publishedAt for topic timestamps
- Update all unit and integration test fixtures to match new schema
fast-json-stringify strips all nested properties from objects when
additionalProperties is not set. This caused settingsSchema, settings,
and manifestJson to always be serialized as empty objects {}.
Fastify uses fast-json-stringify which only serializes fields declared
in the response JSON schema. The serializePlugin function returned
these fields but the schema didn't declare them, so they were silently
dropped from the response.
The frontend uses plugin.settingsSchema for the settings gear icon
and settings modal, but the API wasn't returning it. Extracts the
settings field from manifestJson.
Use build:backend instead of build for plugin-signatures to avoid
React type resolution errors. The API only needs plugin backend code
(hooks, routes); frontend components are loaded by barazo-web.
The plugin-signatures tsconfig.json extends ../../tsconfig.base.json
(the barazo-plugins monorepo root), but the Dockerfile only copied
the plugin subdirectory. Without the base config, tsc falls back to
defaults (no skipLibCheck, no esModuleInterop), causing hundreds of
type errors from node_modules.
Fixes staging deploy failure after workspace glob fix (#102).
serializePlugin was not extracting dependencies from manifestJson,
causing the frontend to crash with "V.dependencies is undefined"
on the admin plugins page.
plugin-signatures is not a pnpm workspace member (nested under
barazo-plugins/) so pnpm deploy --filter fails for it. Since it only
has peerDependencies (already in the API's node_modules), a direct
copy of the built source is sufficient.
Previous approach copied raw workspace directories, but those lack their
own dependencies (hoisted to workspace root). Use pnpm deploy for each
workspace package to get a standalone copy with all resolved dependencies,
then merge into the API's node_modules.
pnpm deploy creates symlinks for workspace packages (@singi-labs/lexicons,
@barazo/plugin-signatures) pointing back to /workspace/ which doesn't
exist in the runner stage. Replace symlinks with actual built packages.
The API depends on @barazo/plugin-signatures via a workspace link.
The Dockerfile was missing the COPY for the plugin source and its
build step, causing pnpm deploy to fail with a broken lockfile error.
Implement the 4 runtime gaps blocking plugin execution:
1. Lifecycle hook execution - onInstall/onUninstall/onEnable/onDisable
hooks called from admin-plugins routes with proper PluginContext
2. PluginContext construction with ScopedAtProto - public reads via
Bluesky API, authenticated writes via OAuth session restore
3. Plugin route registration - discovered routes mounted at
/api/ext/<short-name>/ with enabled-check preHandler
4. onProfileSync call site - fire-and-forget hook execution after
profile DB update
Also adds runtime.ts module (resolveHookRef, executeHook,
loadPluginHooks, buildLoadedPlugin) and updates ScopedAtProto
interface to take explicit did parameter for write operations.
Closes singi-labs/barazo-workspace#94
Adds the signatures plugin as a local link dependency so the plugin
loader can discover it from node_modules/@barazo/plugin-signatures.
Runtime wiring (lifecycle hooks, route mounting, PluginContext) is
tracked separately in the plugin system spec gaps.
Add self-reaction guard at two enforcement points:
- API route returns 403 before PDS write when user.did matches subject author
- Firehose indexer silently skips indexing self-reactions as secondary defense
* feat(plugins): add marketplace registry routes (P2.12 M3)
Add registry fetch/cache service and two public API routes for browsing
and searching plugins from the remote registry at registry.barazo.forum.
- src/lib/plugins/registry.ts: getRegistryIndex (fetch + Valkey cache),
searchRegistryPlugins, getFeaturedPlugins
- GET /api/plugins/registry/search (public, supports q/category/source)
- GET /api/plugins/registry/featured (public)
- 30 tests covering service logic and route behavior
* fix(plugins): suppress unbound-method lint in registry tests
* fix(tests): resolve lint error in plugin registry route tests
Rewrite vi.mock to avoid importOriginal (consistent-type-imports rule)
and fix missing closing parenthesis in mock factory.
* feat(plugins): add PluginContext and lifecycle type definitions
* feat(validation): add Zod schema for plugin.json manifest
* feat(db): add plugins, plugin_settings, plugin_permissions schema
* feat(plugins): add PluginContext factory with scoped cache and settings
Factory function createPluginContext() that builds a sandboxed PluginContext
with key-prefixed cache, read-only settings wrapper, scoped database access,
and child logger. Cache is only provided when the plugin has cache:read or
cache:write permissions.
* feat(plugins): add plugin loader with discovery, validation, and DB sync
Implements topological sort for dependency ordering, manifest validation
with filtering of invalid/missing-dep plugins, filesystem discovery of
@barazo/plugin-* and barazo-plugin-* packages, and database upsert for
discovered plugins (new plugins inserted as disabled).
* feat(validation): add admin plugin settings and install schemas
* feat(routes): add admin plugin management API routes
* feat(db): add migration for plugin tables
* feat(app): register plugin routes and startup discovery
* fix: update lockfile for @singi-labs/lexicons rename
Pre-existing lint errors in firehose indexers (type resolution from
lexicons package) -- unrelated to this rename.
Add a default "Your Data" page to community initialization that explains
how AT Protocol handles public data, what deletion can and cannot do,
content ownership vs. access control, and what the forum software collects.
Content derived from the privacy manifesto, stripped to only what is true
today (no aspirational features). Member-facing only.
* feat(db): add pinnedAt and pinnedScope columns to topics
* feat(api): expose isPinned, isLocked, pinnedScope in topic responses
Add isPinned, isLocked, pinnedScope, and pinnedAt fields to both
topicResponseProperties (JSON schema) and serializeTopic() so the
frontend receives pinned/locked state in API responses.
* feat(api): add scope support to pin endpoint (category/forum)
* feat(api): sort pinned topics first in topic list queries
Add CASE-based ORDER BY to GET /api/topics so pinned topics float to
the top. When a category filter is active, both category-pinned and
forum-pinned topics are promoted. On the homepage (no category filter),
only forum-pinned topics get promoted. Secondary sort remains the
user's chosen order (latest or popular).
* fix(test): add pinned_at and pinned_scope columns to tenant isolation test schema
Community initialization now creates a default category tree with 4 root
categories (General, Development, Community, Feedback) and 7 subcategories
(Frontend, Backend, DevOps, Showcase, Events, Bug Reports, Feature Requests).
Also seeds 7 demo topics (one per leaf category) with one reply each,
using the admin's DID as author. Topics include realistic content so the
forum feels alive from the first visit.
The by-author-rkey endpoints for both topics and replies returned raw
authorDid without resolving the author profile, regressing the fix from
#138 which only covered by-rkey and by-uri. The frontend switched to
by-author-rkey in #177, causing DID display instead of profile info.
Add author-scoped endpoints for topics and replies to support
AT Protocol-style URLs (/{handle}/{rkey}) and fix the latent
rkey collision bug in the existing by-rkey endpoint.
- Add GET /api/topics/by-author-rkey/:handle/:rkey
- Add GET /api/replies/by-author-rkey/:handle/:rkey
- Add shared resolveHandleToDid utility (local DB + Bluesky fallback)
- Add authorHandle to POST /api/topics 201 response
- Enrich GET /api/notifications with actorHandle, subjectTitle,
subjectAuthorDid, subjectAuthorHandle, and message fields
- Update cross-post URL builder to AT Protocol-style format
- Add composite indexes on (author_did, rkey) for topics and replies
- Keep existing by-rkey endpoint for backwards compatibility
The GET /api/topics/by-rkey/:rkey and GET /api/topics/:uri endpoints
returned raw authorDid without resolving the author profile, while the
list endpoint already called resolveAuthors(). This caused the frontend
to display did:plc:... instead of the user's display name and avatar.
Dependabot PRs don't receive repo secrets under pull_request events,
causing DEPLOY_PAT to be empty. Switch to pull_request_target which
runs in the base branch context with full secret access. Safe because
the workflow only runs for dependabot[bot] and only executes pnpm
install -- no PR-supplied code is run.
Commits pushed with the default GITHUB_TOKEN don't trigger other
workflows (GitHub's anti-recursion guard). Switch to DEPLOY_PAT so
the lockfile fix commit triggers CI.
Dependabot doesn't handle pnpm catalogs correctly -- it resolves
catalog: specifiers to concrete versions in the lockfile, causing
frozen-lockfile failures in CI. This workflow detects Dependabot PRs
that touch package.json or the lockfile, regenerates pnpm-lock.yaml,
and commits the fix back to the PR branch.
* feat(pages): add accessibility page to default seeds
New communities now get 4 default pages: Terms of Service,
Privacy Policy, Cookie Policy, and Accessibility Statement.
* fix(anti-spam): change defaults to permissive for new forums
New forums no longer hold posts from new users by default. Admins can
enable anti-spam measures when spam becomes an issue.
- firstPostQueueCount: 3 -> 0 (no first-post moderation by default)
- linkHoldEnabled: true -> false (links allowed by default)
- topicCreationDelayEnabled: true -> false (new users can create topics)
Defaults changed in anti-spam.ts, community-settings schema, and
moderation route fallbacks.
Fixes barazo-forum/barazo-workspace#73
* chore(db): add migration for permissive anti-spam defaults
Updates the SQL DEFAULT for moderation_thresholds jsonb column to match
the new permissive defaults. Only affects new community rows; existing
communities with custom thresholds are unchanged.
New communities now get 4 default pages: Terms of Service,
Privacy Policy, Cookie Policy, and Accessibility Statement.
Replace the two-step dispatch (deploy + sync in parallel) with a
single dispatch to barazo-workspace's sync-lockfile workflow. The
sync workflow regenerates the lockfile if needed, then triggers the
deploy with the correct refs.
This fixes a race condition where the deploy started with a stale
workspace lockfile before the sync had committed the updated one,
causing frozen-lockfile failures when new dependencies were added.
* feat(design): add header logo upload and showCommunityName setting
Add header logo upload endpoint (POST /api/admin/design/header-logo) with
fit-inside resize to 600x120 and two new community_settings columns:
header_logo_url and show_community_name. Update settings serialization,
public endpoint, PUT handler, and Zod validation schemas.
* fix(design): regenerate migration with drizzle-kit for proper snapshot
The manually written migration lacked a drizzle-kit snapshot file,
causing the CI Schema Drift Check to fail. Regenerated using
`pnpm db:generate` which produces both the SQL and snapshot.
* fix(design): resolve rebase conflicts and renumber migration to 0006
Align with barazo-web by only auditing production dependencies.
DevDependency vulnerabilities don't affect deployed containers.
- Split 2,114 unit tests across 3 parallel shards using vitest --shard
(~60% faster wall time)
- Add path filters for PRs: skip CI for docs-only or non-code changes.
Full suite always runs on push to main.
- Include composite setup action + lexicons cache + dependency graph
(combines Phase 2 + Phase 3 changes)
- Extract shared 5-step setup (checkout, pnpm, node, lexicons, install)
into .github/actions/setup composite action
- Cache lexicons build keyed on pnpm-lock.yaml hash, eliminating
redundant clones across 7 jobs
- Add dependency graph: lint+typecheck gate unit tests, unit tests gate
integration tests, schema-check needs lint. Build and security run
independently for fast signal.
* feat(api): add depth column to replies table
Add integer depth column (NOT NULL, default 1) for thread nesting.
Add composite index on (root_uri, depth) for depth-filtered queries.
Direct replies to topic = depth 1, nested = parent_depth + 1.
* feat(api): add maxReplyDepth to community settings
Add integer max_reply_depth column (NOT NULL, default 9999) for
admin-configurable threading depth. 9999 means effectively unlimited.
* feat(api): add threading constants
* chore(api): generate migration for threading schema
Adds depth column to replies, max_reply_depth to community_settings,
and composite index on (root_uri, depth).
* feat(api): add backfill script for reply depth
Recursive CTE computes correct depth for all existing replies.
Direct replies to topic = 1, nested = parent_depth + 1. Idempotent.
* feat(api): compute reply depth on firehose indexing and optimistic insert
Direct replies to topic get depth 1. Nested replies look up parent
depth and add 1. Falls back to depth 1 if parent not found.
* feat(api): add depth query param, childCount, and maxReplyDepth admin setting
- serializeReply now uses stored depth column instead of naive computation
- GET replies accepts ?depth= param (default 10, max 100)
- Response includes childCount for depth-limited branches
- maxReplyDepth added to admin settings GET/PUT and public settings
- Updated mock DB to support groupBy as terminal chain method
- Updated test fixtures with depth field and new convention (1-indexed)
* chore(api): regenerate migration after merge with main
Removes old 0001_modern_master_mold migration (conflicted with main's
0001_add_favicon_url). Regenerated as 0004_threading-schema with same
content: depth column on replies, max_reply_depth on community_settings,
composite index.
* fix(api): add threading columns to tenant-isolation test schema
The integration test creates its own DB schema via raw SQL rather than
using Drizzle migrations. Add the missing depth and max_reply_depth
columns so Drizzle ORM inserts don't fail.
* chore(api): trigger CI re-run
* docs(api): add JSDoc comments to threading constants
* chore(api): renumber threading migration to 0005 after merge
Main added 0004_add_pages_table, so our threading migration becomes
0005_threading-schema.
Replace GitHub's default 6-hour timeout with appropriate limits per job:
lint/typecheck/schema-check/security: 10 min, unit tests: 15 min,
integration tests: 30 min, build: 15 min.
* feat(pages): add admin pages mini-CMS with public rendering
Add a complete CMS system for admin-managed static pages (privacy policy,
terms of service, about, rules, etc.) replacing the hardcoded /legal/* routes.
Backend changes:
- New `pages` table with hierarchy (parentId), status (draft/published),
markdown content, SEO meta description, and tenant isolation via RLS
- Zod validation with reserved slug rejection (new, edit, drafts)
- Full CRUD API: public endpoints (GET /api/pages, GET /api/pages/:slug)
serve only published pages; admin endpoints handle drafts and tree management
- Cycle detection prevents circular parent references
- Setup service seeds 3 default pages (Terms, Privacy, Cookie Policy)
with full markdown content during community initialization
- Migration 0004_add_pages_table with proper journal chain
Closes barazo-forum/barazo-workspace#TBD
* fix(pages): resolve ESLint errors in routes and tests
* feat(config): add HOSTING_MODE env var
Adds HOSTING_MODE with 'saas' | 'selfhosted' options, defaulting to
'selfhosted'. SaaS mode will restrict platform field modifications;
selfhosted gives full admin control over all onboarding fields.
Part of: barazo-forum/barazo-workspace#71
* feat(schema): add source column to community_onboarding_fields
Adds 'source' column with values 'platform' | 'admin' (default: 'admin').
Platform fields are seeded by Barazo; admin fields are created via the UI.
Extensible for future 'plugin' source.
Part of: barazo-forum/barazo-workspace#71
* feat(migration): backfill platform age field and user responses
Seeds platform:age_confirmation field for existing initialized communities.
Backfills user_onboarding_responses from user_preferences.declared_age so
users who already declared their age aren't re-prompted.
Part of: barazo-forum/barazo-workspace#71
* feat(setup): seed platform onboarding fields during initialization
After community initialization, seeds platform:age_confirmation field
with source='platform' and sortOrder=-1. Uses onConflictDoNothing for
idempotent re-runs.
Part of: barazo-forum/barazo-workspace#71
* feat(routes): add source field to admin onboarding endpoints with SaaS guards
- GET /api/admin/onboarding-fields now returns { fields, hostingMode }
- Each field includes source ('platform' | 'admin') in serialization
- PUT and DELETE reject platform field modifications in SaaS mode (403)
- Selfhosted mode allows full control over all fields
Part of: barazo-forum/barazo-workspace#71
* refactor(routes): remove virtual field injection from user endpoints
All onboarding fields (platform + admin) now come from the database.
Removed SYSTEM_AGE_FIELD_ID constant, virtual age field injection in
GET /api/onboarding/status, and system age submission separation in
POST /api/onboarding/submit. All fields validated and stored uniformly.
Part of: barazo-forum/barazo-workspace#71
* refactor(gate): simplify onboarding completeness check
Removed virtual system age field injection and user_preferences lookup.
The function now simply checks: are all mandatory DB fields answered?
Platform and admin fields are treated uniformly.
Part of: barazo-forum/barazo-workspace#71
* fix(migration): renumber migrations after rebase on main
Renumber source column migration from 0001 to 0002 and backfill
from 0002 to 0003, since main now has 0001_add_favicon_url.
* feat(admin): add design upload endpoints for logo and favicon
Add dedicated admin design routes for logo (512x512, WebP q85) and
favicon (256x256, WebP q90) uploads. Add faviconUrl column to
community_settings schema with migration. Extend admin-settings
route and validation to support faviconUrl in GET/PUT endpoints
and public settings response.
* fix(admin): regenerate migration snapshot and fix test lint issues
Regenerated the favicon_url migration via drizzle-kit to include the
required snapshot file for CI schema drift check. Also removed an
unused eslint-disable directive and fixed the mock requireAdmin
preHandler to use sync done() callback instead of async.
* fix(test): add favicon_url column to tenant isolation schema
The integration test creates tables manually via pushSchema() rather
than running migrations. Add the new favicon_url column to keep the
hardcoded schema in sync with the Drizzle schema.
* chore(deps): bump the dependencies group with 4 updates
Bumps the dependencies group with 4 updates: [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify), [@sentry/node](https://github.com/getsentry/sentry-javascript), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [lint-staged](https://github.com/lint-staged/lint-staged).
Updates `@scalar/fastify-api-reference` from 1.46.0 to 1.46.2
- [Release notes](https://github.com/scalar/scalar/releases)
- [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md)
- [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify)
Updates `@sentry/node` from 10.40.0 to 10.41.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.40.0...10.41.0)
Updates `@types/node` from 25.3.2 to 25.3.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `lint-staged` from 16.3.0 to 16.3.1
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v16.3.0...v16.3.1)
---
updated-dependencies:
- dependency-name: "@scalar/fastify-api-reference"
dependency-version: 1.46.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: dependencies
- dependency-name: "@sentry/node"
dependency-version: 10.41.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: dependencies
- dependency-name: "@types/node"
dependency-version: 25.3.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: dependencies
- dependency-name: lint-staged
dependency-version: 16.3.1
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: dependencies
...
Signed-off-by: dependabot[bot] <support@github.com>
* fix(deps): regenerate lockfile for pnpm catalog resolution
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guido X Jansen <x@gui.do>
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.4 to 4.32.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/89a39a4e59826350b863aa6b6252a07ad50cf83e...c793b717bc78562f491db7b0e93a3a178b099162)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.32.5
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3.2.0 to 4.1.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/96278af6caaf10aea03fd8d33a09a777ca52d62f...a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32)
---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
dependency-version: 4.1.0
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The AT Protocol firehose sends label events as CBOR-encoded binary
frames, not JSON. OzoneService.handleMessage was calling JSON.parse
on CBOR data, causing continuous "Failed to process Ozone label event"
warnings with SyntaxError on every incoming message.
- Add cbor-frames utility using cborg's decodeFirst to split the
AT Protocol event stream frame (header + body CBOR objects)
- Replace JSON.parse with CBOR frame decoding in handleMessage
- Add toBinaryData helper to handle Blob/ArrayBuffer/Uint8Array
- Handle error frames (op: -1) with proper logging
- Skip non-#labels message types silently
- Add cborg 4.5.8 as direct dependency (was already transitive via
@ipld/dag-cbor)
Fixes barazo-forum/barazo-workspace#146
The DEPLOY_PAT token lacks permission for barazo-workspace, causing the
lockfile sync repository-dispatch to fail and block the entire deploy
workflow. Add continue-on-error so the actual deploy dispatch still
succeeds even when the sync step fails.
Fixes barazo-forum/barazo-workspace#146
The Fastify JSON Schema and Zod validation for PUT /api/admin/settings
only accepted string for communityDescription, communityLogoUrl,
primaryColor, and accentColor — but the GET response returns null for
these fields when unset. The frontend round-trips the null values back,
causing Fastify's AJV to reject the request with a 400 before the
handler runs. Align both validation layers with the database schema by
adding null as an accepted type, matching the existing pattern used by
jurisdictionCountry.
Fixes barazo-forum/barazo-workspace#TBD
Add `sort` query parameter to GET /api/topics (values: 'latest' | 'popular').
Popular sort uses a time-decay formula:
score = (replyCount + reactionCount * 0.3) / (age_hours + 2)^1.2
This naturally surfaces actively discussed recent topics while letting
older threads fade. Cursor pagination is disabled for popular sort since
computed scores aren't monotonic.
Both serializeTopic() and serializeReply() were missing isModDeleted
from the response object, making it impossible for the frontend to
distinguish author-deleted from mod-deleted content.
Also changes the author-deleted reply placeholder from empty string
to '[Deleted by author]' for consistency with the topic serializer.
Fixes barazo-forum/barazo-workspace#62
Fixes barazo-forum/barazo-workspace#63
Add isAuthorDeleted and isModDeleted to the reply JSON schema so Fastify
includes them in responses. Remove the query filter that excluded
mod-deleted replies, allowing the frontend to render tombstone placeholders.
Fixes barazo-forum/barazo-workspace#62
* feat(profiles,onboarding): handle-based blocking and system age onboarding
Add handle resolution endpoint and enrich preferences with blocked user
profiles so the frontend can display handles instead of raw DIDs.
- GET /api/users/resolve-handles: resolve handles to profiles via DB
lookup with Bluesky public API fallback
- GET /api/users/me/preferences: include blockedProfiles in response
- GET /api/users/me/preferences/communities: include blockedProfiles
per community with batch resolution
- GET /api/onboarding/status: inject system-level age_confirmation
field when user has no declaredAge and no admin age field exists
- POST /api/onboarding/submit: sync age_confirmation responses to
user_preferences and users tables
- checkOnboardingComplete: include system age field in completeness
check
Closes barazo-forum/barazo-workspace#59
Closes barazo-forum/barazo-workspace#60
* fix(tests): update mocks for onboarding gate system age field queries
The checkOnboardingComplete function was extended with two new DB queries
(all community fields for age_confirmation check, and user preferences
for declaredAge). Test mocks needed updating:
- onboarding-gate.test.ts: queue 4 select results instead of 1-2 to
cover the new allCommunityFields + userPreferences queries
- onboarding.test.ts: queue 3rd select result for the userPreferences
query added to GET /api/onboarding/status
- reactions, votes, replies, integration tests: mock checkOnboardingComplete
at module level (matching the pattern in topics.test.ts) instead of
fragile inline DB mock ordering
* chore(tests): use gender-neutral example names
Replace gendered placeholder names (Alice, Bob) with gender-neutral
alternatives (Jay, Alex) across all test fixtures, mock data, and
scripts. Aligns with project language standards.
* fix(tests): remove real surname from mock display names
Replace "Jay Graber" with just "Jay" to avoid using a real person's
full name in test fixtures.
Triggers barazo-workspace's sync-lockfile workflow on every push to
main, so dependency changes are reflected in the workspace lockfile
immediately instead of waiting for the hourly cron.
CLI script that bulk-resolves account creation dates for existing users
with did:plc:* DIDs where accountCreatedAt is NULL. Queries the PLC
directory audit log, processes users with rate-limiting (200ms delay),
and reports progress/summary.
Run: pnpm db:backfill-account-ages
* refactor: update imports for @singi-labs/barazo-lexicons rename
Package renamed from @singi-labs/lexicons to @singi-labs/barazo-lexicons
for consistency with @singi-labs/sifa-lexicons naming convention.
Updates all imports, package.json dependency, and Dockerfile filter.
* chore: regenerate lockfile for @singi-labs/barazo-lexicons rename
Three new tables: community_rules, community_rule_versions,
moderation_action_rules. Rules are versioned -- editing a rule creates
a new version row so historical warnings reference the original text.
API endpoints:
- GET /api/communities/:did/rules (public)
- POST /api/communities/:did/rules (admin)
- PUT /api/communities/:did/rules/:id (admin, creates new version)
- DELETE /api/communities/:did/rules/:id (admin, soft-delete)
- PUT /api/communities/:did/rules/reorder (admin)
- GET /api/communities/:did/rules/:id/versions (admin)
Includes 34 unit tests covering validation schemas and route handlers.
Closes singi-labs/barazo-workspace#97
- Content field now uses union object { $type, value } in PDS records
- Rename topic createdAt → publishedAt (DB column + all references)
- Remove contentFormat from topics and replies (DB + validation + routes)
- Add optional site field to topics
- Backward-compatible content extraction in firehose indexers
- Migration 0011 handles column rename, drop, and addition
- Update all test fixtures for new record format
Closes singi-labs/barazo-workspace#54
The lexicon source updated content from plain string to
{ $type, value } richtext object and renamed topic createdAt to
publishedAt, but the firehose indexers and tests still used the
old format. Docker builds from source, causing type errors that
blocked staging deploys.
- Extract content.value for sanitizeHtml calls in topic/reply indexers
- Use 'markdown' literal for contentFormat (derived from $type)
- Use record.publishedAt for topic timestamps
- Update all unit and integration test fixtures to match new schema
The plugin-signatures tsconfig.json extends ../../tsconfig.base.json
(the barazo-plugins monorepo root), but the Dockerfile only copied
the plugin subdirectory. Without the base config, tsc falls back to
defaults (no skipLibCheck, no esModuleInterop), causing hundreds of
type errors from node_modules.
Fixes staging deploy failure after workspace glob fix (#102).
Implement the 4 runtime gaps blocking plugin execution:
1. Lifecycle hook execution - onInstall/onUninstall/onEnable/onDisable
hooks called from admin-plugins routes with proper PluginContext
2. PluginContext construction with ScopedAtProto - public reads via
Bluesky API, authenticated writes via OAuth session restore
3. Plugin route registration - discovered routes mounted at
/api/ext/<short-name>/ with enabled-check preHandler
4. onProfileSync call site - fire-and-forget hook execution after
profile DB update
Also adds runtime.ts module (resolveHookRef, executeHook,
loadPluginHooks, buildLoadedPlugin) and updates ScopedAtProto
interface to take explicit did parameter for write operations.
Closes singi-labs/barazo-workspace#94
* feat(plugins): add marketplace registry routes (P2.12 M3)
Add registry fetch/cache service and two public API routes for browsing
and searching plugins from the remote registry at registry.barazo.forum.
- src/lib/plugins/registry.ts: getRegistryIndex (fetch + Valkey cache),
searchRegistryPlugins, getFeaturedPlugins
- GET /api/plugins/registry/search (public, supports q/category/source)
- GET /api/plugins/registry/featured (public)
- 30 tests covering service logic and route behavior
* fix(plugins): suppress unbound-method lint in registry tests
* fix(tests): resolve lint error in plugin registry route tests
Rewrite vi.mock to avoid importOriginal (consistent-type-imports rule)
and fix missing closing parenthesis in mock factory.
* feat(plugins): add PluginContext and lifecycle type definitions
* feat(validation): add Zod schema for plugin.json manifest
* feat(db): add plugins, plugin_settings, plugin_permissions schema
* feat(plugins): add PluginContext factory with scoped cache and settings
Factory function createPluginContext() that builds a sandboxed PluginContext
with key-prefixed cache, read-only settings wrapper, scoped database access,
and child logger. Cache is only provided when the plugin has cache:read or
cache:write permissions.
* feat(plugins): add plugin loader with discovery, validation, and DB sync
Implements topological sort for dependency ordering, manifest validation
with filtering of invalid/missing-dep plugins, filesystem discovery of
@barazo/plugin-* and barazo-plugin-* packages, and database upsert for
discovered plugins (new plugins inserted as disabled).
* feat(validation): add admin plugin settings and install schemas
* feat(routes): add admin plugin management API routes
* feat(db): add migration for plugin tables
* feat(app): register plugin routes and startup discovery
* fix: update lockfile for @singi-labs/lexicons rename
Add a default "Your Data" page to community initialization that explains
how AT Protocol handles public data, what deletion can and cannot do,
content ownership vs. access control, and what the forum software collects.
Content derived from the privacy manifesto, stripped to only what is true
today (no aspirational features). Member-facing only.
* feat(db): add pinnedAt and pinnedScope columns to topics
* feat(api): expose isPinned, isLocked, pinnedScope in topic responses
Add isPinned, isLocked, pinnedScope, and pinnedAt fields to both
topicResponseProperties (JSON schema) and serializeTopic() so the
frontend receives pinned/locked state in API responses.
* feat(api): add scope support to pin endpoint (category/forum)
* feat(api): sort pinned topics first in topic list queries
Add CASE-based ORDER BY to GET /api/topics so pinned topics float to
the top. When a category filter is active, both category-pinned and
forum-pinned topics are promoted. On the homepage (no category filter),
only forum-pinned topics get promoted. Secondary sort remains the
user's chosen order (latest or popular).
* fix(test): add pinned_at and pinned_scope columns to tenant isolation test schema
Community initialization now creates a default category tree with 4 root
categories (General, Development, Community, Feedback) and 7 subcategories
(Frontend, Backend, DevOps, Showcase, Events, Bug Reports, Feature Requests).
Also seeds 7 demo topics (one per leaf category) with one reply each,
using the admin's DID as author. Topics include realistic content so the
forum feels alive from the first visit.
Add author-scoped endpoints for topics and replies to support
AT Protocol-style URLs (/{handle}/{rkey}) and fix the latent
rkey collision bug in the existing by-rkey endpoint.
- Add GET /api/topics/by-author-rkey/:handle/:rkey
- Add GET /api/replies/by-author-rkey/:handle/:rkey
- Add shared resolveHandleToDid utility (local DB + Bluesky fallback)
- Add authorHandle to POST /api/topics 201 response
- Enrich GET /api/notifications with actorHandle, subjectTitle,
subjectAuthorDid, subjectAuthorHandle, and message fields
- Update cross-post URL builder to AT Protocol-style format
- Add composite indexes on (author_did, rkey) for topics and replies
- Keep existing by-rkey endpoint for backwards compatibility
Dependabot PRs don't receive repo secrets under pull_request events,
causing DEPLOY_PAT to be empty. Switch to pull_request_target which
runs in the base branch context with full secret access. Safe because
the workflow only runs for dependabot[bot] and only executes pnpm
install -- no PR-supplied code is run.
Dependabot doesn't handle pnpm catalogs correctly -- it resolves
catalog: specifiers to concrete versions in the lockfile, causing
frozen-lockfile failures in CI. This workflow detects Dependabot PRs
that touch package.json or the lockfile, regenerates pnpm-lock.yaml,
and commits the fix back to the PR branch.
* feat(pages): add accessibility page to default seeds
New communities now get 4 default pages: Terms of Service,
Privacy Policy, Cookie Policy, and Accessibility Statement.
* fix(anti-spam): change defaults to permissive for new forums
New forums no longer hold posts from new users by default. Admins can
enable anti-spam measures when spam becomes an issue.
- firstPostQueueCount: 3 -> 0 (no first-post moderation by default)
- linkHoldEnabled: true -> false (links allowed by default)
- topicCreationDelayEnabled: true -> false (new users can create topics)
Defaults changed in anti-spam.ts, community-settings schema, and
moderation route fallbacks.
Fixes barazo-forum/barazo-workspace#73
* chore(db): add migration for permissive anti-spam defaults
Updates the SQL DEFAULT for moderation_thresholds jsonb column to match
the new permissive defaults. Only affects new community rows; existing
communities with custom thresholds are unchanged.
Replace the two-step dispatch (deploy + sync in parallel) with a
single dispatch to barazo-workspace's sync-lockfile workflow. The
sync workflow regenerates the lockfile if needed, then triggers the
deploy with the correct refs.
This fixes a race condition where the deploy started with a stale
workspace lockfile before the sync had committed the updated one,
causing frozen-lockfile failures when new dependencies were added.
* feat(design): add header logo upload and showCommunityName setting
Add header logo upload endpoint (POST /api/admin/design/header-logo) with
fit-inside resize to 600x120 and two new community_settings columns:
header_logo_url and show_community_name. Update settings serialization,
public endpoint, PUT handler, and Zod validation schemas.
* fix(design): regenerate migration with drizzle-kit for proper snapshot
The manually written migration lacked a drizzle-kit snapshot file,
causing the CI Schema Drift Check to fail. Regenerated using
`pnpm db:generate` which produces both the SQL and snapshot.
* fix(design): resolve rebase conflicts and renumber migration to 0006
- Split 2,114 unit tests across 3 parallel shards using vitest --shard
(~60% faster wall time)
- Add path filters for PRs: skip CI for docs-only or non-code changes.
Full suite always runs on push to main.
- Include composite setup action + lexicons cache + dependency graph
(combines Phase 2 + Phase 3 changes)
- Extract shared 5-step setup (checkout, pnpm, node, lexicons, install)
into .github/actions/setup composite action
- Cache lexicons build keyed on pnpm-lock.yaml hash, eliminating
redundant clones across 7 jobs
- Add dependency graph: lint+typecheck gate unit tests, unit tests gate
integration tests, schema-check needs lint. Build and security run
independently for fast signal.
* feat(api): add depth column to replies table
Add integer depth column (NOT NULL, default 1) for thread nesting.
Add composite index on (root_uri, depth) for depth-filtered queries.
Direct replies to topic = depth 1, nested = parent_depth + 1.
* feat(api): add maxReplyDepth to community settings
Add integer max_reply_depth column (NOT NULL, default 9999) for
admin-configurable threading depth. 9999 means effectively unlimited.
* feat(api): add threading constants
* chore(api): generate migration for threading schema
Adds depth column to replies, max_reply_depth to community_settings,
and composite index on (root_uri, depth).
* feat(api): add backfill script for reply depth
Recursive CTE computes correct depth for all existing replies.
Direct replies to topic = 1, nested = parent_depth + 1. Idempotent.
* feat(api): compute reply depth on firehose indexing and optimistic insert
Direct replies to topic get depth 1. Nested replies look up parent
depth and add 1. Falls back to depth 1 if parent not found.
* feat(api): add depth query param, childCount, and maxReplyDepth admin setting
- serializeReply now uses stored depth column instead of naive computation
- GET replies accepts ?depth= param (default 10, max 100)
- Response includes childCount for depth-limited branches
- maxReplyDepth added to admin settings GET/PUT and public settings
- Updated mock DB to support groupBy as terminal chain method
- Updated test fixtures with depth field and new convention (1-indexed)
* chore(api): regenerate migration after merge with main
Removes old 0001_modern_master_mold migration (conflicted with main's
0001_add_favicon_url). Regenerated as 0004_threading-schema with same
content: depth column on replies, max_reply_depth on community_settings,
composite index.
* fix(api): add threading columns to tenant-isolation test schema
The integration test creates its own DB schema via raw SQL rather than
using Drizzle migrations. Add the missing depth and max_reply_depth
columns so Drizzle ORM inserts don't fail.
* chore(api): trigger CI re-run
* docs(api): add JSDoc comments to threading constants
* chore(api): renumber threading migration to 0005 after merge
Main added 0004_add_pages_table, so our threading migration becomes
0005_threading-schema.
* feat(pages): add admin pages mini-CMS with public rendering
Add a complete CMS system for admin-managed static pages (privacy policy,
terms of service, about, rules, etc.) replacing the hardcoded /legal/* routes.
Backend changes:
- New `pages` table with hierarchy (parentId), status (draft/published),
markdown content, SEO meta description, and tenant isolation via RLS
- Zod validation with reserved slug rejection (new, edit, drafts)
- Full CRUD API: public endpoints (GET /api/pages, GET /api/pages/:slug)
serve only published pages; admin endpoints handle drafts and tree management
- Cycle detection prevents circular parent references
- Setup service seeds 3 default pages (Terms, Privacy, Cookie Policy)
with full markdown content during community initialization
- Migration 0004_add_pages_table with proper journal chain
Closes barazo-forum/barazo-workspace#TBD
* fix(pages): resolve ESLint errors in routes and tests
* feat(config): add HOSTING_MODE env var
Adds HOSTING_MODE with 'saas' | 'selfhosted' options, defaulting to
'selfhosted'. SaaS mode will restrict platform field modifications;
selfhosted gives full admin control over all onboarding fields.
Part of: barazo-forum/barazo-workspace#71
* feat(schema): add source column to community_onboarding_fields
Adds 'source' column with values 'platform' | 'admin' (default: 'admin').
Platform fields are seeded by Barazo; admin fields are created via the UI.
Extensible for future 'plugin' source.
Part of: barazo-forum/barazo-workspace#71
* feat(migration): backfill platform age field and user responses
Seeds platform:age_confirmation field for existing initialized communities.
Backfills user_onboarding_responses from user_preferences.declared_age so
users who already declared their age aren't re-prompted.
Part of: barazo-forum/barazo-workspace#71
* feat(setup): seed platform onboarding fields during initialization
After community initialization, seeds platform:age_confirmation field
with source='platform' and sortOrder=-1. Uses onConflictDoNothing for
idempotent re-runs.
Part of: barazo-forum/barazo-workspace#71
* feat(routes): add source field to admin onboarding endpoints with SaaS guards
- GET /api/admin/onboarding-fields now returns { fields, hostingMode }
- Each field includes source ('platform' | 'admin') in serialization
- PUT and DELETE reject platform field modifications in SaaS mode (403)
- Selfhosted mode allows full control over all fields
Part of: barazo-forum/barazo-workspace#71
* refactor(routes): remove virtual field injection from user endpoints
All onboarding fields (platform + admin) now come from the database.
Removed SYSTEM_AGE_FIELD_ID constant, virtual age field injection in
GET /api/onboarding/status, and system age submission separation in
POST /api/onboarding/submit. All fields validated and stored uniformly.
Part of: barazo-forum/barazo-workspace#71
* refactor(gate): simplify onboarding completeness check
Removed virtual system age field injection and user_preferences lookup.
The function now simply checks: are all mandatory DB fields answered?
Platform and admin fields are treated uniformly.
Part of: barazo-forum/barazo-workspace#71
* fix(migration): renumber migrations after rebase on main
Renumber source column migration from 0001 to 0002 and backfill
from 0002 to 0003, since main now has 0001_add_favicon_url.
* feat(admin): add design upload endpoints for logo and favicon
Add dedicated admin design routes for logo (512x512, WebP q85) and
favicon (256x256, WebP q90) uploads. Add faviconUrl column to
community_settings schema with migration. Extend admin-settings
route and validation to support faviconUrl in GET/PUT endpoints
and public settings response.
* fix(admin): regenerate migration snapshot and fix test lint issues
Regenerated the favicon_url migration via drizzle-kit to include the
required snapshot file for CI schema drift check. Also removed an
unused eslint-disable directive and fixed the mock requireAdmin
preHandler to use sync done() callback instead of async.
* fix(test): add favicon_url column to tenant isolation schema
The integration test creates tables manually via pushSchema() rather
than running migrations. Add the new favicon_url column to keep the
hardcoded schema in sync with the Drizzle schema.
* chore(deps): bump the dependencies group with 4 updates
Bumps the dependencies group with 4 updates: [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify), [@sentry/node](https://github.com/getsentry/sentry-javascript), [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) and [lint-staged](https://github.com/lint-staged/lint-staged).
Updates `@scalar/fastify-api-reference` from 1.46.0 to 1.46.2
- [Release notes](https://github.com/scalar/scalar/releases)
- [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md)
- [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify)
Updates `@sentry/node` from 10.40.0 to 10.41.0
- [Release notes](https://github.com/getsentry/sentry-javascript/releases)
- [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/getsentry/sentry-javascript/compare/10.40.0...10.41.0)
Updates `@types/node` from 25.3.2 to 25.3.3
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)
Updates `lint-staged` from 16.3.0 to 16.3.1
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v16.3.0...v16.3.1)
---
updated-dependencies:
- dependency-name: "@scalar/fastify-api-reference"
dependency-version: 1.46.2
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: dependencies
- dependency-name: "@sentry/node"
dependency-version: 10.41.0
dependency-type: direct:production
update-type: version-update:semver-minor
dependency-group: dependencies
- dependency-name: "@types/node"
dependency-version: 25.3.3
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: dependencies
- dependency-name: lint-staged
dependency-version: 16.3.1
dependency-type: direct:production
update-type: version-update:semver-patch
dependency-group: dependencies
...
Signed-off-by: dependabot[bot] <support@github.com>
* fix(deps): regenerate lockfile for pnpm catalog resolution
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Guido X Jansen <x@gui.do>
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 4.32.4 to 4.32.5.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/89a39a4e59826350b863aa6b6252a07ad50cf83e...c793b717bc78562f491db7b0e93a3a178b099162)
---
updated-dependencies:
- dependency-name: github/codeql-action
dependency-version: 4.32.5
dependency-type: direct:production
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/attest-build-provenance](https://github.com/actions/attest-build-provenance) from 3.2.0 to 4.1.0.
- [Release notes](https://github.com/actions/attest-build-provenance/releases)
- [Changelog](https://github.com/actions/attest-build-provenance/blob/main/RELEASE.md)
- [Commits](https://github.com/actions/attest-build-provenance/compare/96278af6caaf10aea03fd8d33a09a777ca52d62f...a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32)
---
updated-dependencies:
- dependency-name: actions/attest-build-provenance
dependency-version: 4.1.0
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
The AT Protocol firehose sends label events as CBOR-encoded binary
frames, not JSON. OzoneService.handleMessage was calling JSON.parse
on CBOR data, causing continuous "Failed to process Ozone label event"
warnings with SyntaxError on every incoming message.
- Add cbor-frames utility using cborg's decodeFirst to split the
AT Protocol event stream frame (header + body CBOR objects)
- Replace JSON.parse with CBOR frame decoding in handleMessage
- Add toBinaryData helper to handle Blob/ArrayBuffer/Uint8Array
- Handle error frames (op: -1) with proper logging
- Skip non-#labels message types silently
- Add cborg 4.5.8 as direct dependency (was already transitive via
@ipld/dag-cbor)
Fixes barazo-forum/barazo-workspace#146
The Fastify JSON Schema and Zod validation for PUT /api/admin/settings
only accepted string for communityDescription, communityLogoUrl,
primaryColor, and accentColor — but the GET response returns null for
these fields when unset. The frontend round-trips the null values back,
causing Fastify's AJV to reject the request with a 400 before the
handler runs. Align both validation layers with the database schema by
adding null as an accepted type, matching the existing pattern used by
jurisdictionCountry.
Fixes barazo-forum/barazo-workspace#TBD
Add `sort` query parameter to GET /api/topics (values: 'latest' | 'popular').
Popular sort uses a time-decay formula:
score = (replyCount + reactionCount * 0.3) / (age_hours + 2)^1.2
This naturally surfaces actively discussed recent topics while letting
older threads fade. Cursor pagination is disabled for popular sort since
computed scores aren't monotonic.
Both serializeTopic() and serializeReply() were missing isModDeleted
from the response object, making it impossible for the frontend to
distinguish author-deleted from mod-deleted content.
Also changes the author-deleted reply placeholder from empty string
to '[Deleted by author]' for consistency with the topic serializer.
Fixes barazo-forum/barazo-workspace#62
Fixes barazo-forum/barazo-workspace#63
* feat(profiles,onboarding): handle-based blocking and system age onboarding
Add handle resolution endpoint and enrich preferences with blocked user
profiles so the frontend can display handles instead of raw DIDs.
- GET /api/users/resolve-handles: resolve handles to profiles via DB
lookup with Bluesky public API fallback
- GET /api/users/me/preferences: include blockedProfiles in response
- GET /api/users/me/preferences/communities: include blockedProfiles
per community with batch resolution
- GET /api/onboarding/status: inject system-level age_confirmation
field when user has no declaredAge and no admin age field exists
- POST /api/onboarding/submit: sync age_confirmation responses to
user_preferences and users tables
- checkOnboardingComplete: include system age field in completeness
check
Closes barazo-forum/barazo-workspace#59
Closes barazo-forum/barazo-workspace#60
* fix(tests): update mocks for onboarding gate system age field queries
The checkOnboardingComplete function was extended with two new DB queries
(all community fields for age_confirmation check, and user preferences
for declaredAge). Test mocks needed updating:
- onboarding-gate.test.ts: queue 4 select results instead of 1-2 to
cover the new allCommunityFields + userPreferences queries
- onboarding.test.ts: queue 3rd select result for the userPreferences
query added to GET /api/onboarding/status
- reactions, votes, replies, integration tests: mock checkOnboardingComplete
at module level (matching the pattern in topics.test.ts) instead of
fragile inline DB mock ordering
* chore(tests): use gender-neutral example names
Replace gendered placeholder names (Alice, Bob) with gender-neutral
alternatives (Jay, Alex) across all test fixtures, mock data, and
scripts. Aligns with project language standards.
* fix(tests): remove real surname from mock display names
Replace "Jay Graber" with just "Jay" to avoid using a real person's
full name in test fixtures.