Repository navigation
Conversation
Consolidates the redirect-loop detection logic that api.links.tsx and redirect.tsx each implement privately (extractSlugOnSameHostFromLongUrl / extractSlugOnSameHost and createsCycleAtPersist / createsCycle). The walk algorithm is extracted verbatim into createsCycle(); the DB fetch is injected as a lookup callback so each route keeps its own error-handling semantics. extractSlugOnSameHost() is exported for upcoming unit tests.
…tems Document Nuto's architecture (React Router v7 loaders/actions as the backend, D1 + KV, Clerk), the security invariants verified against the code (no IP storage in clicks, svix-verified webhooks, parameterized queries), and known pending items that require discussion before any change (unsalted SHA-256 link passwords, dead dashboard action, user.updated binding undefined).
Drop the private extractSlugOnSameHostFromLongUrl and createsCycleAtPersist copies in favor of utils/cycle-detection. The DB fetch becomes a getLongUrlBySlug helper passed as the lookup callback; it keeps propagating errors to handleCreate/handleUpdate's existing catch blocks, preserving the endpoints' observable behavior.
Drop the private extractSlugOnSameHost and createsCycle copies in favor of utils/cycle-detection. getLongUrlBySlug stays local as the lookup callback and keeps swallowing DB errors (returning null), so the redirect proceeds as before on lookup failures. The 'context as unknown as AppLoadContext' cast in the action's call site is no longer needed: the callback captures context directly.
app/repository/urls.ts: urls-table queries (existence check, redirect lookups, ownership checks, dashboard listing, insert/update/delete) moved verbatim from api.links.tsx, redirect.tsx and dashboard.tsx. app/repository/clicks.ts: click logging statements from redirect.tsx. app/repository/users.ts: users-table statements from webhooks.clerk.tsx. updateUser keeps accepting subscriptionPlan: undefined — the existing bind(undefined) behavior is preserved and remains a pending item. app/repository/url-cache.ts: URL_STORE read/write/delete with the same JSON entry shape and raw-string fallback used by redirect.tsx. No route uses these yet; wiring comes in the following commits.
handleGetLink, handleCreate, handleUpdate and handleDelete now orchestrate app/repository/urls.ts and url-cache.ts instead of calling DB.prepare / URL_STORE directly. shortCodeTaken keeps its DB-first, KV-second ordering, and the create path still writes the same KV entry (with storedHash) and expiration options. Queries and observable behavior are unchanged.
The loader and action now orchestrate app/repository/urls.ts, clicks.ts and url-cache.ts instead of calling DB.prepare / URL_STORE directly. KV hits resolve through getRedirectEntry with the same JSON-entry parsing and raw-string fallback; the DB fallback keeps the expiry check, cleanup delete and 30-day KV repopulation with storedHash. Cycle-check lookups keep swallowing errors via .catch so redirects proceed as before on lookup failures.
user.created / user.updated / user.deleted now call app/repository/users.ts. The event payload is still verified with svix before any DB write. user.updated keeps binding undefined for subscription_plan (the known runtime issue stays a pending item); queries and observable behavior are unchanged.
The loader's links query moves to listActiveLinksForUser; the (dead, pending-removal) action's insert moves to insertLinkWithCreatedAt. Both keep their exact SQL and behavior — including the action storing the raw password, which stays a pending item. Wiring dashboard keeps every loader/action compliant with the repository-layer constraint.
Add app/repository/ to the structure tree and mention the cycle detection module under utils/ now that both exist.
Cover extractSlugOnSameHost (same-host extraction, port stripping, scheme prepending, case-insensitive hostname comparison, null/invalid inputs) and createsCycle (self, two-link and longer cycles, unknown slugs, off-host exits, maxDepth cap, lookup interaction). The host-header port cases act as the regression guard for host:port parsing: any mutation that breaks calling split (instead of indexing it) or changes the port-stripping line fails these tests before reaching production.
Integration tests against real D1 (miniflare via cloudflare:test): insert/lookup round-trips, password and expiry fields, ownership checks, click-counted details, dashboard listing (active-only, per-user), rename, field update and delete.
insertClick persists only the privacy-safe columns (url_id, clicked_at, country, user_agent) — asserted by checking the exact column set of the stored row, guarding the no-IP invariant. updateLastClicked stamps urls.last_clicked.
insertUser / updateUser / deleteUserById against real D1. updateUser is tested on the valid-plan path only; the undefined subscription_plan bind is the known pending runtime issue and is not codified here.
KV round-trip with storedHash, cache miss, and the two legacy fallbacks (raw non-JSON value, JSON without longUrl) that the redirect loader relies on, plus delete.
Three findings from the refactor, all awaiting discussion before any change: the update path writes KV entries without storedHash which lets the loader's password wall be skipped on KV hits; the unused GET /api/links loader exposes the stored SHA-256 hash; and the Clerk webhook logs user emails (PII) with observability enabled.
Reorganize into the standard context format: project overview, tech stack, structure, key commands, git workflow, environment setup, backend conventions, security notes, known issues, important files, build/deploy, and notes for AI assistants. Phase-plan conditionals are gone: the repository-layer rule is now unconditional (the layer landed on this stack). Known issues keep the security findings that still await a decision, without line numbers so they survive future edits. Git workflow rules from this session are documented as standing rules.
Add CLAUDE.md: project structure, verified constraints, and pending items
…tion Consolidate cycle detection into a shared utils module
Extract repository layer for D1 and KV data access
Add cycle-detection and repository tests; rename CLAUDE.md to AGENTS.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request adds a comprehensive repository test suite for the data-access layer in
app/repository/, introduces new repository functions for click tracking, and documents the Nuto project with a detailed technical overview. The main themes are improved test coverage, new repository features, and enhanced documentation.Repository test coverage:
urls,clicks,users, andurl-cacherepositories, using real D1 and KV bindings via miniflare. These tests cover all CRUD operations, privacy guarantees, and edge cases for each repository module. [1] [2] [3] [4]Repository features:
insertClickandupdateLastClickedinapp/repository/clicks.tsto support privacy-safe click logging and last-click timestamp updates.Documentation:
AGENTS.md, a technical project overview that explains the architecture, tech stack, backend conventions, security/privacy rules, and known issues for Nuto. This serves as onboarding and reference documentation for contributors and AI assistants.