rate review

This commit is contained in:
Erik Winter 2024-01-13 13:59:13 +01:00
parent 997efe2e4c
commit eaa920abf5
5 changed files with 317 additions and 64 deletions

View File

@ -162,3 +162,67 @@ func (e *EMDB) GetReviews(movieID string) ([]moviestore.Review, error) {
return reviews, nil return reviews, nil
} }
func (e *EMDB) GetNextUnratedReview() (moviestore.Review, error) {
url := fmt.Sprintf("%s/review/unrated/next", e.baseURL)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return moviestore.Review{}, err
}
req.Header.Add("Authorization", e.apiKey)
resp, err := e.c.Do(req)
if err != nil {
return moviestore.Review{}, err
}
if resp.StatusCode != http.StatusOK {
return moviestore.Review{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
defer resp.Body.Close()
var review moviestore.Review
if err := json.Unmarshal(body, &review); err != nil {
return moviestore.Review{}, err
}
return review, nil
}
func (e *EMDB) UpdateReview(review moviestore.Review) (moviestore.Review, error) {
body, err := json.Marshal(review)
if err != nil {
return moviestore.Review{}, err
}
url := fmt.Sprintf("%s/review/%s", e.baseURL, review.ID)
req, err := http.NewRequest(http.MethodPut, url, bytes.NewReader(body))
if err != nil {
return moviestore.Review{}, err
}
req.Header.Add("Authorization", e.apiKey)
resp, err := e.c.Do(req)
if err != nil {
return moviestore.Review{}, err
}
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
return moviestore.Review{}, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
newBody, err := io.ReadAll(resp.Body)
if err != nil {
return moviestore.Review{}, err
}
defer resp.Body.Close()
var newReview moviestore.Review
if err := json.Unmarshal(newBody, &newReview); err != nil {
return moviestore.Review{}, err
}
return newReview, nil
}

View File

@ -27,6 +27,8 @@ func (reviewAPI *ReviewAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
subPath, subTrail := ShiftPath(r.URL.Path) subPath, subTrail := ShiftPath(r.URL.Path)
subSubPath, _ := ShiftPath(subTrail) subSubPath, _ := ShiftPath(subTrail)
switch { switch {
case r.Method == http.MethodGet && subPath != "":
reviewAPI.Get(w, r, subPath)
case r.Method == http.MethodGet && subPath == "unrated" && subSubPath == "": case r.Method == http.MethodGet && subPath == "unrated" && subSubPath == "":
reviewAPI.ListUnrated(w, r) reviewAPI.ListUnrated(w, r)
case r.Method == http.MethodGet && subPath == "unrated" && subSubPath == "next": case r.Method == http.MethodGet && subPath == "unrated" && subSubPath == "next":
@ -38,6 +40,21 @@ func (reviewAPI *ReviewAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
func (reviewAPI *ReviewAPI) Get(w http.ResponseWriter, r *http.Request, id string) {
logger := reviewAPI.logger.With("method", "get")
review, err := reviewAPI.repo.FindOne(id)
if err != nil {
Error(w, http.StatusInternalServerError, "could not get review", err, logger)
return
}
if err := json.NewEncoder(w).Encode(review); err != nil {
Error(w, http.StatusInternalServerError, "could not encode review", err, logger)
return
}
}
func (reviewAPI *ReviewAPI) ListUnrated(w http.ResponseWriter, r *http.Request) { func (reviewAPI *ReviewAPI) ListUnrated(w http.ResponseWriter, r *http.Request) {
logger := reviewAPI.logger.With("method", "listUnrated") logger := reviewAPI.logger.With("method", "listUnrated")

View File

@ -0,0 +1,11 @@
package tui
type Review struct {
ID string
MovieID string
Source string
URL string
Review string
Quality int
Mentions []string
}

View File

@ -147,15 +147,6 @@ func (m tabEMDB) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, tea.Batch(cmds...) return m, tea.Batch(cmds...)
} }
func (m *tabEMDB) updateFormInputs(msg tea.Msg) tea.Cmd {
cmds := make([]tea.Cmd, 3)
m.inputWatchedOn, cmds[0] = m.inputWatchedOn.Update(msg)
m.inputRating, cmds[1] = m.inputRating.Update(msg)
m.inputComment, cmds[2] = m.inputComment.Update(msg)
return tea.Batch(cmds...)
}
func (m tabEMDB) View() string { func (m tabEMDB) View() string {
colLeft := lipgloss.NewStyle(). colLeft := lipgloss.NewStyle().
Width(m.colWidth - 2). Width(m.colWidth - 2).
@ -182,6 +173,58 @@ func (m *tabEMDB) UpdateForm() {
m.Log(fmt.Sprintf("showing movie %s", movie.m.ID)) m.Log(fmt.Sprintf("showing movie %s", movie.m.ID))
} }
func (m *tabEMDB) updateFormInputs(msg tea.Msg) tea.Cmd {
var cmd tea.Cmd
switch m.formFocus {
case 0:
m.inputWatchedOn, cmd = m.inputWatchedOn.Update(msg)
case 1:
m.inputRating, cmd = m.inputRating.Update(msg)
case 2:
m.inputComment, cmd = m.inputComment.Update(msg)
}
return cmd
}
func (m *tabEMDB) NavigateForm(key string) []tea.Cmd {
order := []string{"Watched on", "Rating", "Comment"}
var cmds []tea.Cmd
if key == "up" || key == "shift+tab" {
m.formFocus--
} else {
m.formFocus++
}
if m.formFocus >= len(order) {
m.formFocus = 0
}
if m.formFocus < 0 {
m.formFocus = len(order) - 1
}
switch order[m.formFocus] {
case "Watched on":
m.inputWatchedOn.PromptStyle = focusedStyle
m.inputWatchedOn.TextStyle = focusedStyle
cmds = append(cmds, m.inputWatchedOn.Focus())
m.inputRating.Blur()
m.inputComment.Blur()
case "Rating":
m.inputRating.PromptStyle = focusedStyle
m.inputRating.TextStyle = focusedStyle
cmds = append(cmds, m.inputRating.Focus())
m.inputWatchedOn.Blur()
m.inputComment.Blur()
case "Comment":
cmds = append(cmds, m.inputComment.Focus())
m.inputWatchedOn.Blur()
m.inputRating.Blur()
}
return cmds
}
func (m *tabEMDB) ViewForm() string { func (m *tabEMDB) ViewForm() string {
movie, ok := m.list.SelectedItem().(Movie) movie, ok := m.list.SelectedItem().(Movie)
if !ok { if !ok {
@ -215,44 +258,6 @@ func (m *tabEMDB) ViewForm() string {
return lipgloss.JoinHorizontal(lipgloss.Top, labelView, fieldsView) return lipgloss.JoinHorizontal(lipgloss.Top, labelView, fieldsView)
} }
func (m *tabEMDB) NavigateForm(key string) []tea.Cmd {
order := []string{"Watched on", "Rating", "Comment"}
var cmds []tea.Cmd
if key == "up" || key == "shift+tab" {
m.formFocus--
} else {
m.formFocus++
}
if m.formFocus > len(order) {
m.formFocus = 0
}
if m.formFocus < 0 {
m.formFocus = len(order)
}
switch order[m.formFocus] {
case "Watched on":
m.inputWatchedOn.PromptStyle = focusedStyle
m.inputWatchedOn.TextStyle = focusedStyle
cmds = append(cmds, m.inputWatchedOn.Focus())
m.inputRating.Blur()
m.inputComment.Blur()
case "Rating":
m.inputRating.PromptStyle = focusedStyle
m.inputRating.TextStyle = focusedStyle
cmds = append(cmds, m.inputRating.Focus())
m.inputWatchedOn.Blur()
m.inputComment.Blur()
case "Comment":
cmds = append(cmds, m.inputComment.Focus())
m.inputWatchedOn.Blur()
m.inputRating.Blur()
}
return cmds
}
func (m *tabEMDB) StoreMovie() tea.Cmd { func (m *tabEMDB) StoreMovie() tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
updatedMovie := m.list.SelectedItem().(Movie) updatedMovie := m.list.SelectedItem().(Movie)

View File

@ -1,23 +1,47 @@
package tui package tui
import ( import (
"fmt"
"strconv"
"strings"
"ewintr.nl/emdb/client" "ewintr.nl/emdb/client"
"ewintr.nl/emdb/cmd/api-service/moviestore"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
) )
type tabReview struct { type tabReview struct {
initialized bool initialized bool
emdb *client.EMDB emdb *client.EMDB
width int width int
height int height int
logger *Logger mode string
selectedReview moviestore.Review
inputQuality textinput.Model
inputMentions textarea.Model
formFocus int
logger *Logger
} }
func NewTabReview(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) { func NewTabReview(emdb *client.EMDB, logger *Logger) (tea.Model, tea.Cmd) {
inputQuality := textinput.New()
inputQuality.Prompt = ""
inputQuality.Width = 50
inputQuality.CharLimit = 500
inputMentions := textarea.New()
inputMentions.SetWidth(30)
inputMentions.SetHeight(5)
inputMentions.CharLimit = 500
return &tabReview{ return &tabReview{
emdb: emdb, emdb: emdb,
logger: logger, mode: "view",
inputQuality: inputQuality,
inputMentions: inputMentions,
logger: logger,
}, nil }, nil
} }
@ -35,23 +59,155 @@ func (m *tabReview) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.width = msg.Width m.width = msg.Width
m.height = msg.Height m.height = msg.Height
case tea.KeyMsg: case tea.KeyMsg:
switch msg.String() { switch m.mode {
case "q", "esc", "ctrl+c": case "edit":
return m, tea.Quit switch msg.String() {
case "right", "tab": case "tab", "shift+tab", "up", "down":
cmds = append(cmds, SelectNextTab()) cmds = append(cmds, m.NavigateForm(msg.String())...)
case "left", "shift+tab": case "esc":
cmds = append(cmds, SelectPrevTab()) m.mode = "view"
case "enter":
m.mode = "view"
cmds = append(cmds, m.StoreReview())
default:
cmds = append(cmds, m.updateFormInputs(msg))
}
default:
switch msg.String() {
case "q", "esc", "ctrl+c":
return m, tea.Quit
case "right", "tab":
cmds = append(cmds, SelectNextTab())
case "left", "shift+tab":
cmds = append(cmds, SelectPrevTab())
case "e":
m.mode = "edit"
m.formFocus = 0
cmds = append(cmds, m.inputQuality.Focus())
case "n":
m.mode = "edit"
m.formFocus = 0
m.logger.Log("fetching next unrated review")
cmds = append(cmds, m.inputQuality.Focus())
cmds = append(cmds, FetchNextUnratedReview(m.emdb))
}
} }
case moviestore.Review:
m.logger.Log(fmt.Sprintf("got review %s", msg.ID))
m.selectedReview = msg
m.UpdateForm()
} }
return m, tea.Batch(cmds...) return m, tea.Batch(cmds...)
} }
func (m *tabReview) View() string { func (m *tabReview) View() string {
return lipgloss.NewStyle(). colReviewWidth := m.width / 2
Width(m.width - 2). colRateWidth := m.width - colReviewWidth
colReview := lipgloss.NewStyle().
Width(colReviewWidth - 2).
Height(m.height - 2). Height(m.height - 2).
Padding(1). Padding(1).
Render("Review") Render(m.ViewReview())
colRate := lipgloss.NewStyle().
Width(colRateWidth - 2).
Height(m.height - 2).
Padding(1).
Render(m.ViewForm())
return lipgloss.JoinHorizontal(lipgloss.Top, colRate, colReview)
}
func (m *tabReview) UpdateForm() {
mentions := strings.Join(m.selectedReview.Mentions, ",")
m.inputQuality.SetValue(fmt.Sprintf("%d", m.selectedReview.Quality))
m.inputMentions.SetValue(mentions)
}
func (m *tabReview) updateFormInputs(msg tea.Msg) tea.Cmd {
var cmd tea.Cmd
switch m.formFocus {
case 0:
m.inputQuality, cmd = m.inputQuality.Update(msg)
case 1:
m.inputMentions, cmd = m.inputMentions.Update(msg)
}
return cmd
}
func (m *tabReview) NavigateForm(key string) []tea.Cmd {
order := []string{"quality", "mentions"}
var cmds []tea.Cmd
if key == "up" || key == "shift+tab" {
m.formFocus--
} else {
m.formFocus++
}
if m.formFocus >= len(order) {
m.formFocus = 0
}
if m.formFocus < 0 {
m.formFocus = len(order) - 1
}
switch order[m.formFocus] {
case "quality":
m.inputQuality.PromptStyle = focusedStyle
m.inputQuality.TextStyle = focusedStyle
cmds = append(cmds, m.inputQuality.Focus())
m.inputMentions.Blur()
case "mentions":
cmds = append(cmds, m.inputMentions.Focus())
m.inputQuality.Blur()
}
return cmds
}
func (m *tabReview) ViewForm() string {
labels := "Quality:\nMentions:"
fields := fmt.Sprintf("%s\n%s", m.inputQuality.View(), m.inputMentions.View())
return lipgloss.JoinHorizontal(lipgloss.Left, labels, fields)
}
func (m *tabReview) ViewReview() string {
review := strings.ReplaceAll(m.selectedReview.Review, "\n", "\n\n")
return review
}
func (m *tabReview) StoreReview() tea.Cmd {
return func() tea.Msg {
quality, err := strconv.Atoi(m.inputQuality.Value())
if err != nil {
return err
}
mentions := m.inputMentions.Value()
m.selectedReview.Quality = quality
m.selectedReview.Mentions = strings.Split(mentions, ",")
review, err := m.emdb.UpdateReview(m.selectedReview)
if err != nil {
return err
}
return review
}
}
func FetchNextUnratedReview(emdb *client.EMDB) tea.Cmd {
return func() tea.Msg {
review, err := emdb.GetNextUnratedReview()
if err != nil {
return err
}
return review
}
} }