package server import ( "encoding/json" "fmt" "log" "net/http" "strconv" "time" "git.hrafn.xyz/aether/notes/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) }