bring repo into policy compliance; vendor assets; Gitea CI

Vendor the front-end assets and drop the third-party CDN dependencies
(BootstrapCDN is being sunset): the bootstrap 4.0.0 css/js, jquery
3.2.1 slim, and popper 1.12.9 now live under static/ and are served
from the app, byte-for-byte identical to the previous SRI-pinned files.

Migrate CI from Drone to a Gitea Actions workflow that runs
docker build . on push, with the checkout action pinned by SHA.

Bring the repo up to standard:

- add REPO_POLICIES.md, .editorconfig, .dockerignore, .golangci.yml,
  and a comprehensive root-anchored .gitignore
- rewrite the Makefile with the required test/lint/fmt/fmt-check/check/
  docker/hooks targets (golangci-lint, 30s test timeout, verbose rerun
  on failure, check modifies nothing)
- rewrite the Dockerfile as a hash-pinned multistage build: a lint
  stage (golangci-lint), a glibc build+test stage (the legacy sqlite
  driver needs cgo+glibc), and a debian-slim runtime carrying the
  binary, templates, and static assets
- add real tests for the hn package
- bring the code into golangci-lint (default: all) compliance: fix the
  malformed gorm struct tags, check previously-ignored errors, dispatch
  the zerolog error event, avoid a uint->Duration overflow, split long
  functions, and add doc comments — all behaviour-preserving
- expand the README with the required sections
This commit is contained in:
2026-07-26 23:55:21 +07:00
parent 000f5bbaa6
commit 3ab9637246
23 changed files with 1009 additions and 321 deletions

View File

@@ -10,38 +10,58 @@ import (
"github.com/labstack/echo"
)
// RequestHandlerSet holds the dependencies shared by the HTTP handlers.
type RequestHandlerSet struct {
db *gorm.DB
version string
}
// NewRequestHandlerSet builds a RequestHandlerSet for the given version and
// database handle.
func NewRequestHandlerSet(version string, db *gorm.DB) *RequestHandlerSet {
rhs := new(RequestHandlerSet)
rhs.db = db
rhs.version = version
return rhs
}
func (r *RequestHandlerSet) indexHandler(c echo.Context) error {
// exitRow is a story that has left the front page within the last 24h.
type exitRow struct {
Duration string
DurationSecs uint
URL string
Title string
HighestRank uint
HNID uint
Score uint
TimeGone string
TimeGoneSecs uint
}
// currentRow is a story currently on the front page.
type currentRow struct {
Duration string
DurationSecs uint
URL string
Title string
Score uint
HighestRank uint
HNID uint
Rank uint
}
// exitedRows returns the stories that left the front page in the last 24h,
// most recently departed first.
func (r *RequestHandlerSet) exitedRows() []exitRow {
last24h := time.Now().Add(time.Second * 86400 * -1)
var fpi []HNFrontPage
r.db.Where("disappeared is not ? and disappeared > ?", time.Time{}, last24h).Order("disappeared desc").Find(&fpi)
type fprow struct {
Duration string
DurationSecs uint
URL string
Title string
HighestRank uint
HNID uint
Score uint
TimeGone string
TimeGoneSecs uint
}
var fprows []fprow
rows := make([]exitRow, 0, len(fpi))
for _, item := range fpi {
fprows = append(fprows, fprow{
rows = append(rows, exitRow{
Duration: u.TimeDiffHuman(item.Disappeared, item.Appeared),
DurationSecs: u.TimeDiffAbsSeconds(item.Disappeared, item.Appeared),
URL: item.URL,
@@ -54,23 +74,17 @@ func (r *RequestHandlerSet) indexHandler(c echo.Context) error {
})
}
type rowtwo struct {
Duration string
DurationSecs uint
URL string
Title string
Score uint
HighestRank uint
HNID uint
Rank uint
}
var currentfp []rowtwo
return rows
}
// currentRows returns the stories currently on the front page, best rank first.
func (r *RequestHandlerSet) currentRows() []currentRow {
var cur []HNFrontPage
r.db.Where("disappeared is ?", time.Time{}).Order("rank asc").Find(&cur)
rows := make([]currentRow, 0, len(cur))
for _, item := range cur {
currentfp = append(currentfp, rowtwo{
rows = append(rows, currentRow{
Duration: u.TimeDiffHuman(time.Now(), item.Appeared),
DurationSecs: u.TimeDiffAbsSeconds(time.Now(), item.Appeared),
URL: item.URL,
@@ -82,12 +96,17 @@ func (r *RequestHandlerSet) indexHandler(c echo.Context) error {
})
}
return rows
}
func (r *RequestHandlerSet) indexHandler(c echo.Context) error {
tc := pongo2.Context{
"time": time.Now().UTC().Format(time.RFC3339Nano),
"exits": fprows,
"current": currentfp,
"exits": r.exitedRows(),
"current": r.currentRows(),
"gitrev": r.version,
}
return c.Render(http.StatusOK, "index.html", tc)
}
@@ -96,5 +115,6 @@ func (r *RequestHandlerSet) aboutHandler(c echo.Context) error {
"time": time.Now().UTC().Format(time.RFC3339Nano),
"gitrev": r.version,
}
return c.Render(http.StatusOK, "about.html", tc)
}