From 88a859a88be9c5d3effdbf48795fe5782fc66e4c Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 5 May 2024 18:43:00 +0200 Subject: [PATCH] wip: Add user entries status --- api/cmd/entries.go | 51 +++++++++++++++++++++- api/entries.go | 103 ++++++++++++++++++++++++++++++++++++--------- api/feeds.go | 1 + sql/create.sql | 9 +++- 4 files changed, 142 insertions(+), 22 deletions(-) diff --git a/api/cmd/entries.go b/api/cmd/entries.go index 1e6b263..d3859b6 100644 --- a/api/cmd/entries.go +++ b/api/cmd/entries.go @@ -1,11 +1,57 @@ package main import ( + "net/http" + + "github.com/google/uuid" "github.com/labstack/echo/v4" + "github.com/zoriya/vex" ) +type ChangeEntryStatusDto struct { + Id uuid.UUID `json:"id" validate:"required"` + IsRead bool `json:"isRead"` + IsBookmarked bool `json:"isBookmarked"` + IsReadLater bool `json:"isReadLater"` + IsIgnored bool `json:"isIgnored"` +} + func (h *Handler) GetEntries(c echo.Context) error { - ret, err := h.entries.ListEntries() + user, err := GetCurrentUserId(c) + if err != nil { + return err + } + ret, err := h.entries.ListEntries(user) + if err != nil { + return err + } + return c.JSON(200, ret) +} + +func (h *Handler) ChangeUserStatus(c echo.Context) error { + user, err := GetCurrentUserId(c) + if err != nil { + return err + } + + var req ChangeEntryStatusDto + err = c.Bind(&req) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + if err = c.Validate(&req); err != nil { + return err + } + + h.entries.ChangeStatus(vex.ChangeStatusDao{ + Id: req.Id, + User: user, + IsRead: req.IsRead, + IsBookmarked: req.IsBookmarked, + IsReadLater: req.IsReadLater, + IsIgnored: req.IsIgnored, + }) + ret, err := h.entries.GetEntry(req.Id, user) if err != nil { return err } @@ -13,5 +59,6 @@ func (h *Handler) GetEntries(c echo.Context) error { } func (h *Handler) RegisterEntriesRoutes(e *echo.Echo, r *echo.Group) { - e.GET("/entries", h.GetEntries) + r.GET("/entries", h.GetEntries) + r.PATCH("/entries", h.ChangeUserStatus) } diff --git a/api/entries.go b/api/entries.go index de53965..3afd995 100644 --- a/api/entries.go +++ b/api/entries.go @@ -9,14 +9,18 @@ import ( ) type Entry struct { - Id uuid.UUID `json:"id"` - Title string `json:"title"` - Link string `json:"link"` - Date time.Time `json:"date"` - Content string `json:"content"` - Authors []string `json:"authors"` - FeedId uuid.UUID `json:"feedId"` - Feed Feed `json:"feed,omitempty"` + Id uuid.UUID `json:"id"` + Title string `json:"title"` + Link string `json:"link"` + Date time.Time `json:"date"` + Content string `json:"content"` + Authors []string `json:"authors"` + FeedId uuid.UUID `json:"feedId"` + Feed Feed `json:"feed,omitempty"` + IsRead bool `json:"isRead"` + IsBookmarked bool `json:"isBookmarked"` + IsReadLater bool `json:"isReadLater"` + IsIgnored bool `json:"isIgnored"` } type EntryDao struct { @@ -28,18 +32,37 @@ type EntryDao struct { Authors pq.StringArray FeedId uuid.UUID `db:"feed_id"` Feed FeedDao `db:"feed"` + + EntryId *uuid.UUID `db:"entry_id"` + UserId *uuid.UUID `db:"user_id"` + IsRead *bool `db:"is_read"` + IsBookmarked *bool `db:"is_bookmarked"` + IsReadLater *bool `db:"is_read_later"` + IsIgnored *bool `db:"is_ignored"` +} + +func OrDefault[T any](val *T) T { + if val != nil { + return *val + } + var ret T + return ret } func (e *EntryDao) ToEntry() Entry { return Entry{ - Id: e.Id, - Title: e.Title, - Link: e.Link, - Date: e.Date, - Content: e.Content, - Authors: e.Authors, - FeedId: e.FeedId, - Feed: e.Feed.ToFeed(), + Id: e.Id, + Title: e.Title, + Link: e.Link, + Date: e.Date, + Content: e.Content, + Authors: e.Authors, + FeedId: e.FeedId, + Feed: e.Feed.ToFeed(), + IsRead: OrDefault(e.IsRead), + IsBookmarked: OrDefault(e.IsBookmarked), + IsReadLater: OrDefault(e.IsReadLater), + IsIgnored: OrDefault(e.IsIgnored), } } @@ -60,18 +83,60 @@ func (s EntryService) Add(entries []EntryDao) error { return err } -func (s EntryService) ListEntries() ([]Entry, error) { +func (s EntryService) GetEntry(id uuid.UUID, userId uuid.UUID) (Entry, error) { + var ret EntryDao + err := s.database.Select( + &ret, + `select e.*, s.*, + f.id as "feed.id", f.name as "feed.name", f.link as "feed.link", f.favicon_url as "feed.favicon_url", + f.tags as "feed.tags", f.submitter_id as "feed.submitter_id", f.added_date as "feed.added_date" + from entries as e + left join entries_users as s on s.entry_id = e.id and s.user_id = $1 + left join feeds as f on f.id = e.feed_id + where e.id = $2`, + userId, + id, + ) + if err != nil { + return Entry{}, err + } + return ret.ToEntry(), nil +} + +func (s EntryService) ListEntries(userId uuid.UUID) ([]Entry, error) { ret := []EntryDao{} err := s.database.Select( &ret, - `select e.*, f.id as "feed.id", f.name as "feed.name", f.link as "feed.link", f.favicon_url as "feed.favicon_url", + `select e.*, s.*, + f.id as "feed.id", f.name as "feed.name", f.link as "feed.link", f.favicon_url as "feed.favicon_url", f.tags as "feed.tags", f.submitter_id as "feed.submitter_id", f.added_date as "feed.added_date" from entries as e + left join entries_users as s on s.entry_id = e.id and s.user_id = $1 left join feeds as f on f.id = e.feed_id - order by e.date`, + order by e.date desc`, + userId, ) if err != nil { return nil, err } return Map(ret, func(e EntryDao, _ int) Entry { return e.ToEntry() }), nil } + +type ChangeStatusDao struct { + Id uuid.UUID + User uuid.UUID + IsRead bool `db:"is_read"` + IsBookmarked bool `db:"is_bookmarked"` + IsReadLater bool `db:"is_read_later"` + IsIgnored bool `db:"is_ignored"` +} + +func (s EntryService) ChangeStatus(status ChangeStatusDao) error { + _, err := s.database.NamedExec( + `insert into entries_users (entry_id, user_id, is_read, is_bookmared, is_read_later, is_ignored) + values (:id, :user, :is_read, :is_bookmared, :is_read_later, :is_ignored) + on conflict(entry_id, user_id) do update set is_read = :is_read, is_bookmarked = :is_bookmarked, is_read_later = :is_read_later, is_ignored = :is_ignored`, + status, + ) + return err +} diff --git a/api/feeds.go b/api/feeds.go index 1e2b3cd..85aaa5c 100644 --- a/api/feeds.go +++ b/api/feeds.go @@ -104,6 +104,7 @@ func (s FeedService) AddFeed(feed Feed) (Feed, error) { if err != nil { return Feed{}, err } + return feed, nil } diff --git a/sql/create.sql b/sql/create.sql index 0d4e517..c54b598 100644 --- a/sql/create.sql +++ b/sql/create.sql @@ -30,11 +30,18 @@ create table if not exists entries( create table if not exists entries_users( user_id uuid not null references users(id), - feed_id uuid not null references feeds(id), + entry_id uuid not null references entries(id), is_read bool not null, is_bookmarked bool not null, is_read_later bool not null, is_ignored bool not null, + constraint entries_users_pk primary key (user_id, entry_id) +); + +create table if not exists feeds_users( + user_id uuid not null references users(id), + feed_id uuid not null references feeds(id), + is_ignored bool not null, constraint entries_users_pk primary key (user_id, feed_id) );