Skip to content

Feat/url checker - #988

Draft
gunnarvelle wants to merge 6 commits into
masterfrom
feat/url-checker
Draft

Feat/url checker#988
gunnarvelle wants to merge 6 commits into
masterfrom
feat/url-checker

Conversation

@gunnarvelle

Copy link
Copy Markdown
Member

Legger til funksjonalitet for å sjekke urler i artikler. Bruker quickRequest for å sende head-spørring og gjør følgende basert på svar:

  • 200: Alt er tipp topp
  • 30x: Bytt ut url med erstatning. Legg inn endringsloggmelding og endre status til I arbeid.
  • 40x og 50x: Legg til revisjonmeta en måned fram i tid der det legges til melding om url som ikkje fungerer.

Har også et endepunkt som kan kalles av en cronjobb for daglig vasking. Endepunktet kan også kalles for å gjøre en full vasking av databasen.

Copilot AI left a comment

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.

Pull request overview

This PR introduces a URL-checking workflow in draft-api to scan article HTML for external links, verify them via HTTP HEAD, and take automated actions (update redirected URLs + add editor notes, or schedule revisions for broken/unreachable URLs). It also adds an internal endpoint intended for cron-based daily “washing” of a partitioned slice of the article corpus.

Changes:

  • Added UrlCheckerService to extract URLs from article content, perform HEAD checks, update redirected URLs, and add EditorNote / RevisionMeta as appropriate.
  • Added DB support for selecting the latest revision of articles by modulus partitioning (article_id % modulus).
  • Added POST /intern/check-urls endpoint to run the checker for a given modulus slice (defaulting to “day-of-year” partitioning).

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
draft-api/src/test/scala/no/ndla/draftapi/TestEnvironment.scala Adds a mocked UrlCheckerService to the shared test environment.
draft-api/src/test/scala/no/ndla/draftapi/service/UrlCheckerServiceTest.scala New unit tests covering URL extraction, URL checking behavior, and draft update logic.
draft-api/src/main/scala/no/ndla/draftapi/service/UrlCheckerService.scala New service implementing URL extraction, HEAD checks, redirect updates, and revision scheduling for broken URLs.
draft-api/src/main/scala/no/ndla/draftapi/repository/DraftRepository.scala Adds getArticlesByModulus to fetch the latest revision per article for a modulus-selected slice.
draft-api/src/main/scala/no/ndla/draftapi/DraftApiProperties.scala Adds UrlCheckDaysInYear configuration for partitioning.
draft-api/src/main/scala/no/ndla/draftapi/controller/InternController.scala Adds POST /intern/check-urls endpoint to run the URL checker.
draft-api/src/main/scala/no/ndla/draftapi/ComponentRegistry.scala Registers UrlCheckerService in the DI registry.
common/src/main/scala/no/ndla/common/model/NDLADate.scala Adds plusMonths used for “revision one month ahead”.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread draft-api/src/main/scala/no/ndla/draftapi/service/UrlCheckerService.scala Outdated
Comment on lines +279 to +292
/** POST /intern/check-urls
*
* Runs the URL checker for a slice of articles selected by modulus arithmetic on article_id.
*
* Query parameters:
* - modulus (optional) – number of buckets; defaults to [[DraftApiProperties.UrlCheckDaysInYear]]. Set to 1 to
* process every article in a single run.
* - remainder (optional) – which bucket to process; defaults to the current day-of-year (1-based).
*
* Example – run today's scheduled slice: POST /intern/check-urls
*
* Example – force-check all articles right now: POST /intern/check-urls?modulus=1&remainder=0
*/
def checkUrls: ServerEndpoint[Any, Eff] = endpoint

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.

Denne kommentaren kunne vel like greit vært beskrivelse på endepunktet i form av .description(...) og .example(...)

quickRequest
.head(uri"$url")
.followRedirects(false)
.readTimeout(scala.concurrent.duration.Duration(10, "seconds"))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Snubla over urlen https://deploii.no som bruker laaang tid på å svare på head-kall, men svarer likevel. Så kanskje ha en lengre timeout?

@amatho amatho left a comment

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.

Jeg syns det blir litt tungvint med disse modulus greiene. Kunne ikke denne jobben bare kjørt på alle artiklene, og heller hatt en form for throttling hvis målet er å unngå å DoS'e lenkene som sjekkes?

@gunnarvelle

Copy link
Copy Markdown
Member Author

Det vil fort være en jobb som tar fleire timer kvar gong.

@jnatten

jnatten commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Det vil fort være en jobb som tar fleire timer kvar gong.

Hvor mange url'er er det snakk om totalt? Kan ikke forstå at det er tilfellet om vi parallelliserer det? Det bør jo være en rimelig IO heavy jobb som skalerer ganske bra med flere tråder.

@gunnarvelle

Copy link
Copy Markdown
Member Author

Forrige gang eg testa brukte jobben to minutt på ca 130 artikler. Om den skal ta 40000 artikler i en omgang vil det fort ta 40000 sekund, altså 11 timer.

@jnatten

jnatten commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Forrige gang eg testa brukte jobben to minutt på ca 130 artikler. Om den skal ta 40000 artikler i en omgang vil det fort ta 40000 sekund, altså 11 timer.

Vibekoda en parallellisert løsning basert på denne branchen nå (oneshot ingen optimalisering forutenom å be den parallelisere), kjørte gjennom hele test databasen på <30 minutter på min maskin i alle fall.

@gunnarvelle
gunnarvelle marked this pull request as draft June 11, 2026 12:20
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.

4 participants