Files
orangesite/hn/server.go
sneak 3ab9637246 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
2026-07-26 23:55:21 +07:00

146 lines
3.0 KiB
Go

package hn
import (
"os"
"time"
"github.com/jinzhu/gorm"
// sqlite3 dialect registered with gorm via import side effects.
_ "github.com/jinzhu/gorm/dialects/sqlite"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
gl "github.com/labstack/gommon/log"
"github.com/mattn/go-isatty"
ep2 "github.com/mayowa/echo-pongo2"
"github.com/ziflex/lecho/v2"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
// App holds the running server's dependencies: the echo instance, the
// logger, the database handle, the startup time, and the background fetcher.
type App struct {
version string
buildarch string
e *echo.Echo
log *zerolog.Logger
db *gorm.DB
startup time.Time
fetcher *Fetcher
}
// RunServer boots the application, starts the background fetcher, and blocks
// serving HTTP until the server exits, returning a process exit code.
func RunServer(version string, buildarch string) int {
a := new(App)
a.version = version
a.buildarch = buildarch
a.startup = time.Now()
a.init()
defer func() { _ = a.db.Close() }()
a.fetcher = NewFetcher(a.db)
a.fetcher.AddLogger(a.log)
go a.fetcher.run()
return a.runForever()
}
func (a *App) init() {
// setup logging
l := log.With().Caller().Logger()
log.Logger = l
tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
if tty {
out := zerolog.NewConsoleWriter(
func(w *zerolog.ConsoleWriter) {
// Customize time format
w.TimeFormat = time.RFC3339
},
)
log.Logger = log.Output(out)
}
// always log in UTC
zerolog.TimestampFunc = func() time.Time {
return time.Now().UTC()
}
zerolog.SetGlobalLevel(zerolog.InfoLevel)
if os.Getenv("DEBUG") != "" {
zerolog.SetGlobalLevel(zerolog.DebugLevel)
}
a.log = &log.Logger
a.identify()
// database path defaults to the container data volume, overridable
// with the DATABASE_PATH environment variable
dbPath := "/data/storage.sqlite"
if os.Getenv("DATABASE_PATH") != "" {
dbPath = os.Getenv("DATABASE_PATH")
}
db, err := gorm.Open("sqlite3", dbPath)
if err != nil {
panic("failed to open database: " + err.Error())
}
a.db = db
}
func (a *App) identify() {
log.Info().
Str("version", a.version).
Str("buildarch", a.buildarch).
Msg("starting")
}
func (a *App) runForever() int {
// Echo instance
a.e = echo.New()
lev := gl.INFO
if os.Getenv("DEBUG") != "" {
lev = gl.DEBUG
}
logger := lecho.New(
os.Stdout,
lecho.WithLevel(lev),
lecho.WithTimestamp(),
lecho.WithCaller(),
)
a.e.Logger = logger
a.e.Use(middleware.RequestID())
// Middleware
a.e.Use(middleware.Logger())
a.e.Use(middleware.Recover())
r, err := ep2.NewRenderer("view")
if err != nil {
a.e.Logger.Fatal(err)
}
a.e.Renderer = r
rhs := NewRequestHandlerSet(a.version, a.db)
// Routes
a.e.Static("/static", "static")
a.e.GET("/", rhs.indexHandler)
a.e.GET("/about", rhs.aboutHandler)
// Start server (blocks; Fatal exits the process on error)
a.e.Logger.Fatal(a.e.Start(":8080"))
return 0
}