start scaffolding
This commit is contained in:
parent
6e5af42d93
commit
d6dc30bea2
3
Makefile
Normal file
3
Makefile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
furthur: $(shell find . -type f -name "*.go")
|
||||||
|
go build ./cmd/furthur
|
||||||
|
|
18
api/auth.go
Normal file
18
api/auth.go
Normal 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
38
api/common.go
Normal 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
10
api/link.go
Normal 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
50
api/manage.go
Normal 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
117
api/server.go
Normal 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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
||||||
|
@ -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
1
storage/file.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package storage
|
1
storage/memory.go
Normal file
1
storage/memory.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package storage
|
4
storage/struct.go
Normal file
4
storage/struct.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user