start scaffolding

This commit is contained in:
David 2024-04-22 21:53:20 -04:00
parent 6e5af42d93
commit d6dc30bea2
11 changed files with 270 additions and 2 deletions

3
Makefile Normal file
View File

@ -0,0 +1,3 @@
furthur: $(shell find . -type f -name "*.go")
go build ./cmd/furthur

18
api/auth.go Normal file
View File

@ -0,0 +1,18 @@
package api
import (
"log/slog"
"net/http"
)
func getLoginHandleFunc(sessions SessionProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// login html page
}
}
func postLoginHandleFunc(sessions SessionProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// login html page
}
}

38
api/common.go Normal file
View File

@ -0,0 +1,38 @@
package api
import (
"log/slog"
"net/http"
)
func versionHandleFunc(log *slog.Logger, version string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
log.Debug("version handler")
w.WriteHeader(http.StatusOK)
w.Write([]byte(version))
}
}
func staticHandleFunc() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
}
}
func sessionMiddleware(sessions SessionProvider, log *slog.Logger) func(http.HandlerFunc) http.HandlerFunc {
return func(h http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
c, err := r.Cookie("FURTHUR_SESS")
if err != nil {
log.Warn("error loading session")
http.NotFound(w, r)
return
}
if !sessions.Valid(c.Value) {
log.Warn("user provided invalid session")
http.NotFound(w, r)
return
}
h(w, r)
})
}
}

10
api/link.go Normal file
View File

@ -0,0 +1,10 @@
package api
import (
"log/slog"
"net/http"
)
func linkHandleFunc(links LinkProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}

50
api/manage.go Normal file
View File

@ -0,0 +1,50 @@
package api
import (
"log/slog"
"net/http"
)
func getManageHandleFunc(log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func getLinkListHandleFunc(links LinkProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func getLinkHandleFunc(links LinkProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func postLinkHandleFunc(links LinkProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func putLinkHandleFunc(links LinkProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func deleteLinkHandleFunc(links LinkProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func getUserListHandleFunc(users UserProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func getUserHandleFunc(sers UserProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func postUserHandleFunc(sers UserProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func putUserHandleFunc(sers UserProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}
func deleteUserHandleFunc(sers UserProvider, log *slog.Logger) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {}
}

117
api/server.go Normal file
View File

@ -0,0 +1,117 @@
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()))
}
}
}

View File

@ -1,3 +1,26 @@
package main package main
func main() {} import (
"log/slog"
"os"
"os/signal"
"git.yetaga.in/alazyreader/going-further/api"
)
func main() {
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)).
With(slog.String("application", "furthur"))
slog.SetDefault(logger)
server := api.NewServer(logger, "8080", "v0.0.1")
start, stop := server.Setup()
start()
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt)
<-c
stop()
}

View File

@ -65,11 +65,14 @@ It turns out channels are hard to reason about, but that's because _concurrency_
hard to reason about. hard to reason about.
Always remember that read/write access to a shared map _must_ be gated with a mutex. Always remember that read/write access to a shared map _must_ be gated with a mutex.
Concurrent read-only access is safe, however.
## Go Generate ## Go Generate
## Build tags ## Build tags
## Templates
## Logging ## Logging
`slog` package `slog` package
@ -77,7 +80,7 @@ Always remember that read/write access to a shared map _must_ be gated with a mu
## init functions and globals ## init functions and globals
Don't use them! They're hard to reason about and until recent versions of go Don't use them! They're hard to reason about and until recent versions of go
the order they ran in was undefined, leading to subtle bugs. the order they ran in was under-defined, leading to subtle bugs.
## Common tools ## Common tools

1
storage/file.go Normal file
View File

@ -0,0 +1 @@
package storage

1
storage/memory.go Normal file
View File

@ -0,0 +1 @@
package storage

4
storage/struct.go Normal file
View File

@ -0,0 +1,4 @@
package storage
type Link struct {
}