aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--go.mod17
-rw-r--r--go.sum41
-rw-r--r--walrss/internal/core/feeds.go55
-rw-r--r--walrss/internal/core/users.go85
-rw-r--r--walrss/internal/db/00000000000001_initialise.go38
-rw-r--r--walrss/internal/db/db.go51
-rw-r--r--walrss/internal/db/migrations.go40
-rw-r--r--walrss/internal/http/edit.go20
-rw-r--r--walrss/internal/http/mainpage.go6
-rw-r--r--walrss/internal/rss/processor.go4
-rw-r--r--walrss/internal/state/state.go4
-rw-r--r--walrss/main.go10
12 files changed, 246 insertions, 125 deletions
diff --git a/go.mod b/go.mod
index d2fb84f..f73434b 100644
--- a/go.mod
+++ b/go.mod
@@ -10,11 +10,15 @@ require (
github.com/kkyr/fig v0.3.0
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/matcornic/hermes v1.2.0
+ github.com/mattn/go-sqlite3 v1.14.14
github.com/mmcdole/gofeed v1.1.3
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/rs/zerolog v1.26.1
github.com/stevelacy/daz v0.1.4
- github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a
+ github.com/uptrace/bun v1.1.7
+ github.com/uptrace/bun/dialect/sqlitedialect v1.1.7
+ github.com/uptrace/bun/extra/bundebug v1.1.7
+ github.com/valyala/fasthttp v1.34.0
github.com/valyala/quicktemplate v1.7.0
golang.org/x/crypto v0.0.0-20220214200702-86341886e292
)
@@ -26,12 +30,16 @@ require (
github.com/PuerkitoBio/goquery v1.5.1 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/andybalholm/cascadia v1.1.0 // indirect
+ github.com/fatih/color v1.13.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba // indirect
+ github.com/jinzhu/inflection v1.0.0 // indirect
github.com/json-iterator/go v1.1.10 // indirect
github.com/klauspost/compress v1.15.0 // indirect
+ github.com/mattn/go-colorable v0.1.12 // indirect
+ github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.9 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.4.1 // indirect
@@ -43,12 +51,13 @@ require (
github.com/pelletier/go-toml v1.9.3 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
+ github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
- github.com/valyala/fasthttp v1.34.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
- go.etcd.io/bbolt v1.3.6 // indirect
+ github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
+ github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
- golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
+ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/russross/blackfriday.v2 v2.0.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index 8c88be3..4fa2365 100644
--- a/go.sum
+++ b/go.sum
@@ -22,6 +22,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
+github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.31.0 h1:M2rWPQbD5fDVAjcoOLjKRXTIlHesI5Eq7I5FEQPt4Ow=
github.com/gofiber/fiber/v2 v2.31.0/go.mod h1:1Ega6O199a3Y7yDGuM9FyXDPYQfv+7/y48wl6WCwUF4=
@@ -35,6 +37,8 @@ github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba h1:QFQpJdgbON7I0jr2hYW7Bs+XV0qjc3d5tZoDnRFnqTg=
github.com/jaytaylor/html2text v0.0.0-20211105163654-bc68cce691ba/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
+github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
@@ -49,8 +53,16 @@ github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/matcornic/hermes v1.2.0 h1:AuqZpYcTOtTB7cahdevLfnhIpfzmpqw5Czv8vpdnFDU=
github.com/matcornic/hermes v1.2.0/go.mod h1:lujJomb016Xjv8wBnWlNvUdtmvowjjfkqri5J/+1hYc=
+github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw=
+github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag=
@@ -85,10 +97,17 @@ github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02n
github.com/stevelacy/daz v0.1.4 h1:ugmff/D7D764wZjXSgSryEINE/bi+Xddllw3JQQGbWk=
github.com/stevelacy/daz v0.1.4/go.mod h1:AbK6DzjiIL15r4bQtcFvOBAvDGMXoh+uIG26NRUugt0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a h1:oIi7H/bwFUYKYhzKbHc+3MvHRWqhQwXVB4LweLMiVy0=
-github.com/timshannon/bolthold v0.0.0-20210913165410-232392fc8a6a/go.mod h1:iSvujNDmpZ6eQX+bg/0X3lF7LEmZ8N77g2a/J/+Zt2U=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
+github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
+github.com/uptrace/bun v1.1.7 h1:biOoh5dov69hQPBlaRsXSHoEOIEnCxFzQvUmbscSNJI=
+github.com/uptrace/bun v1.1.7/go.mod h1:Z2Pd3cRvNKbrYuL6Gp1XGjA9QEYz+rDz5KkEi9MZLnQ=
+github.com/uptrace/bun/dialect/sqlitedialect v1.1.7 h1:xxc1n1nUdn6zqY6ji1ZkiaHQyop8J237uRyptqXGW08=
+github.com/uptrace/bun/dialect/sqlitedialect v1.1.7/go.mod h1:GjqiPWAa9JCLlv51mB1rjk8QRgwv6HlQ+IAtyrobfAY=
+github.com/uptrace/bun/extra/bundebug v1.1.7 h1:YbW7i9pUfPJMzclSzdHslIvAAR0WO9dW34ctL1Xh+UM=
+github.com/uptrace/bun/extra/bundebug v1.1.7/go.mod h1:WoBnTrBG9CXITZUw+UfF+DYjWi71boo8FKZGuS5qDzA=
github.com/urfave/cli v1.22.3/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
@@ -99,9 +118,11 @@ github.com/valyala/quicktemplate v1.7.0 h1:LUPTJmlVcb46OOUY3IeD9DojFpAVbsG+5WFTc
github.com/valyala/quicktemplate v1.7.0/go.mod h1:sqKJnoaOF88V07vkO+9FL8fb9uZg/VPSJnLYn+LmLk8=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
+github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
+github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
+github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
-go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
@@ -124,15 +145,19 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
+golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -155,3 +180,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/walrss/internal/core/feeds.go b/walrss/internal/core/feeds.go
index 9ba6545..600977d 100644
--- a/walrss/internal/core/feeds.go
+++ b/walrss/internal/core/feeds.go
@@ -1,12 +1,13 @@
package core
import (
+ "context"
+ "database/sql"
"errors"
"github.com/codemicro/walrss/walrss/internal/core/opml"
"github.com/codemicro/walrss/walrss/internal/db"
"github.com/codemicro/walrss/walrss/internal/state"
"github.com/lithammer/shortuuid/v4"
- bh "github.com/timshannon/bolthold"
)
func NewFeed(st *state.State, userID, name, url string) (*db.Feed, error) {
@@ -25,37 +26,34 @@ func NewFeed(st *state.State, userID, name, url string) (*db.Feed, error) {
UserID: userID,
}
- if err := st.Data.Insert(feed.ID, feed); err != nil {
+ if _, err := st.Data.NewInsert().Model(feed).Exec(context.Background()); err != nil {
return nil, err
}
return feed, nil
}
-func GetFeedsForUser(st *state.State, userID string) ([]*db.Feed, error) {
- var feeds []*db.Feed
- if err := st.Data.Find(&feeds, bh.Where("UserID").Eq(userID)); err != nil {
- return nil, err
- }
- return feeds, nil
+func GetFeedsForUser(st *state.State, userID string) (res []*db.Feed, err error) {
+ err = st.Data.NewSelect().
+ Model(&res).
+ Relation("User").
+ Where("Feed.user_id = ?", userID).
+ Scan(context.Background())
+ return
}
-func GetFeed(st *state.State, id string) (*db.Feed, error) {
- feed := new(db.Feed)
- if err := st.Data.FindOne(feed, bh.Where("ID").Eq(id)); err != nil {
- if errors.Is(err, bh.ErrNotFound) {
- return nil, ErrNotFound
- }
- return nil, err
+func GetFeed(st *state.State, id string) (res *db.Feed, err error) {
+ res = new(db.Feed)
+ err = st.Data.NewSelect().Model(res).Where("id = ?", id).Scan(context.Background())
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, ErrNotFound
}
- return feed, nil
+ return
}
func DeleteFeed(st *state.State, id string) error {
- if err := st.Data.Delete(id, new(db.Feed)); err != nil {
- return err
- }
- return nil
+ _, err := st.Data.NewSelect().Model((*db.Feed)(nil)).Where("id = ?", id).Exec(context.Background())
+ return err
}
func UpdateFeed(st *state.State, feed *db.Feed) error {
@@ -67,13 +65,11 @@ func UpdateFeed(st *state.State, feed *db.Feed) error {
return err
}
- if err := st.Data.Update(feed.ID, feed); err != nil {
- if errors.Is(err, bh.ErrNotFound) {
- return ErrNotFound
- }
- return err
+ _, err := st.Data.NewUpdate().Model(feed).WherePK().Exec(context.Background())
+ if errors.Is(err, sql.ErrNoRows) {
+ return ErrNotFound
}
- return nil
+ return err
}
func ExportFeedsForUser(st *state.State, userID string) ([]byte, error) {
@@ -82,9 +78,14 @@ func ExportFeedsForUser(st *state.State, userID string) ([]byte, error) {
if err != nil {
return nil, err
}
- if err := st.Data.Find(&feeds, bh.Where("UserID").Eq(userID)); err != nil {
+
+ if err := st.Data.NewSelect().
+ Model(&feeds).
+ Where("user_id = ?", userID).
+ Scan(context.Background()); err != nil {
return nil, err
}
+
return opml.FromFeeds(feeds, user.Email).ToBytes()
}
diff --git a/walrss/internal/core/users.go b/walrss/internal/core/users.go
index 5071543..5c17251 100644
--- a/walrss/internal/core/users.go
+++ b/walrss/internal/core/users.go
@@ -1,11 +1,13 @@
package core
import (
+ "context"
+ "database/sql"
"errors"
"github.com/codemicro/walrss/walrss/internal/db"
"github.com/codemicro/walrss/walrss/internal/state"
"github.com/lithammer/shortuuid/v4"
- bh "github.com/timshannon/bolthold"
+ "github.com/mattn/go-sqlite3"
"golang.org/x/crypto/bcrypt"
)
@@ -31,9 +33,11 @@ func RegisterUser(st *state.State, email, password string) (*db.User, error) {
u.Password = hash
- if err := st.Data.Insert(u.ID, u); err != nil {
- if errors.Is(err, bh.ErrUniqueExists) {
- return nil, NewUserError("email address in use")
+ if _, err := st.Data.NewInsert().Model(u).Exec(context.Background()); err != nil {
+ if e, ok := err.(*sqlite3.Error); ok {
+ if e.Code == sqlite3.ErrConstraint {
+ return nil, NewUserError("email address in use")
+ }
}
return nil, err
}
@@ -57,59 +61,42 @@ func AreUserCredentialsCorrect(st *state.State, email, password string) (bool, e
return true, nil
}
-func GetUserByID(st *state.State, userID string) (*db.User, error) {
- user := new(db.User)
- if err := st.Data.FindOne(user, bh.Where("ID").Eq(userID)); err != nil {
- if errors.Is(err, bh.ErrNotFound) {
- return nil, ErrNotFound
- }
- return nil, err
+func GetUserByID(st *state.State, userID string) (res *db.User, err error) {
+ res = new(db.User)
+ err = st.Data.NewSelect().Model(res).Where("id = ?", userID).Scan(context.Background())
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, ErrNotFound
}
- return user, nil
+ return
}
-func GetUserByEmail(st *state.State, email string) (*db.User, error) {
- user := new(db.User)
- if err := st.Data.FindOne(user, bh.Where("Email").Eq(email)); err != nil {
- if errors.Is(err, bh.ErrNotFound) {
- return nil, ErrNotFound
- }
- return nil, err
+func GetUserByEmail(st *state.State, email string) (res *db.User, err error) {
+ res = new(db.User)
+ err = st.Data.NewSelect().Model(res).Where("email = ?", email).Scan(context.Background())
+ if errors.Is(err, sql.ErrNoRows) {
+ return nil, ErrNotFound
}
- return user, nil
+ return
}
func UpdateUser(st *state.State, user *db.User) error {
- if err := st.Data.Update(user.ID, user); err != nil {
- if errors.Is(err, bh.ErrNotFound) {
- return ErrNotFound
- }
- return err
+ _, err := st.Data.NewUpdate().Model(user).WherePK().Exec(context.Background())
+ if errors.Is(err, sql.ErrNoRows) {
+ return ErrNotFound
}
- return nil
+ return err
}
-func GetUsersBySchedule(st *state.State, day db.SendDay, hour int) ([]*db.User, error) {
- // When trying to Or some queries, BH was weird, so it's easier to make two queries and combine them.
- // This ensures that indexes are used.
-
- var users []*db.User
- if err := st.Data.Find(&users,
- bh.Where("Schedule.Active").Eq(true).
- And("Schedule.Day").Eq(day).
- And("Schedule.Hour").Eq(hour),
- ); err != nil {
- return nil, err
- }
-
- var users2 []*db.User
- if err := st.Data.Find(&users2,
- bh.Where("Schedule.Active").Eq(true).
- And("Schedule.Day").Eq(db.SendDaily).
- And("Schedule.Hour").Eq(hour),
- ); err != nil {
- return nil, err
- }
-
- return append(users, users2...), nil
+func GetUsersBySchedule(st *state.State, day db.SendDay, hour int) (res []*db.User, err error) {
+ err = st.Data.NewSelect().
+ Model(&res).
+ Where(
+ "active = ? and (schedule_day = ? or schedule_day = ?) and schedule_hour = ?",
+ true,
+ day,
+ db.SendDaily,
+ hour,
+ ).
+ Scan(context.Background())
+ return
}
diff --git a/walrss/internal/db/00000000000001_initialise.go b/walrss/internal/db/00000000000001_initialise.go
new file mode 100644
index 0000000..aaa37d1
--- /dev/null
+++ b/walrss/internal/db/00000000000001_initialise.go
@@ -0,0 +1,38 @@
+package db
+
+import (
+ "context"
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ tps := []any{
+ (*User)(nil),
+ (*Feed)(nil),
+ }
+
+ migs.MustRegister(
+ func(ctx context.Context, db *bun.DB) error {
+ migLogger.Debug().Msg("1 up")
+
+ for _, t := range tps {
+ if _, err := db.NewCreateTable().Model(t).Exec(ctx); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ },
+ func(ctx context.Context, db *bun.DB) error {
+ migLogger.Debug().Msg("1 down")
+
+ for _, t := range tps {
+ if _, err := db.NewDropTable().Model(t).Exec(ctx); err != nil {
+ return err
+ }
+ }
+
+ return nil
+ },
+ )
+}
diff --git a/walrss/internal/db/db.go b/walrss/internal/db/db.go
index 989038c..4da74e0 100644
--- a/walrss/internal/db/db.go
+++ b/walrss/internal/db/db.go
@@ -1,40 +1,49 @@
package db
import (
- "encoding/json"
- bh "github.com/timshannon/bolthold"
+ "database/sql"
+ _ "github.com/mattn/go-sqlite3"
+ "github.com/rs/zerolog/log"
+ "github.com/uptrace/bun"
+ "github.com/uptrace/bun/dialect/sqlitedialect"
"strings"
)
-func New(filename string) (*bh.Store, error) {
- store, err := bh.Open(filename, 0644, &bh.Options{
- Encoder: json.Marshal,
- Decoder: json.Unmarshal,
- })
+func New(filename string) (*bun.DB, error) {
+ dsn := filename
+ log.Info().Msg("connecting to database")
+ db, err := sql.Open("sqlite3", dsn)
if err != nil {
return nil, err
}
- return store, nil
+
+ db.SetMaxOpenConns(1) // https://github.com/mattn/go-sqlite3/issues/274#issuecomment-191597862
+
+ return bun.NewDB(db, sqlitedialect.New()), nil
}
type User struct {
- ID string `boldholdKey:""`
- Email string `boltholdUnique:"UniqueEmail"`
- Password []byte
- Salt []byte
+ bun.BaseModel `bun:"table:users"`
- Schedule struct {
- Active bool `boltholdIndex:"Active"`
- Day SendDay `boltholdIndex:"Day"`
- Hour int `boltholdIndex:"Hour"`
- }
+ ID string `bun:"id,pk"`
+ Email string `bun:"email,notnull,unique"`
+ Password []byte `bun:"password"`
+ Salt []byte `bun:"salt"`
+
+ Active bool `bun:"active,notnull"`
+ ScheduleDay SendDay `bun:"schedule_day"`
+ ScheduleHour int `bun:"schedule_hour"`
}
type Feed struct {
- ID string `boltholdKey:""`
- URL string
- Name string
- UserID string `boldholdIndex:"UserID"`
+ bun.BaseModel `bun:"table:feeds"`
+
+ ID string `bun:"id,pk"`
+ URL string `bun:"url,notnull"`
+ Name string `bun:"name,notnull"`
+ UserID string `bun:"user_id,notnull"`
+
+ User *User `bun:",rel:belongs-to,join:user_id=id"`
}
type FeedSlice []*Feed
diff --git a/walrss/internal/db/migrations.go b/walrss/internal/db/migrations.go
new file mode 100644
index 0000000..d4773fc
--- /dev/null
+++ b/walrss/internal/db/migrations.go
@@ -0,0 +1,40 @@
+package db
+
+import (
+ "context"
+ "github.com/rs/zerolog/log"
+ "github.com/uptrace/bun"
+ "github.com/uptrace/bun/migrate"
+ "time"
+)
+
+var (
+ migs = migrate.NewMigrations()
+ migLogger = log.Logger.With().Str("location", "migrations").Logger()
+)
+
+func DoMigrations(db *bun.DB) error {
+ log.Info().Msg("running migrations")
+
+ mig := migrate.NewMigrator(db, migs)
+
+ ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
+ defer cancel()
+
+ if err := mig.Init(ctx); err != nil {
+ return err
+ }
+
+ group, err := mig.Migrate(ctx)
+ if err != nil {
+ return err
+ }
+
+ if group.IsZero() {
+ log.Info().Msg("database up to date")
+ } else {
+ log.Info().Msg("migrations applied")
+ }
+
+ return nil
+}
diff --git a/walrss/internal/http/edit.go b/walrss/internal/http/edit.go
index 2cfc545..657989a 100644
--- a/walrss/internal/http/edit.go
+++ b/walrss/internal/http/edit.go
@@ -22,9 +22,9 @@ func (s *Server) editEnabledState(ctx *fiber.Ctx) error {
}
if strings.ToLower(ctx.FormValue("enable", "off")) == "on" {
- user.Schedule.Active = true
+ user.Active = true
} else {
- user.Schedule.Active = false
+ user.Active = false
}
if err := core.UpdateUser(s.state, user); err != nil {
@@ -33,9 +33,9 @@ func (s *Server) editEnabledState(ctx *fiber.Ctx) error {
fragmentEmitSuccess(ctx)
return ctx.SendString((&views.MainPage{
- EnableDigests: user.Schedule.Active,
- SelectedDay: user.Schedule.Day,
- SelectedTime: user.Schedule.Hour,
+ EnableDigests: user.Active,
+ SelectedDay: user.ScheduleDay,
+ SelectedTime: user.ScheduleHour,
}).RenderScheduleCard())
}
@@ -57,7 +57,7 @@ func (s *Server) editTimings(ctx *fiber.Ctx) error {
if x > db.SendOnSunday || x < 0 {
return core.NewUserError("invalid day: out of range 0<=x<=%d", int(db.SendOnSunday))
}
- user.Schedule.Day = x
+ user.ScheduleDay = x
}
if n, err := strconv.ParseInt(ctx.FormValue("time"), 10, 8); err != nil {
@@ -67,7 +67,7 @@ func (s *Server) editTimings(ctx *fiber.Ctx) error {
if x > 23 || x < 0 {
return core.NewUserError("invalid time: out of range 0<=x<=23")
}
- user.Schedule.Hour = x
+ user.ScheduleHour = x
}
if err := core.UpdateUser(s.state, user); err != nil {
@@ -76,9 +76,9 @@ func (s *Server) editTimings(ctx *fiber.Ctx) error {
fragmentEmitSuccess(ctx)
return ctx.SendString((&views.MainPage{
- EnableDigests: user.Schedule.Active,
- SelectedDay: user.Schedule.Day,
- SelectedTime: user.Schedule.Hour,
+ EnableDigests: user.Active,
+ SelectedDay: user.ScheduleDay,
+ SelectedTime: user.ScheduleHour,
}).RenderScheduleCard())
}
diff --git a/walrss/internal/http/mainpage.go b/walrss/internal/http/mainpage.go
index 1d94d2a..99b26b8 100644
--- a/walrss/internal/http/mainpage.go
+++ b/walrss/internal/http/mainpage.go
@@ -23,9 +23,9 @@ func (s *Server) mainPage(ctx *fiber.Ctx) error {
}
return views.SendPage(ctx, &views.MainPage{
- EnableDigests: user.Schedule.Active,
- SelectedDay: user.Schedule.Day,
- SelectedTime: user.Schedule.Hour,
+ EnableDigests: user.Active,
+ SelectedDay: user.ScheduleDay,
+ SelectedTime: user.ScheduleHour,
Feeds: feeds,
})
}
diff --git a/walrss/internal/rss/processor.go b/walrss/internal/rss/processor.go
index 6fcca56..8e34466 100644
--- a/walrss/internal/rss/processor.go
+++ b/walrss/internal/rss/processor.go
@@ -80,7 +80,7 @@ func ProcessUserFeed(st *state.State, user *db.User, progressChannel chan string
}
var interval time.Duration
- if user.Schedule.Day == db.SendDaily || user.Schedule.Day == db.SendDayNever {
+ if user.ScheduleDay == db.SendDaily || user.ScheduleDay == db.SendDayNever {
interval = time.Hour * 24
} else {
interval = time.Hour * 24 * 7
@@ -189,7 +189,7 @@ func filterFeedContent(feed *gofeed.Feed, earliestPublishTime time.Time) []*feed
}
*item.PublishedParsed = item.PublishedParsed.UTC()
-
+
if item.PublishedParsed.After(earliestPublishTime) || item.PublishedParsed.Equal(earliestPublishTime) {
o = append(o, &feedItem{
Title: strings.TrimSpace(item.Title),
diff --git a/walrss/internal/state/state.go b/walrss/internal/state/state.go
index 76fad36..a65a261 100644
--- a/walrss/internal/state/state.go
+++ b/walrss/internal/state/state.go
@@ -6,14 +6,14 @@ import (
"github.com/kkyr/fig"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
- bh "github.com/timshannon/bolthold"
+ "github.com/uptrace/bun"
"os"
"strings"
)
type State struct {
Config *Config
- Data *bh.Store
+ Data *bun.DB
}
func New() *State {
diff --git a/walrss/main.go b/walrss/main.go
index a61ca51..6d2d135 100644
--- a/walrss/main.go
+++ b/walrss/main.go
@@ -6,6 +6,7 @@ import (
"github.com/codemicro/walrss/walrss/internal/rss"
"github.com/codemicro/walrss/walrss/internal/state"
"github.com/rs/zerolog/log"
+ "github.com/uptrace/bun/extra/bundebug"
"os"
)
@@ -28,6 +29,15 @@ func run() error {
if err != nil {
return err
}
+
+ store.AddQueryHook(bundebug.NewQueryHook(
+ bundebug.WithEnabled(st.Config.Debug),
+ ))
+
+ if err := db.DoMigrations(store); err != nil {
+ return err
+ }
+
st.Data = store
server, err := http.New(st)