final case class TrackedTimeDto(id: Option[Long], issue: Option[IssueDto], issueId: Option[Long], time: Option[Long], userId: Option[Long], userName: Option[String], created: Option[String])
Forgejo's TrackedTime model, field for field.
'''Derived from spec/swagger.v1.json, not from a captured response''': every timetracking endpoint requires a token and the harvest was anonymous. All seven declared properties are represented; two of them, issue_id and user_id, are kept here and dropped in conversion because the spec marks both "deprecated (only for backwards compatibility)" and both duplicate something the domain already has.
==Seconds become a duration here, and only here==
time is an int64 the spec documents as "Time in seconds". The conversion to scala.concurrent.duration.FiniteDuration happens at this boundary so that the unit exists in exactly one place; see com.worxbend.codeberg4s.issues.TrackedTime.spent for why the domain does not keep the raw number.
Converts to the domain, reporting failure paths relative to at.
Converts to the domain, reporting failure paths relative to at.
Two things are required. id addresses the entry — it is what DELETE …/times/{id} takes — and goes through com.worxbend.codeberg4s.issues.TrackedTimeId.from. time is required because a time entry that does not say how long is not an entry; an absent one is reported at $.time rather than becoming a silent zero, which would quietly corrupt any total a caller computed.
'''A negative time is accepted.''' Forgejo records a correction as a negative entry, so refusing one would lose a legitimate row; the resulting scala.concurrent.duration.FiniteDuration is negative, and a caller summing entries gets the right answer.
A failure inside issue is reported at $.issue, not at the entry's own path.