package api import ( "context" "log/slog" "net" "net/http" "net/url" "time" "git.yetaga.in/alazyreader/going-further/storage" ) type LinkProvider interface { Link(key string) *url.URL List() []string Details(key string) storage.Link } type SessionProvider interface { Login(username string, password string) (string, bool) // session token, valid Valid(token string) bool User(token string) string // token -> username } type UserProvider interface { List() []string // usernames Add(username string, password string) error Update(username string, password string) error Remove(username string) error } type shutdown struct { wait int timeout int } type Server struct { host string port string version string users UserProvider sessions SessionProvider links LinkProvider http *http.Server waits shutdown log *slog.Logger } func NewServer(logger *slog.Logger, port string, version string) *Server { return &Server{ log: logger, port: port, version: version, waits: shutdown{ wait: 10, timeout: 10, }, } } // Setup returns start and stop functions for the http service func (s *Server) Setup() (func(), func()) { mux := http.NewServeMux() mux.HandleFunc("GET /version", versionHandleFunc(s.log, s.version)) mux.HandleFunc("GET /login", getLoginHandleFunc(s.sessions, s.log)) mux.HandleFunc("POST /login", postLoginHandleFunc(s.sessions, s.log)) mux.HandleFunc("GET /manage", getManageHandleFunc(s.log)) mux.HandleFunc("GET /manage/links", getLinkListHandleFunc(s.links, s.log)) mux.HandleFunc("GET /manage/links/{name}", getLinkHandleFunc(s.links, s.log)) mux.HandleFunc("POST /manage/links", postLinkHandleFunc(s.links, s.log)) mux.HandleFunc("PUT /manage/links/{name}", putLinkHandleFunc(s.links, s.log)) mux.HandleFunc("DELETE /manage/links/{name}", deleteLinkHandleFunc(s.links, s.log)) mux.HandleFunc("GET /manage/users", getUserListHandleFunc(s.users, s.log)) mux.HandleFunc("GET /manage/users/{name}", getUserHandleFunc(s.users, s.log)) mux.HandleFunc("POST /manage/users", postUserHandleFunc(s.users, s.log)) mux.HandleFunc("PUT /manage/users/{name}", putUserHandleFunc(s.users, s.log)) mux.HandleFunc("DELETE /manage/users/{name}", deleteUserHandleFunc(s.users, s.log)) mux.HandleFunc("GET /static/", staticHandleFunc()) mux.HandleFunc("GET /", linkHandleFunc(s.links, s.log)) s.http = &http.Server{ Addr: net.JoinHostPort(s.host, s.port), Handler: mux, } return s.start(), s.stop() } func (s *Server) start() func() { return func() { s.log.Info("server startup", slog.String("addr", s.http.Addr)) if err := s.http.ListenAndServe(); err != nil && err != http.ErrServerClosed { s.log.Error("error listening and serving", slog.String("error", err.Error())) } } } func (s *Server) stop() func() { return func() { s.log.Info("server shutdown") time.Sleep(time.Duration(s.waits.wait) * time.Second) shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Duration(s.waits.timeout)*time.Second) defer cancel() if err := s.http.Shutdown(shutdownCtx); err != nil { s.log.Error("error shutting down http server", slog.String("error", err.Error())) } } }