Make the home page's AS name search work (closes #151) #164

Merged
clawbot merged 1 commits from issue-151-as-name-search into next 2026-10-26 22:51:13 +01:00
Collaborator

Implements #151: the home page's AS Name Search card said the search was coming soon.

  • pkg/asinfo: Search ignores case and takes a limit. It returns the first matches up to the limit, in AS number order, and the number of all matches.
  • internal/server: new GET /api/v1/search/as?q={text}. Its data holds query, total and results, the first 50 matches. A missing or empty q answers 400.
  • internal/database: when the asns table has no row for an AS, its handle and description come from the built-in AS list, and its prefixes are still read. /as/{asn} and /api/v1/as/{asn} answer 404 only for an AS with no row, no built-in entry and no prefix.
  • internal/ristypes: NoOrigin names origin 0, no known origin. /as/0 and /api/v1/as/0 always answer 404; the search never lists AS 0.
  • internal/templates/index.html: the card lists the matches as links to /as/{asn}. It says how many more matched, or that nothing did, and shows an error line when the request fails. It writes names with textContent.

What the diff does not show:

  • The search reads the built-in AS list, not the database.
  • Separate handlers write an AS's routes and its row, so the routes often come first.
  • /api/v1/as/{asn} now writes an empty prefix list as [], not null. For an AS with no row it leaves out first_seen and last_seen, and the page leaves out the first-seen date.

Judgement call: the TODO.md entry tops Completed Steps, since its Next Step is the live-feed run.

Model: opus-5-5

Implements https://git.eeqj.de/sneak/routewatch/issues/151: the home page's AS Name Search card said the search was coming soon. - `pkg/asinfo`: `Search` ignores case and takes a limit. It returns the first matches up to the limit, in AS number order, and the number of all matches. - `internal/server`: new `GET /api/v1/search/as?q={text}`. Its `data` holds `query`, `total` and `results`, the first 50 matches. A missing or empty `q` answers 400. - `internal/database`: when the `asns` table has no row for an AS, its handle and description come from the built-in AS list, and its prefixes are still read. `/as/{asn}` and `/api/v1/as/{asn}` answer 404 only for an AS with no row, no built-in entry and no prefix. - `internal/ristypes`: `NoOrigin` names origin 0, no known origin. `/as/0` and `/api/v1/as/0` always answer 404; the search never lists AS 0. - `internal/templates/index.html`: the card lists the matches as links to `/as/{asn}`. It says how many more matched, or that nothing did, and shows an error line when the request fails. It writes names with `textContent`. What the diff does not show: - The search reads the built-in AS list, not the database. - Separate handlers write an AS's routes and its row, so the routes often come first. - `/api/v1/as/{asn}` now writes an empty prefix list as `[]`, not `null`. For an AS with no row it leaves out `first_seen` and `last_seen`, and the page leaves out the first-seen date. Judgement call: the `TODO.md` entry tops Completed Steps, since its Next Step is the live-feed run. Model: opus-5-5
clawbot added the needs-review label 2026-10-26 21:21:38 +01:00
clawbot self-assigned this 2026-10-26 21:21:38 +01:00
Author
Collaborator

FAIL: needs-rework.

  1. internal/server/handlers.go, handleASDetail and handleASDetailJSON: the search lists every AS in the built-in AS list, but /as/{asn} and /api/v1/as/{asn} answer 404 for any AS the database does not hold, so result links fail. The owner's rule on #151 is that nothing shown may fail. Acceptable: for an AS that is in the built-in list but not in the database, both answer 200 with the AS's number, handle and description, and the page shows its existing "No prefixes announced by this AS" state; an AS number in neither still answers 404. Tests cover all three cases. README "HTTP Endpoints" drops "Its /as/{asn} and /api/v1/as/{asn} then answer 404.", and its error paragraph says a 404 comes only for an AS number in neither.

  2. README.md, the /api/v1/search/as entry: "The search reads the AS list built into RouteWatch, taken from ipverse/asn-info, not the database." The inserted "taken from" clause pushes "not the database" to the end, where it reads as part of where the list came from. Acceptable: two short sentences, such as "The search reads the AS list built into RouteWatch, not the database. The list comes from ipverse/asn-info."

  3. pkg/asinfo/asinfo_test.go, TestSearch, the "Empty search" case: wantMin: 0 is only a lower bound, so the new empty-query guard in Search is untested. Without it, Search("") returns every AS and every test still passes. Acceptable: the case asserts that an empty query returns no matches.

  4. internal/server/server.go line 1: the package comment ("HTTP endpoints for status monitoring and statistics") does not cover the new AS name search handler. Acceptable: the PR amends the comment to cover the pages and lookups the package serves, the search included.

Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted here.

Model: opus-5-5

FAIL: needs-rework. 1. `internal/server/handlers.go`, `handleASDetail` and `handleASDetailJSON`: the search lists every AS in the built-in AS list, but `/as/{asn}` and `/api/v1/as/{asn}` answer 404 for any AS the database does not hold, so result links fail. The owner's rule on https://git.eeqj.de/sneak/routewatch/issues/151 is that nothing shown may fail. Acceptable: for an AS that is in the built-in list but not in the database, both answer 200 with the AS's number, handle and description, and the page shows its existing "No prefixes announced by this AS" state; an AS number in neither still answers 404. Tests cover all three cases. README "HTTP Endpoints" drops "Its `/as/{asn}` and `/api/v1/as/{asn}` then answer 404.", and its error paragraph says a 404 comes only for an AS number in neither. 2. `README.md`, the `/api/v1/search/as` entry: "The search reads the AS list built into RouteWatch, taken from [ipverse/asn-info](https://github.com/ipverse/asn-info), not the database." The inserted "taken from" clause pushes "not the database" to the end, where it reads as part of where the list came from. Acceptable: two short sentences, such as "The search reads the AS list built into RouteWatch, not the database. The list comes from ipverse/asn-info." 3. `pkg/asinfo/asinfo_test.go`, `TestSearch`, the "Empty search" case: `wantMin: 0` is only a lower bound, so the new empty-query guard in `Search` is untested. Without it, `Search("")` returns every AS and every test still passes. Acceptable: the case asserts that an empty query returns no matches. 4. `internal/server/server.go` line 1: the package comment ("HTTP endpoints for status monitoring and statistics") does not cover the new AS name search handler. Acceptable: the PR amends the comment to cover the pages and lookups the package serves, the search included. Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted here. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-26 21:32:48 +01:00
clawbot force-pushed issue-151-as-name-search from 50283d9e9e to ca68b8f605 2026-10-26 21:39:56 +01:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-26 21:40:30 +01:00
Author
Collaborator

Rework pushed as ca68b8f.

  1. Fixed in GetASDetailsContext in internal/database, so the page and the JSON answer share it. Tests cover the three cases for both answers, and the database lookup on its own. README updated as asked.
  2. Rewritten as two sentences.
  3. A separate test now asserts that an empty query returns no matches.
  4. The package comment now lists the pages, the JSON answers behind them, the IP lookups and the AS name search.

Judgement call: for an AS the database does not hold, the page shows "Never" as its first-seen date, and the JSON answer leaves out first_seen and last_seen, instead of showing the zero date. Empty prefix lists in /api/v1/as/{asn} are now [] for every AS, not null.

Model: opus-5-5

Rework pushed as `ca68b8f`. 1. Fixed in `GetASDetailsContext` in `internal/database`, so the page and the JSON answer share it. Tests cover the three cases for both answers, and the database lookup on its own. README updated as asked. 2. Rewritten as two sentences. 3. A separate test now asserts that an empty query returns no matches. 4. The package comment now lists the pages, the JSON answers behind them, the IP lookups and the AS name search. Judgement call: for an AS the database does not hold, the page shows "Never" as its first-seen date, and the JSON answer leaves out `first_seen` and `last_seen`, instead of showing the zero date. Empty prefix lists in `/api/v1/as/{asn}` are now `[]` for every AS, not `null`. Model: opus-5-5
Author
Collaborator

FAIL: needs-rework.

  1. internal/database/database.go, GetASDetailsContext: when asns has no row for the AS, the new fallback returns the built-in handle and description without reading the AS's prefixes. The AS and prefix handlers write separately, so routes are often stored before their AS's row. For such an AS, /as/{asn} now says "No prefixes announced by this AS" and "First Seen: Never", and /api/v1/as/{asn} lists no prefixes, although the database holds its routes and their prefix pages link to it. Acceptable: with no row, the handle and description come from the built-in list and the prefixes are still read; 404 only when there is no row, no built-in entry and no prefix; the page does not say "Never" beside listed prefixes. A test stores a route without an AS row and expects its prefix listed. README and the TODO.md entry match.

  2. README.md, the GET /as/{asn} and GET /api/v1/as/{asn} entries: "An AS that only the AS list built into RouteWatch knows gets a page with its handle and description and no prefixes." and "An AS that only the AS list built into RouteWatch knows has its handle and description and no prefixes." Each subject nests one clause inside another, so the main verb arrives late, right after another verb ("knows gets", "knows has"), and a newcomer has to reread. Acceptable: the condition first, in plain words, such as "When the database has no record of the AS, its handle and description come from the AS list built into RouteWatch."

Judgement call: the fallback sits in internal/database, which already takes handles and descriptions from the built-in list when it stores an AS, so it is not counted as a package-scope finding.
Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the first review.

Model: opus-5-5

FAIL: needs-rework. 1. `internal/database/database.go`, `GetASDetailsContext`: when `asns` has no row for the AS, the new fallback returns the built-in handle and description without reading the AS's prefixes. The AS and prefix handlers write separately, so routes are often stored before their AS's row. For such an AS, `/as/{asn}` now says "No prefixes announced by this AS" and "First Seen: Never", and `/api/v1/as/{asn}` lists no prefixes, although the database holds its routes and their prefix pages link to it. Acceptable: with no row, the handle and description come from the built-in list and the prefixes are still read; 404 only when there is no row, no built-in entry and no prefix; the page does not say "Never" beside listed prefixes. A test stores a route without an AS row and expects its prefix listed. README and the `TODO.md` entry match. 2. `README.md`, the `GET /as/{asn}` and `GET /api/v1/as/{asn}` entries: "An AS that only the AS list built into RouteWatch knows gets a page with its handle and description and no prefixes." and "An AS that only the AS list built into RouteWatch knows has its handle and description and no prefixes." Each subject nests one clause inside another, so the main verb arrives late, right after another verb ("knows gets", "knows has"), and a newcomer has to reread. Acceptable: the condition first, in plain words, such as "When the database has no record of the AS, its handle and description come from the AS list built into RouteWatch." Judgement call: the fallback sits in `internal/database`, which already takes handles and descriptions from the built-in list when it stores an AS, so it is not counted as a package-scope finding. Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the first review. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-26 21:51:22 +01:00
clawbot force-pushed issue-151-as-name-search from ca68b8f605 to 72034907a7 2026-10-26 21:58:34 +01:00 Compare
clawbot force-pushed issue-151-as-name-search from 72034907a7 to e9308df2a3 2026-10-26 21:59:32 +01:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-26 21:59:43 +01:00
Author
Collaborator

Rework pushed as e9308df.

  1. With no AS row, the handle and description come from the built-in list and the prefixes are still read; 404 comes only with no row, no built-in entry and no prefix. With no row the page leaves out the first-seen date. A new test stores a route for an AS with no row and expects its prefix on the page, in the JSON answer and from the database lookup. README and the TODO.md entry match.
  2. Both entries now open with "When the database has no record of the AS".

From the review's notes: pkg/asinfo puts each handle and description in lower case once, when the list loads, and sorts the list by AS number then, so a search only compares.

Model: opus-5-5

Rework pushed as `e9308df`. 1. With no AS row, the handle and description come from the built-in list and the prefixes are still read; 404 comes only with no row, no built-in entry and no prefix. With no row the page leaves out the first-seen date. A new test stores a route for an AS with no row and expects its prefix on the page, in the JSON answer and from the database lookup. README and the `TODO.md` entry match. 2. Both entries now open with "When the database has no record of the AS". From the review's notes: `pkg/asinfo` puts each handle and description in lower case once, when the list loads, and sorts the list by AS number then, so a search only compares. Model: opus-5-5
Author
Collaborator

FAIL: needs-rework.

  1. internal/database/database.go, GetASDetailsContext: AS 0 now gets a page. It is in the AS list built into RouteWatch (IANA-RSVD), and a route whose path ends in an AS set is stored with origin 0, which means no known origin (#75). So /as/0 and /api/v1/as/0, which answer 404 on next, now answer 200 and list those routes as prefixes AS 0 originates. The search puts AS 0 first for common text such as "iana" or "internet", so a result links to that page. Acceptable: /as/0 and /api/v1/as/0 answer 404 as on next, the search leaves AS 0 out so no result leads to a 404, tests cover both with a stored origin-0 route, and the README's 404 sentence says so.

  2. internal/server/handlers.go, handleASSearch: it takes every match from asinfo.Search to send 50 and a count. A one-letter query such as "e" matches about 115,000 ASes and allocates about 20 MB per request, on a public route, in a program with a tight memory budget (README "Memory"). Acceptable: the search counts every match but copies only the first 50, for example an asinfo search that takes the limit and returns the total.

  3. The PR body is about 265 words and the commit message body about 130. Acceptable: at most about 250 and 120 words.

Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the earlier reviews.
Judgement call: README sentences the change only re-wraps, without changing their words, are not counted.
Judgement call: ASN.FirstSeen and ASN.LastSeen now use the zero time for "no record", where the Go style guide asks for a *time.Time; not counted, since the fields predate the change and their comment says what zero means.
Unverified: the home page script was read, not run in a browser.

Model: opus-5-5

FAIL: needs-rework. 1. `internal/database/database.go`, `GetASDetailsContext`: AS 0 now gets a page. It is in the AS list built into RouteWatch (`IANA-RSVD`), and a route whose path ends in an AS set is stored with origin 0, which means no known origin (https://git.eeqj.de/sneak/routewatch/issues/75). So `/as/0` and `/api/v1/as/0`, which answer 404 on `next`, now answer 200 and list those routes as prefixes AS 0 originates. The search puts AS 0 first for common text such as "iana" or "internet", so a result links to that page. Acceptable: `/as/0` and `/api/v1/as/0` answer 404 as on `next`, the search leaves AS 0 out so no result leads to a 404, tests cover both with a stored origin-0 route, and the README's 404 sentence says so. 2. `internal/server/handlers.go`, `handleASSearch`: it takes every match from `asinfo.Search` to send 50 and a count. A one-letter query such as "e" matches about 115,000 ASes and allocates about 20 MB per request, on a public route, in a program with a tight memory budget (README "Memory"). Acceptable: the search counts every match but copies only the first 50, for example an `asinfo` search that takes the limit and returns the total. 3. The PR body is about 265 words and the commit message body about 130. Acceptable: at most about 250 and 120 words. Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the earlier reviews. Judgement call: README sentences the change only re-wraps, without changing their words, are not counted. Judgement call: `ASN.FirstSeen` and `ASN.LastSeen` now use the zero time for "no record", where the Go style guide asks for a `*time.Time`; not counted, since the fields predate the change and their comment says what zero means. Unverified: the home page script was read, not run in a browser. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-26 22:13:26 +01:00
clawbot force-pushed issue-151-as-name-search from e9308df2a3 to c360a82aa2 2026-10-26 22:20:44 +01:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-26 22:20:54 +01:00
Author
Collaborator

Rework pushed as c360a82.

  1. /as/0 and /api/v1/as/0 answer 404 whatever is stored, and the search leaves AS 0 out of its results and its total. The new ristypes.NoOrigin names origin 0 in both places. A test with a stored origin-0 route covers both, and the README's 404 sentence says so.
  2. asinfo.Search takes a limit and returns the first matches up to it and the number of all matches, copying only those it returns. handleASSearch uses it with its limit of 50. A test checks a query with more matches than the limit.
  3. The commit message body and the PR body are shortened to within about 120 and 250 words.

Judgement call: AS 0 is left out in handleASSearch, not in pkg/asinfo, which does not know what origin 0 means. AS 0 can only be the first match, so the handler asks for 51 matches to still show 50.
Judgement call: ASPath.Origin returns ristypes.NoOrigin instead of a bare 0, since the constant's comment names it.

Model: opus-5-5

Rework pushed as `c360a82`. 1. `/as/0` and `/api/v1/as/0` answer 404 whatever is stored, and the search leaves AS 0 out of its results and its total. The new `ristypes.NoOrigin` names origin 0 in both places. A test with a stored origin-0 route covers both, and the README's 404 sentence says so. 2. `asinfo.Search` takes a limit and returns the first matches up to it and the number of all matches, copying only those it returns. `handleASSearch` uses it with its limit of 50. A test checks a query with more matches than the limit. 3. The commit message body and the PR body are shortened to within about 120 and 250 words. Judgement call: AS 0 is left out in `handleASSearch`, not in `pkg/asinfo`, which does not know what origin 0 means. AS 0 can only be the first match, so the handler asks for 51 matches to still show 50. Judgement call: `ASPath.Origin` returns `ristypes.NoOrigin` instead of a bare 0, since the constant's comment names it. Model: opus-5-5
Author
Collaborator

FAIL: needs-rework.

  1. internal/server/handlers.go line 1143, handleASSearch: the handler asks asinfo.Search for 51 matches so that a search whose matches include AS 0 still lists 50, but no test checks that. A handler that asked for 50 would list 49 for such a search and every test would still pass. Acceptable: TestNoOriginHasNoASAnswers, or a test beside it, expects the "iana" search to list exactly maxASSearchResults matches.

  2. Four test comments describe earlier versions of this PR rather than next, so they will be false once merged:

    • internal/database/database_internal_test.go line 1707, TestGetASDetailsOfASNotInDatabase: "Before the fix an AS with no record got no prefixes". On next such an AS is ErrNoRoute.
    • internal/server/handlers_internal_test.go, TestASDetailJSONAnswersBuiltInAS and TestASDetailPageShowsBuiltInAS: "routeOnlyASN listed no prefixes". On next it answers 404.
    • Same file, line 1497, TestNoOriginHasNoASAnswers: "Before the fix both AS answers were 200 and listed the route's prefix, and the search listed AS 0 first". On next both answer 404, and there is no search.

    Acceptable: each "Before the fix" sentence says what next does, and is removed where next did not have the defect.

Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the earlier reviews.
Judgement call: README sentences the change only re-wraps, without changing their words, are not counted.
Judgement call: ASN.FirstSeen and ASN.LastSeen use the zero time for "no record" rather than a *time.Time; not counted, as in the third review.
Unverified: the home page script was run against a stand-in for the browser, not in a browser.

Model: opus-5-5

FAIL: needs-rework. 1. `internal/server/handlers.go` line 1143, `handleASSearch`: the handler asks `asinfo.Search` for 51 matches so that a search whose matches include AS 0 still lists 50, but no test checks that. A handler that asked for 50 would list 49 for such a search and every test would still pass. Acceptable: `TestNoOriginHasNoASAnswers`, or a test beside it, expects the "iana" search to list exactly `maxASSearchResults` matches. 2. Four test comments describe earlier versions of this PR rather than `next`, so they will be false once merged: - `internal/database/database_internal_test.go` line 1707, `TestGetASDetailsOfASNotInDatabase`: "Before the fix an AS with no record got no prefixes". On `next` such an AS is `ErrNoRoute`. - `internal/server/handlers_internal_test.go`, `TestASDetailJSONAnswersBuiltInAS` and `TestASDetailPageShowsBuiltInAS`: "routeOnlyASN listed no prefixes". On `next` it answers 404. - Same file, line 1497, `TestNoOriginHasNoASAnswers`: "Before the fix both AS answers were 200 and listed the route's prefix, and the search listed AS 0 first". On `next` both answer 404, and there is no search. Acceptable: each "Before the fix" sentence says what `next` does, and is removed where `next` did not have the defect. Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the earlier reviews. Judgement call: README sentences the change only re-wraps, without changing their words, are not counted. Judgement call: `ASN.FirstSeen` and `ASN.LastSeen` use the zero time for "no record" rather than a `*time.Time`; not counted, as in the third review. Unverified: the home page script was run against a stand-in for the browser, not in a browser. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-26 22:32:21 +01:00
clawbot added 1 commit 2026-10-26 22:35:48 +01:00
The AS Name Search card on the home page said the search was coming soon.
It now asks the new GET /api/v1/search/as?q=<text> and lists the matches
as links to their AS pages. asinfo.Search ignores case, returns the first
matches up to a limit in AS number order, and counts all matches. An
empty q answers 400.

When the database has no record of an AS, its page and /api/v1/as/{asn}
take its handle and description from asinfo and still list its prefixes.
They answer 404 only for an AS with no record, no asinfo entry and no
prefix. AS 0 means no known origin: it always answers 404, and the search
never lists it.

Model: opus-5-5
clawbot force-pushed issue-151-as-name-search from c360a82aa2 to 9daea4a710 2026-10-26 22:35:48 +01:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-26 22:35:52 +01:00
Author
Collaborator

Rework pushed as 9daea4a.

  1. TestNoOriginHasNoASAnswers now expects the "iana" search to list exactly maxASSearchResults matches.
  2. Three of the comments now say what next does; the one in TestNoOriginHasNoASAnswers is removed, since next already answers 404 there.

Judgement call: the two AS detail tests also drop "so a result of the AS name search could lead nowhere", since next has no search.

Model: opus-5-5

Rework pushed as `9daea4a`. 1. `TestNoOriginHasNoASAnswers` now expects the "iana" search to list exactly `maxASSearchResults` matches. 2. Three of the comments now say what `next` does; the one in `TestNoOriginHasNoASAnswers` is removed, since `next` already answers 404 there. Judgement call: the two AS detail tests also drop "so a result of the AS name search could lead nowhere", since `next` has no search. Model: opus-5-5
Author
Collaborator

PASS: ready to merge into next; the findings of the four earlier reviews are fixed and the change meets #151 and its plan.

Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the earlier reviews.
Judgement call: README sentences the change only re-wraps, without changing their words, are not counted.
Judgement call: ASN.FirstSeen and ASN.LastSeen use the zero time for "no record" rather than a *time.Time; not counted, as in the earlier reviews.
Unverified: the home page script was run against a stand-in for the browser, not in a browser.

Model: opus-5-5

PASS: ready to merge into `next`; the findings of the four earlier reviews are fixed and the change meets https://git.eeqj.de/sneak/routewatch/issues/151 and its plan. Judgement call: the README's first sentence (about 40 words, a comma-chained list) predates this change and is not counted, as in the earlier reviews. Judgement call: README sentences the change only re-wraps, without changing their words, are not counted. Judgement call: `ASN.FirstSeen` and `ASN.LastSeen` use the zero time for "no record" rather than a `*time.Time`; not counted, as in the earlier reviews. Unverified: the home page script was run against a stand-in for the browser, not in a browser. Model: opus-5-5
clawbot merged commit 99c7efc156 into next 2026-10-26 22:51:13 +01:00
clawbot deleted branch issue-151-as-name-search 2026-10-26 22:51:14 +01:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/routewatch#164