Correcting project layout
This commit is contained in:
136
internal/cli/client/client.go
Normal file
136
internal/cli/client/client.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const baseURL = "http://localhost:8080"
|
||||
|
||||
var httpClient = &http.Client{}
|
||||
|
||||
func doRequest(method, url string, body any) []byte {
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to marshal request body")
|
||||
os.Exit(1)
|
||||
}
|
||||
reqBody = bytes.NewReader(data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, url, reqBody)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to create request")
|
||||
os.Exit(1)
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(req)
|
||||
if err != nil {
|
||||
fmt.Println("Failed to send request is the server running?")
|
||||
os.Exit(1)
|
||||
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to read response body: %v", err)
|
||||
}
|
||||
return respBody
|
||||
}
|
||||
|
||||
func ListNotes() {
|
||||
body := doRequest(http.MethodGet, baseURL+"/notes", nil)
|
||||
var notes []struct {
|
||||
ID int `json:"id"`
|
||||
Content string `json:"content"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
}
|
||||
if err := json.Unmarshal(body, ¬es); err != nil {
|
||||
log.Fatalf("Failed to parse notes: %v", err)
|
||||
}
|
||||
if len(notes) == 0 {
|
||||
fmt.Println("No notes found.")
|
||||
return
|
||||
}
|
||||
|
||||
// Calculate column widths from headers and content
|
||||
idW, updatedW, contentW := 2, 7, 7 // minimums: "ID", "Updated", "Content"
|
||||
for _, n := range notes {
|
||||
if w := len(fmt.Sprintf("%d", n.ID)); w > idW {
|
||||
idW = w
|
||||
}
|
||||
if w := len(n.LastUpdate.Format("2006-01-02 15:04:05")); w > updatedW {
|
||||
updatedW = w
|
||||
}
|
||||
if w := len(n.Content); w > contentW {
|
||||
contentW = w
|
||||
}
|
||||
}
|
||||
|
||||
row := func(id, updated, content string) {
|
||||
fmt.Printf("| %-*s | %-*s | %-*s |\n", idW, id, updatedW, updated, contentW, content)
|
||||
}
|
||||
sep := fmt.Sprintf("|-%s-|-%s-|-%s-|", strings.Repeat("-", idW), strings.Repeat("-", updatedW), strings.Repeat("-", contentW))
|
||||
fmt.Println(strings.Repeat("-", idW+updatedW+contentW+10))
|
||||
row("ID", "Updated", "Content")
|
||||
fmt.Println(sep)
|
||||
for _, n := range notes {
|
||||
row(fmt.Sprintf("%d", n.ID), n.LastUpdate.Format("2006-01-02 15:04:05"), n.Content)
|
||||
}
|
||||
fmt.Println(strings.Repeat("-", idW+updatedW+contentW+10))
|
||||
}
|
||||
|
||||
func CreateNote(content string) {
|
||||
body := doRequest(http.MethodPost, baseURL+"/notes", map[string]string{"content": content})
|
||||
var note struct {
|
||||
ID int `json:"id"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := json.Unmarshal(body, ¬e); err != nil {
|
||||
log.Fatalf("Failed to parse note: %v", err)
|
||||
}
|
||||
fmt.Printf("Created note #%d: %s\n", note.ID, note.Content)
|
||||
}
|
||||
|
||||
func GetNoteByID(id int) {
|
||||
body := doRequest(http.MethodGet, fmt.Sprintf("%s/note/%d", baseURL, id), nil)
|
||||
var note struct {
|
||||
ID int `json:"id"`
|
||||
Content string `json:"content"`
|
||||
LastUpdate time.Time `json:"last_update"`
|
||||
}
|
||||
if err := json.Unmarshal(body, ¬e); err != nil {
|
||||
log.Fatalf("Failed to parse note: %v", err)
|
||||
}
|
||||
fmt.Printf("Note #%d (updated %s):\n %s\n", note.ID, note.LastUpdate.Format("2006-01-02 15:04:05"), note.Content)
|
||||
}
|
||||
|
||||
func UpdateNoteByID(id int, content string) {
|
||||
body := doRequest(http.MethodPut, fmt.Sprintf("%s/note/%d", baseURL, id), map[string]string{"content": content})
|
||||
var note struct {
|
||||
ID int `json:"id"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
if err := json.Unmarshal(body, ¬e); err != nil {
|
||||
log.Fatalf("Failed to parse note: %v", err)
|
||||
}
|
||||
fmt.Printf("Updated note #%d: %s\n", note.ID, note.Content)
|
||||
}
|
||||
|
||||
func DeleteNoteByID(id int) {
|
||||
doRequest(http.MethodDelete, fmt.Sprintf("%s/note/%d", baseURL, id), nil)
|
||||
fmt.Printf("Deleted note #%d\n", id)
|
||||
}
|
||||
153
internal/cli/server/server.go
Normal file
153
internal/cli/server/server.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.hrafn.xyz/aether/gotes/internal/repository"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
httpServer *http.Server
|
||||
noteRepo *repository.NoteRepository
|
||||
logger *log.Logger
|
||||
}
|
||||
|
||||
func GetServer(repo *repository.NoteRepository, logger *log.Logger) Server {
|
||||
mux := http.NewServeMux()
|
||||
server := Server{
|
||||
httpServer: &http.Server{Handler: loggingMiddleware(mux, logger)},
|
||||
noteRepo: repo,
|
||||
logger: logger,
|
||||
}
|
||||
mux.HandleFunc("GET /notes", server.getNotes)
|
||||
mux.HandleFunc("POST /notes", server.createNote)
|
||||
mux.HandleFunc("GET /note/{id}", server.getNoteByID)
|
||||
mux.HandleFunc("PUT /note/{id}", server.updateNoteByID)
|
||||
mux.HandleFunc("DELETE /note/{id}", server.deleteNoteByID)
|
||||
return server
|
||||
}
|
||||
|
||||
func (s *Server) Start(addr string) error {
|
||||
s.httpServer.Addr = addr
|
||||
s.logger.Printf("Starting server on %s", addr)
|
||||
return s.httpServer.ListenAndServe()
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
return s.httpServer.Close()
|
||||
}
|
||||
|
||||
func loggingMiddleware(next http.Handler, logger *log.Logger) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
|
||||
next.ServeHTTP(wrapped, r)
|
||||
duration := time.Since(start)
|
||||
logger.Printf("%s %s - %d (%dms)", r.Method, r.RequestURI, wrapped.statusCode, duration.Milliseconds())
|
||||
})
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
rw.statusCode = code
|
||||
rw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func (s *Server) getJsonResponse(w http.ResponseWriter, data any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
s.logger.Printf("failed to encode response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) getErrorResponse(w http.ResponseWriter, err error) {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func (s *Server) getNotes(w http.ResponseWriter, r *http.Request) {
|
||||
notes, err := s.noteRepo.ListNotes(r.Context())
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
s.getJsonResponse(w, notes)
|
||||
}
|
||||
|
||||
func (s *Server) createNote(w http.ResponseWriter, r *http.Request) {
|
||||
note := struct {
|
||||
Content string `json:"content"`
|
||||
Title string `json:"title"`
|
||||
}{}
|
||||
if err := json.NewDecoder(r.Body).Decode(¬e); err != nil {
|
||||
http.Error(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
createdNote, err := s.noteRepo.CreateNote(r.Context(), note.Content, note.Title)
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
s.getJsonResponse(w, createdNote)
|
||||
}
|
||||
|
||||
func (s *Server) getNoteByID(w http.ResponseWriter, r *http.Request) {
|
||||
ids := r.PathValue("id")
|
||||
id, err := strconv.Atoi(ids)
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, fmt.Errorf("invalid note ID: %w", err))
|
||||
return
|
||||
}
|
||||
note, err := s.noteRepo.GetNote(r.Context(), id)
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
s.getJsonResponse(w, note)
|
||||
}
|
||||
|
||||
func (s *Server) updateNoteByID(w http.ResponseWriter, r *http.Request) {
|
||||
ids := r.PathValue("id")
|
||||
id, err := strconv.Atoi(ids)
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, fmt.Errorf("invalid note ID: %w", err))
|
||||
return
|
||||
}
|
||||
note := struct {
|
||||
Content string `json:"content"`
|
||||
Title string `json:"title"`
|
||||
}{}
|
||||
if err := json.NewDecoder(r.Body).Decode(¬e); err != nil {
|
||||
http.Error(w, "invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
updatedNote, err := s.noteRepo.UpdateNote(r.Context(), id, repository.NoteUpdate{Content: note.Content, Title: note.Title})
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
s.getJsonResponse(w, updatedNote)
|
||||
}
|
||||
|
||||
func (s *Server) deleteNoteByID(w http.ResponseWriter, r *http.Request) {
|
||||
ids := r.PathValue("id")
|
||||
id, err := strconv.Atoi(ids)
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, fmt.Errorf("invalid note ID: %w", err))
|
||||
return
|
||||
}
|
||||
err = s.noteRepo.DeleteNote(r.Context(), id)
|
||||
if err != nil {
|
||||
s.getErrorResponse(w, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user