Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 28 additions & 15 deletions cmd/web/client/html/partial--settings_feeds.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,36 @@ <h5 class="card-header">Feeds</h5>
<p>Add RSS feeds that you'd like to follow there</p>

{{ if gt (len .Feeds) 0}}
<ul class="list-group">
<ul class="list-group list-group-flush">
{{ range .Feeds }}
<li class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<span>{{ .URL }}</span>
<button type="button"
class="btn btn-sm btn-danger"
data-controller="action"
data-action="action#run"
data-action-action-value="remove_rss_subscription"
data-action-prompt-value="Do you want to unsubcribe from {{ .URL }}?"
data-id="{{ .ID }}"
><i class="bi-trash"></i></button>
<li class="list-group-item px-2 py-1">
<div class="d-flex justify-content-between align-items-center gap-2">
<div class="overflow-hidden flex-grow-1">
<div class="d-flex align-items-baseline gap-1">
<a href="{{ .WebsiteURL }}" target="_blank" rel="noopener noreferrer">{{ with .Title }}{{ . }}{{ else }}{{ .WebsiteURL }}{{ end }}</a>
<a href="{{ .URL }}" target="_blank" rel="noopener noreferrer" class="ms-1 text-muted small" title="RSS Feed"><i class="bi bi-rss"></i></a>
</div>
<div class="d-flex flex-wrap small gap-2">
<span><span class="text-muted">Fetched:</span> {{ with .LastFetchedAt }}<span title="{{ renderTimestamp . $.User.DBUser }}">{{ relativeTime . }}</span>{{ else }}Never{{ end }}</span>
<span><span class="text-muted">Post:</span> {{ with .LastImportedAt }}<span title="{{ renderTimestamp . $.User.DBUser }}">{{ relativeTime . }}</span>{{ else }}None{{ end }}</span>
<span><span class="text-muted">Next:</span> {{ with .NextFetchAt }}<span title="{{ renderTimestamp . $.User.DBUser }}">{{ relativeTime . }}</span>{{ else }}Now{{ end }}</span>
</div>
{{ with .LastError }}
<details class="small">
<summary class="text-danger" role="button">Error</summary>
<div class="text-danger mt-1">{{ . }}</div>
</details>
{{ end }}
</div>
<button type="button"
class="btn btn-sm btn-outline-danger flex-shrink-0"
data-controller="action"
data-action="action#run"
data-action-action-value="remove_rss_subscription"
data-action-prompt-value="Do you want to unsubcribe from {{ .URL }}?"
data-id="{{ .ID }}"
><i class="bi-trash"></i></button>
</div>
{{ with .Title }}<div><small>{{ . }}</small></div>{{ end }}
{{ with .LastError }}<div><small>LastError: {{ . }}</small></div>{{ end }}
<div><small>Next fetch: {{ with .NextFetchAt }}{{ renderTimestamp . $.User.DBUser }}{{ else }}Now{{ end }}</small></div>
</li>
{{ end }}
</ul>
Expand Down
5 changes: 5 additions & 0 deletions cmd/web/client/scss/_dark-mode.scss
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,11 @@
--bs-list-group-color: var(--text-color);
--bs-list-group-action-hover-bg: #353535;
}

// Text utilities
.text-muted {
color: var(--muted-text) !important;
}
}

// Apply dark mode styles manually with data-bs-theme="dark"
Expand Down
5 changes: 5 additions & 0 deletions cmd/web/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import (
"github.com/can3p/pcom/pkg/util/ginhelpers/csp"
"github.com/can3p/pcom/pkg/util/ginhelpers/csrf"
"github.com/can3p/pcom/pkg/web"
"github.com/dustin/go-humanize"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
"github.com/jmoiron/sqlx"
Expand Down Expand Up @@ -897,6 +898,10 @@ func funcmap(staticAsset staticAssetFunc) template.FuncMap {
return t.Format("Mon, 02 Jan 2006 15:04")
},

"relativeTime": func(t time.Time) string {
return humanize.Time(t)
},

"toMap": func(args ...interface{}) map[string]interface{} {
if len(args)%2 != 0 {
panic("toMap got uneven number of arguments")
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
github.com/can3p/anti-disposable-email v0.0.0-20230623054934-598d3044afb0
github.com/can3p/gogo v0.0.0-20240724001046-388a9ef0ec1b
github.com/davidbyttow/govips/v2 v2.15.0
github.com/dustin/go-humanize v1.0.0
github.com/friendsofgo/errors v0.9.2
github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0
Expand All @@ -28,6 +29,7 @@ require (
github.com/mileusna/useragent v1.3.5
github.com/mmcdole/gofeed v1.3.0
github.com/ory/dockertest/v3 v3.12.0
github.com/ovechkin-dm/mockio/v2 v2.0.4
github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v1.8.1
github.com/samber/lo v1.47.0
Expand Down Expand Up @@ -110,8 +112,6 @@ require (
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/opencontainers/runc v1.2.3 // indirect
github.com/ovechkin-dm/go-dyno v0.5.3 // indirect
github.com/ovechkin-dm/mockio v1.0.2 // indirect
github.com/ovechkin-dm/mockio/v2 v2.0.4 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
Expand Down
7 changes: 1 addition & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
Expand Down Expand Up @@ -550,12 +551,8 @@ github.com/opencontainers/runc v1.2.3 h1:fxE7amCzfZflJO2lHXf4y/y8M1BoAqp+FVmG19o
github.com/opencontainers/runc v1.2.3/go.mod h1:nSxcWUydXrsBZVYNSkTjoQ/N6rcyTtn+1SD5D4+kRIM=
github.com/ory/dockertest/v3 v3.12.0 h1:3oV9d0sDzlSQfHtIaB5k6ghUCVMVLpAY8hwrqoCyRCw=
github.com/ory/dockertest/v3 v3.12.0/go.mod h1:aKNDTva3cp8dwOWwb9cWuX84aH5akkxXRvO7KCwWVjE=
github.com/ovechkin-dm/go-dyno v0.3.2 h1:jl0hE+o6M/egVVk1SljGw2GYkIEZFkzFWJ1nbqLyEbw=
github.com/ovechkin-dm/go-dyno v0.3.2/go.mod h1:CcJNuo7AbePMoRNpM3i1jC1Rp9kHEMyWozNdWzR+0ys=
github.com/ovechkin-dm/go-dyno v0.5.3 h1:/MrL26kFTxbLj/qPbEtR4piVeFYUqjSamAgWpuzeD/k=
github.com/ovechkin-dm/go-dyno v0.5.3/go.mod h1:CcJNuo7AbePMoRNpM3i1jC1Rp9kHEMyWozNdWzR+0ys=
github.com/ovechkin-dm/mockio v1.0.2 h1:AR31nVoWhZeMDe9FnfFfayof/y9ed3HASIVhqVKyl8M=
github.com/ovechkin-dm/mockio v1.0.2/go.mod h1:TAmLa+rztm8IKxrc44JPAviGEhRzNeIyF9oiFznMcCo=
github.com/ovechkin-dm/mockio/v2 v2.0.4 h1:miQxn7WOnRLsnqGgOl9Tbgpv0VxiMky0eAEPSm3ejUs=
github.com/ovechkin-dm/mockio/v2 v2.0.4/go.mod h1:NIkz06mKOotiaEiZtLgKOWgOPzgx3+6Pqg+x+6blucM=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
Expand All @@ -565,8 +562,6 @@ github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZO
github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e h1:D0bJD+4O3G4izvrQUmzCL80zazlN7EwJ0PPDhpJWC/I=
github.com/petermattis/goid v0.0.0-20250721140440-ea1c0173183e/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ=
Expand Down
56 changes: 46 additions & 10 deletions pkg/feedops/feedops.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package feedops
import (
"context"
"fmt"
"net/url"
"time"

"github.com/can3p/pcom/pkg/model/core"
Expand All @@ -12,11 +13,14 @@ import (
)

type RssFeed struct {
ID string
URL string
Title string
NextFetchAt *time.Time
LastError string
ID string
URL string
WebsiteURL string
Title string
NextFetchAt *time.Time
LastFetchedAt *time.Time
LastImportedAt *time.Time
LastError string
}

func GetRssFeeds(ctx context.Context, db boil.ContextExecutor, userID string) ([]*RssFeed, error) {
Expand All @@ -30,18 +34,50 @@ func GetRssFeeds(ctx context.Context, db boil.ContextExecutor, userID string) ([
return nil, err
}

feedIDs := lo.Map(rssFeeds, func(feed *core.UserFeedSubscription, idx int) string {
return feed.FeedID
})

lastImportedMap := make(map[string]*time.Time)
if len(feedIDs) > 0 {
latestItems, err := core.RSSItems(
core.RSSItemWhere.FeedID.IN(feedIDs),
qm.Select(core.RSSItemColumns.FeedID, fmt.Sprintf("MAX(%s) as created_at", core.RSSItemColumns.CreatedAt)),
qm.GroupBy(core.RSSItemColumns.FeedID),
).All(ctx, db)

if err != nil {
return nil, err
}

for _, item := range latestItems {
t := item.CreatedAt
lastImportedMap[item.FeedID] = &t
}
}

feeds := lo.Map(rssFeeds, func(feed *core.UserFeedSubscription, idx int) *RssFeed {
return &RssFeed{
ID: feed.ID,
URL: feed.R.Feed.URL,
Title: feed.R.Feed.Title.String,
NextFetchAt: feed.R.Feed.NextFetchAt.Ptr(),
LastError: feed.R.Feed.LastFetchError.String,
ID: feed.ID,
URL: feed.R.Feed.URL,
WebsiteURL: extractWebsiteURL(feed.R.Feed.URL),
Title: feed.R.Feed.Title.String,
NextFetchAt: feed.R.Feed.NextFetchAt.Ptr(),
LastFetchedAt: feed.R.Feed.LastFetchedAt.Ptr(),
LastImportedAt: lastImportedMap[feed.FeedID],
LastError: feed.R.Feed.LastFetchError.String,
}
})

return feeds, nil
}

func extractWebsiteURL(feedURL string) string {
parsed, err := url.Parse(feedURL)
if err != nil {
return feedURL
}
return fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host)
}

type RssFeedItem struct {
Expand Down
Loading