finish the frontend functionality; needs styling
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				ci/woodpecker/push/woodpecker Pipeline was successful
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	ci/woodpecker/push/woodpecker Pipeline was successful
				
			This commit is contained in:
		@@ -2,8 +2,10 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"io/fs"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"git.yetaga.in/alazyreader/library/media"
 | 
			
		||||
@@ -30,6 +32,7 @@ func (h path) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func writeJSONerror(w http.ResponseWriter, err string, status int) {
 | 
			
		||||
	log.Println(err)
 | 
			
		||||
	writeJSON(w, struct{ Status, Reason string }{Status: "error", Reason: err}, status)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -97,50 +100,26 @@ func getBooks(l Library, w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func addBook(l Library, w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	if r.Body == nil {
 | 
			
		||||
		writeJSONerror(w, "no body provided", http.StatusBadRequest)
 | 
			
		||||
	book, err := ReadBody[media.Book](r.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, err.Error(), http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Body.Close()
 | 
			
		||||
	b, err := io.ReadAll(r.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, "error reading body", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	book := &media.Book{}
 | 
			
		||||
	err = json.Unmarshal(b, book)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, "error parsing body", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = l.AddBook(r.Context(), book)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, "error parsing body", http.StatusBadRequest)
 | 
			
		||||
	if err = l.AddBook(r.Context(), book); err != nil {
 | 
			
		||||
		writeJSONerror(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.WriteHeader(http.StatusAccepted)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func deleteBook(l Library, w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	if r.Body == nil {
 | 
			
		||||
		writeJSONerror(w, "no body provided", http.StatusBadRequest)
 | 
			
		||||
	book, err := ReadBody[media.Book](r.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, err.Error(), http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Body.Close()
 | 
			
		||||
	b, err := io.ReadAll(r.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, "error reading body", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	book := &media.Book{}
 | 
			
		||||
	err = json.Unmarshal(b, book)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, "error parsing body", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	err = l.DeleteBook(r.Context(), book)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, "error deleting book", http.StatusInternalServerError)
 | 
			
		||||
	if err = l.DeleteBook(r.Context(), book); err != nil {
 | 
			
		||||
		writeJSONerror(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	w.WriteHeader(http.StatusAccepted)
 | 
			
		||||
@@ -165,12 +144,12 @@ func getWhoAmI(ts *tailscale.LocalClient, w http.ResponseWriter, r *http.Request
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	isbn := r.FormValue("isbn")
 | 
			
		||||
	if len(isbn) != 10 && len(isbn) != 13 {
 | 
			
		||||
		writeJSONerror(w, "invalid isbn", http.StatusBadRequest)
 | 
			
		||||
	req, err := ReadBody[media.Book](r.Body)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, err.Error(), http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	book, err := query.GetByISBN(isbn)
 | 
			
		||||
	book, err := query.GetByISBN(req.ISBN13)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		writeJSONerror(w, err.Error(), http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
@@ -181,3 +160,20 @@ func lookupBook(query Query, w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
func static(f fs.FS) http.Handler {
 | 
			
		||||
	return http.FileServer(http.FS(f))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ReadBody[T any](r io.ReadCloser) (*T, error) {
 | 
			
		||||
	t := new(T)
 | 
			
		||||
	if r == nil {
 | 
			
		||||
		return t, fmt.Errorf("no body provided")
 | 
			
		||||
	}
 | 
			
		||||
	defer r.Close()
 | 
			
		||||
	b, err := io.ReadAll(r)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return t, fmt.Errorf("error reading body: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	err = json.Unmarshal(b, t)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return t, fmt.Errorf("error reading body: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	return t, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -45,50 +45,44 @@ func main() {
 | 
			
		||||
	must.Do(envconfig.Process("library", &c))
 | 
			
		||||
 | 
			
		||||
	var lib Library
 | 
			
		||||
	var err error
 | 
			
		||||
	if c.DBType == "memory" {
 | 
			
		||||
		lib = &database.Memory{}
 | 
			
		||||
	} else if c.DBType == "sql" {
 | 
			
		||||
		var latest, run int
 | 
			
		||||
		lib, latest, run, err = setupSQL(c)
 | 
			
		||||
		sqllib, latest, run, err := setupSQL(c)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			log.Fatalf("err starting sql connection: %v", err)
 | 
			
		||||
			log.Fatalf("sql connection err: %v", err)
 | 
			
		||||
		}
 | 
			
		||||
		log.Printf("latest migration: %d; migrations run: %d", latest, run)
 | 
			
		||||
		lib = sqllib
 | 
			
		||||
	}
 | 
			
		||||
	discogsCache := must.Get(database.NewDiscogsCache(
 | 
			
		||||
		c.DiscogsToken, time.Hour*24, c.DiscogsUser, c.DiscogsPersist, c.DiscogsCacheFile,
 | 
			
		||||
	))
 | 
			
		||||
 | 
			
		||||
	queryProvider := &query.GoogleBooks{}
 | 
			
		||||
 | 
			
		||||
	staticRoot := must.Get(frontend.Root())
 | 
			
		||||
 | 
			
		||||
	servers := make(chan (*http.Server), 3)
 | 
			
		||||
	errGroup := errgroup.Group{}
 | 
			
		||||
	errGroup.Go(func() error {
 | 
			
		||||
		return start(servers)(
 | 
			
		||||
			publicServer(8080, &Router{
 | 
			
		||||
				static:  staticRoot,
 | 
			
		||||
				lib:     lib,
 | 
			
		||||
				rcol:    discogsCache,
 | 
			
		||||
				isAdmin: false,
 | 
			
		||||
			}))
 | 
			
		||||
		return start(servers)(publicServer(8080, &Router{
 | 
			
		||||
			static:  staticRoot,
 | 
			
		||||
			lib:     lib,
 | 
			
		||||
			rcol:    discogsCache,
 | 
			
		||||
			isAdmin: false,
 | 
			
		||||
		}))
 | 
			
		||||
	})
 | 
			
		||||
	errGroup.Go(func() error {
 | 
			
		||||
		return start(servers)(
 | 
			
		||||
			tailscaleListener("library-admin", &Router{
 | 
			
		||||
				static:  staticRoot,
 | 
			
		||||
				lib:     lib,
 | 
			
		||||
				rcol:    discogsCache,
 | 
			
		||||
				query:   queryProvider,
 | 
			
		||||
				isAdmin: true,
 | 
			
		||||
			}))
 | 
			
		||||
		return start(servers)(tailscaleListener("library-admin", &Router{
 | 
			
		||||
			static:  staticRoot,
 | 
			
		||||
			lib:     lib,
 | 
			
		||||
			rcol:    discogsCache,
 | 
			
		||||
			query:   queryProvider,
 | 
			
		||||
			isAdmin: true,
 | 
			
		||||
		}))
 | 
			
		||||
	})
 | 
			
		||||
	errGroup.Go(func() error {
 | 
			
		||||
		return shutdown(servers)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	log.Println(errGroup.Wait())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -172,6 +166,5 @@ func tailscaleListener(hostname string, handler *Router) (*http.Server, net.List
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
	log.Printf("management server: http://%s/", hostname)
 | 
			
		||||
	server := &http.Server{Handler: handler}
 | 
			
		||||
	return server, ln, nil
 | 
			
		||||
	return &http.Server{Handler: handler}, ln, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user