-
{{ .URL }}
-
+
+
+
+
+
+ Fetched: {{ with .LastFetchedAt }}{{ relativeTime . }}{{ else }}Never{{ end }}
+ Post: {{ with .LastImportedAt }}{{ relativeTime . }}{{ else }}None{{ end }}
+ Next: {{ with .NextFetchAt }}{{ relativeTime . }}{{ else }}Now{{ end }}
+
+ {{ with .LastError }}
+
+ Error
+ {{ . }}
+
+ {{ end }}
+
+
- {{ with .Title }}{{ . }}
{{ end }}
- {{ with .LastError }}LastError: {{ . }}
{{ end }}
- Next fetch: {{ with .NextFetchAt }}{{ renderTimestamp . $.User.DBUser }}{{ else }}Now{{ end }}
{{ end }}
diff --git a/cmd/web/client/scss/_dark-mode.scss b/cmd/web/client/scss/_dark-mode.scss
index 8babf80..94a932f 100644
--- a/cmd/web/client/scss/_dark-mode.scss
+++ b/cmd/web/client/scss/_dark-mode.scss
@@ -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"
diff --git a/cmd/web/main.go b/cmd/web/main.go
index de8718b..f8eb4e1 100644
--- a/cmd/web/main.go
+++ b/cmd/web/main.go
@@ -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"
@@ -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")
diff --git a/go.mod b/go.mod
index ff6ed43..58bd88d 100644
--- a/go.mod
+++ b/go.mod
@@ -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
@@ -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
@@ -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
diff --git a/go.sum b/go.sum
index c7139d2..a2217f9 100644
--- a/go.sum
+++ b/go.sum
@@ -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=
@@ -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=
@@ -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=
diff --git a/pkg/feedops/feedops.go b/pkg/feedops/feedops.go
index 8cda886..696cf64 100644
--- a/pkg/feedops/feedops.go
+++ b/pkg/feedops/feedops.go
@@ -3,6 +3,7 @@ package feedops
import (
"context"
"fmt"
+ "net/url"
"time"
"github.com/can3p/pcom/pkg/model/core"
@@ -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) {
@@ -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 {