start scaffolding
This commit is contained in:
		
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
## Build tags
 | 
			
		||||
 | 
			
		||||
## Templates
 | 
			
		||||
 | 
			
		||||
## Logging
 | 
			
		||||
 | 
			
		||||
`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
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 {
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user