Skip to content

feat(search): D1 FTS5 search over videos with header search box#23

Merged
aloewright merged 1 commit into
mainfrom
alo-150-fts-search
Apr 26, 2026
Merged

feat(search): D1 FTS5 search over videos with header search box#23
aloewright merged 1 commit into
mainfrom
alo-150-fts-search

Conversation

@aloewright
Copy link
Copy Markdown
Owner

@aloewright aloewright commented Apr 26, 2026

Summary

  • Adds an FTS5 virtual table videos_fts over title/description/channel name with triggers that keep it in sync on insert/update/delete and on creator rename. The update trigger only fires for indexed columns so view-count writes don't churn the index.
  • Adds GET /api/videos/search?q= returning ranked, soft-delete-filtered results joined to channel metadata. Query is sanitised and split into prefix-quoted FTS5 tokens (8 max) to make q safe.
  • Adds a header search box and a /search results page using the existing Strand styles.
  • Unit tests cover query sanitisation including empty/syntax-only inputs and token caps.

Test plan

  • npm run lint
  • npm run type-check
  • npm test
  • npm run build
  • Manual: visit /search?q=… once deployed against a D1 with the new migration applied.

Closes ALO-150


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added full-text search functionality to discover videos by title, description, and channel name.
    • Added search bar in the header for quick access.
    • New dedicated search results page displaying matching videos with channel information, view counts, and descriptions.

Copilot AI review requested due to automatic review settings April 26, 2026 23:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

Caution

Review failed

Pull request was closed or merged during review

Walkthrough

This pull request introduces full-text search functionality across the entire application stack. A database migration creates an FTS5 virtual table for videos with automatic synchronization triggers, the frontend adds a header search form and dedicated search results page, a new API endpoint handles FTS queries with pagination, and supporting tests and styling are included.

Changes

Cohort / File(s) Summary
Database FTS Setup
src/db/migrations/0006_videos_fts.sql
Creates FTS5 virtual table indexing video title, description, and channel_name with automatic sync triggers; backfills existing videos and maintains index on insert, update, and delete operations.
Frontend Search Interface
src/frontend/App.tsx, src/frontend/pages/Search.tsx, src/frontend/styles/strand.css
Adds header search form with controlled input, new /search route, dedicated search results page with grid layout, and CSS styling for search components and input variants.
Backend Search API
src/workers/search.ts, src/workers/index.ts
Implements GET /api/videos/search endpoint with zod validation, FTS query builder (buildFtsQuery) that sanitizes input and generates FTS5 MATCH expressions, parameterized SQL joins with pagination; mounts route into worker.
Search Implementation Tests
src/workers/search.test.ts
Tests for buildFtsQuery covering whitespace handling, multi-token conversion to quoted FTS format, FTS5 special character stripping, empty token omission, and token count constraints.

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser as Frontend (Browser)
    participant API as API Worker
    participant DB as Database (D1)

    User->>Browser: Types & submits search query
    Browser->>Browser: Trim input, navigate to /search?q=query
    Browser->>Browser: Mount Search page, extract q param
    Browser->>API: GET /api/videos/search?q=query&page=1&limit=10
    API->>API: Validate params (zod)
    API->>API: buildFtsQuery() sanitize & build FTS expression
    API->>DB: SELECT * FROM videos_fts JOIN videos JOIN user<br/>WHERE videos_fts MATCH 'query'<br/>ORDER BY rank LIMIT 10
    DB->>API: Return matching video rows (id, title, description, etc.)
    API->>Browser: JSON { videos: [...] }
    Browser->>Browser: Render results grid with thumbnails & links
    Browser->>User: Display search results
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding D1 FTS5 search over videos with a header search box.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alo-150-fts-search

Warning

Review ran into problems

🔥 Problems

These MCP integrations need to be re-authenticated in the Integrations settings: Sentry


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@aloewright aloewright merged commit dc2de85 into main Apr 26, 2026
5 of 6 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 70d2881109

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

<div className="ds-meta" style={{ marginTop: 4 }}>
{v.channel_username ? (
<>
<Link to={`/channel/${v.channel_username}`}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove nested anchor in search result card

Placing the channel <Link> inside the outer card <Link> creates nested anchors, which is invalid HTML and leads to unreliable navigation behavior (e.g., clicking the channel name can trigger the parent watch-page navigation instead). This affects users specifically on search results where channel links are shown, and React will also emit DOM nesting warnings.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements full-text search for videos using SQLite FTS5, adding a new backend search endpoint, a database migration for indexing, and a frontend search interface. Several critical issues were identified regarding schema consistency: the migration and backend queries incorrectly reference a user table and name column instead of the existing users table and display_name column. Additionally, the search results page contains nested Link components, which results in invalid HTML and broken navigation behavior.

Comment on lines +15 to +18
SELECT v.id, v.title, v.description, COALESCE(u.name, '')
FROM videos v
LEFT JOIN user u ON u.id = v.user_id
WHERE v.deleted_at IS NULL;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The migration references a table named user and a column named name, but the existing schema defines the table as users and the column as display_name. This will cause the migration to fail.

  SELECT v.id, v.title, v.description, COALESCE(u.display_name, '')
  FROM videos v
  LEFT JOIN users u ON u.id = v.user_id
  WHERE v.deleted_at IS NULL;

new.id,
new.title,
new.description,
COALESCE((SELECT name FROM user WHERE id = new.user_id), '')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Incorrect table and column names. Should be users and display_name per src/db/schema.sql.

    COALESCE((SELECT display_name FROM users WHERE id = new.user_id), '')

DELETE FROM videos_fts WHERE video_id = old.id;
INSERT INTO videos_fts (video_id, title, description, channel_name)
SELECT new.id, new.title, new.description, COALESCE(u.name, '')
FROM (SELECT 1) AS s LEFT JOIN user u ON u.id = new.user_id
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Incorrect table name. Should be users.

    FROM (SELECT 1) AS s LEFT JOIN users u ON u.id = new.user_id

Comment on lines +49 to +50
CREATE TRIGGER IF NOT EXISTS user_name_videos_fts
AFTER UPDATE OF name ON user
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The trigger is defined on the wrong table and column. It should be display_name on users.

CREATE TRIGGER IF NOT EXISTS user_name_videos_fts
AFTER UPDATE OF display_name ON users

BEGIN
DELETE FROM videos_fts WHERE video_id IN (SELECT id FROM videos WHERE user_id = new.id);
INSERT INTO videos_fts (video_id, title, description, channel_name)
SELECT v.id, v.title, v.description, new.name
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

Incorrect column reference. Should be new.display_name.

    SELECT v.id, v.title, v.description, new.display_name

Comment thread src/workers/search.ts
const { results } = await c.env.DB.prepare(
`SELECT v.id, v.user_id, v.title, v.description, v.stream_video_id,
v.thumbnail_url, v.view_count, v.created_at, v.updated_at,
u.name AS channel_name, u.username AS channel_username,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The column name does not exist on the users table. It should be display_name.

Suggested change
u.name AS channel_name, u.username AS channel_username,
u.display_name AS channel_name, u.username AS channel_username,

Comment thread src/workers/search.ts
videos_fts.rank AS rank
FROM videos_fts
JOIN videos v ON v.id = videos_fts.video_id
LEFT JOIN user u ON u.id = v.user_id
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The table name is users, not user.

Suggested change
LEFT JOIN user u ON u.id = v.user_id
LEFT JOIN users u ON u.id = v.user_id

Comment on lines +85 to +87
<Link to={`/channel/${v.channel_username}`}>
{v.channel_name ?? v.channel_username}
</Link>{' '}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

You are nesting a Link component inside another Link (the outer link starts at line 80). HTML does not allow nested anchor tags (<a>), which will lead to invalid DOM structures and broken navigation behavior.

@aloewright aloewright review requested due to automatic review settings April 26, 2026 23:55
@aloewright aloewright deleted the alo-150-fts-search branch April 27, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants