From 1b69a89c59263d8647e652cb08fe0de13770a508 Mon Sep 17 00:00:00 2001 From: Anish-Parkhi Date: Wed, 24 Apr 2024 19:16:11 +0530 Subject: [PATCH 01/24] fix: removed unnecessary strict post number checking causing error --- routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes.go b/routes.go index 2e4e8c2..f3b454a 100644 --- a/routes.go +++ b/routes.go @@ -159,7 +159,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { // Handle posts write.HandleFunc("/api/posts", handler.All(newPost)).Methods("POST") posts := write.PathPrefix("/api/posts/").Subrouter() - posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.AllReader(fetchPost)).Methods("GET") + posts.HandleFunc("/{post:[a-zA-Z0-9]}", handler.AllReader(fetchPost)).Methods("GET") posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST", "PUT") posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(deletePost)).Methods("DELETE") posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET") From 680c0f55646609198871954970d01e0ce394c84c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:25:11 +0000 Subject: [PATCH 02/24] Bump github.com/go-sql-driver/mysql from 1.7.1 to 1.8.1 Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.7.1 to 1.8.1. - [Release notes](https://github.com/go-sql-driver/mysql/releases) - [Changelog](https://github.com/go-sql-driver/mysql/blob/master/CHANGELOG.md) - [Commits](https://github.com/go-sql-driver/mysql/compare/v1.7.1...v1.8.1) --- updated-dependencies: - dependency-name: github.com/go-sql-driver/mysql dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 3 ++- go.sum | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 666bd89..0934dc5 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect github.com/fatih/color v1.16.0 github.com/go-ini/ini v1.67.0 - github.com/go-sql-driver/mysql v1.7.1 + github.com/go-sql-driver/mysql v1.8.1 github.com/go-test/deep v1.0.1 // indirect github.com/gobuffalo/envy v1.9.0 // indirect github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect @@ -55,6 +55,7 @@ require ( require ( code.as/core/socks v1.0.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect github.com/andybalholm/cascadia v1.3.2 // indirect github.com/beevik/etree v1.1.0 // indirect github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1 // indirect diff --git a/go.sum b/go.sum index a09893d..49e0043 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs= code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= @@ -47,8 +49,8 @@ github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4L github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= From b9f50883a96bea40ffb1b27fa4a9155b4f4b6079 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:25:17 +0000 Subject: [PATCH 03/24] Bump github.com/fatih/color from 1.16.0 to 1.17.0 Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.16.0 to 1.17.0. - [Release notes](https://github.com/fatih/color/releases) - [Commits](https://github.com/fatih/color/compare/v1.16.0...v1.17.0) --- updated-dependencies: - dependency-name: github.com/fatih/color dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 666bd89..8666ba4 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect - github.com/fatih/color v1.16.0 + github.com/fatih/color v1.17.0 github.com/go-ini/ini v1.67.0 github.com/go-sql-driver/mysql v1.7.1 github.com/go-test/deep v1.0.1 // indirect diff --git a/go.sum b/go.sum index a09893d..9008304 100644 --- a/go.sum +++ b/go.sum @@ -35,8 +35,8 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk= github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= +github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= From 2a668d18d342473e94833f4fd8d143d32c4510ad Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 2 Sep 2024 12:43:10 +1000 Subject: [PATCH 04/24] Update config/setup.go for docker environment --- config/setup.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/config/setup.go b/config/setup.go index b00392d..4beef13 100644 --- a/config/setup.go +++ b/config/setup.go @@ -12,12 +12,14 @@ package config import ( "fmt" + "os" + "strconv" + "strings" + "github.com/fatih/color" "github.com/manifoldco/promptui" "github.com/mitchellh/go-wordwrap" "github.com/writeas/web-core/auth" - "strconv" - "strings" ) type SetupData struct { @@ -80,6 +82,8 @@ func Configure(fname string, configSections string) (*SetupData, error) { isDevEnv := envType == "Development" isStandalone := envType == "Production, standalone" + _, isDocker := os.LookupEnv("WRITEFREELY_DOCKER") + data.Config.Server.Dev = isDevEnv if isDevEnv || !isStandalone { @@ -150,6 +154,16 @@ func Configure(fname string, configSections string) (*SetupData, error) { data.Config.Server.TLSKeyPath = "" } + // If running in docker: + // 1. always bind to 0.0.0.0 instead of localhost + // 2. set paths of static files in UNIX manners + if !isDevEnv && isDocker { + data.Config.Server.TemplatesParentDir = "/usr/share/writefreely" + data.Config.Server.StaticParentDir = "/usr/share/writefreely" + data.Config.Server.PagesParentDir = "/usr/share/writefreely" + data.Config.Server.Bind = "0.0.0.0" + } + fmt.Println() } From 52d6ea60f33a70027d9adc45ee0146393f63b480 Mon Sep 17 00:00:00 2001 From: Leo Date: Mon, 2 Sep 2024 12:43:42 +1000 Subject: [PATCH 05/24] Create Dockerfile & sample docker-compose.yml for production build --- Dockerfile.prod | 34 ++++++++++++++++++++++++++++++++++ docker-compose.prod.yml | 25 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 Dockerfile.prod create mode 100644 docker-compose.prod.yml diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..18cde43 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,34 @@ +FROM golang:alpine AS build + +LABEL org.opencontainers.image.source="https://github.com/writefreely/writefreely" +LABEL org.opencontainers.image.description="WriteFreely is a clean, minimalist publishing platform made for writers. Start a blog, share knowledge within your organization, or build a community around the shared act of writing." + +RUN apk update --no-cache && \ + apk upgrade --no-cache && \ + apk add --no-cache nodejs npm make g++ git sqlite-dev patch && \ + npm install -g less less-plugin-clean-css && \ + mkdir -p /go/src/github.com/writefreely/writefreely + +COPY . /go/src/github.com/writefreely/writefreely +WORKDIR /go/src/github.com/writefreely/writefreely +ENV NODE_OPTIONS=--openssl-legacy-provider +RUN cat ossl_legacy.cnf >> /etc/ssl/openssl.cnf && \ + make build && \ + make ui + +FROM alpine + +RUN apk update --no-cache && \ + apk upgrade --no-cache && \ + apk add --no-cache openssl ca-certificates && \ + mkdir /usr/share/writefreely + +COPY --from=build /go/src/github.com/writefreely/writefreely/cmd/writefreely/writefreely /usr/bin +COPY --from=build /go/src/github.com/writefreely/writefreely/pages /usr/share/writefreely/pages +COPY --from=build /go/src/github.com/writefreely/writefreely/static /usr/share/writefreely/static +COPY --from=build /go/src/github.com/writefreely/writefreely/templates /usr/share/writefreely/templates + +ENV WRITEFREELY_DOCKER=True +ENV HOME=/data +WORKDIR /data +CMD ["/usr/bin/writefreely"] diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..ef85671 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,25 @@ +services: + app: + image: writefreely + container_name: writefreely + volumes: + - ./data:/data + ports: + - 127.0.0.1:8080:8080 + depends_on: + - db + restart: unless-stopped + + db: + image: lscr.io/linuxserver/mariadb + container_name: writefreely-mariadb + volumes: + - ./db:/config + environment: + - PUID=65534 + - PGID=65534 + - TZ=Etc/UTC + - MYSQL_DATABASE=writefreely + - MYSQL_USER=writefreely + - MYSQL_PASSWORD=P@ssw0rd + restart: unless-stopped From 4753eef5502343da372ceac11fe8e189b2276c01 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 27 Sep 2024 19:20:43 -0400 Subject: [PATCH 06/24] Switch to gosimple/slug from writeas/slug The upstream library now has the changes we'd merged in this fork, so no need to use our (very outdated) fork anymore. --- go.mod | 4 +++- go.sum | 7 +++++++ oauth_slack.go | 2 +- posts.go | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 666bd89..8e64757 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/gorilla/mux v1.8.1 github.com/gorilla/schema v1.4.1 github.com/gorilla/sessions v1.3.0 + github.com/gosimple/slug v1.14.0 github.com/guregu/null v4.0.0+incompatible github.com/hashicorp/go-multierror v1.1.1 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 @@ -45,7 +46,6 @@ require ( github.com/writeas/import v0.2.1 github.com/writeas/monday v1.3.0 github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 - github.com/writeas/slug v1.2.0 github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431 github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b github.com/writefreely/go-nodeinfo v1.2.0 @@ -68,6 +68,7 @@ require ( github.com/gologme/log v1.2.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/securecookie v1.1.2 // indirect + github.com/gosimple/unidecode v1.0.1 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/joho/godotenv v1.3.0 // indirect github.com/jtolds/gls v4.2.1+incompatible // indirect @@ -82,6 +83,7 @@ require ( github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/writeas/go-writeas/v2 v2.0.2 // indirect github.com/writeas/openssl-go v1.0.0 // indirect + github.com/writeas/slug v1.2.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect diff --git a/go.sum b/go.sum index a09893d..88ae68e 100644 --- a/go.sum +++ b/go.sum @@ -72,6 +72,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= @@ -89,6 +90,10 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/gorilla/sessions v1.3.0 h1:XYlkq7KcpOB2ZhHBPv5WpjMIxrQosiZanfoy1HLZFzg= github.com/gorilla/sessions v1.3.0/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/gosimple/slug v1.14.0 h1:RtTL/71mJNDfpUbCOmnf/XFkzKRtD6wL6Uy+3akm4Es= +github.com/gosimple/slug v1.14.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw= github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= @@ -106,9 +111,11 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec h1:ZXWuspqypleMuJy4bzYEqlMhJnGAYpLrWe5p7W3CdvI= github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1:voECJzdraJmolzPBgL9Z7ANwXf4oMXaTCsIkdiPpR/g= github.com/mailgun/mailgun-go v2.0.0+incompatible h1:0FoRHWwMUctnd8KIR3vtZbqdfjpIMxOZgcSa51s8F8o= diff --git a/oauth_slack.go b/oauth_slack.go index a2752db..40f50e4 100644 --- a/oauth_slack.go +++ b/oauth_slack.go @@ -13,7 +13,7 @@ package writefreely import ( "context" "errors" - "github.com/writeas/slug" + "github.com/gosimple/slug" "net/http" "net/url" "strings" diff --git a/posts.go b/posts.go index 46f5871..889e947 100644 --- a/posts.go +++ b/posts.go @@ -23,6 +23,7 @@ import ( "time" "github.com/gorilla/mux" + "github.com/gosimple/slug" "github.com/guregu/null" "github.com/guregu/null/zero" "github.com/kylemcc/twitter-text-go/extract" @@ -30,7 +31,6 @@ import ( stripmd "github.com/writeas/go-strip-markdown/v2" "github.com/writeas/impart" "github.com/writeas/monday" - "github.com/writeas/slug" "github.com/writeas/web-core/activitystreams" "github.com/writeas/web-core/bots" "github.com/writeas/web-core/converter" From 096430f1cdaa608f5f10c87cfa84ecdc5f2b1a29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Oct 2024 18:40:49 +0000 Subject: [PATCH 07/24] Bump golang.org/x/crypto from 0.24.0 to 0.28.0 Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.24.0 to 0.28.0. - [Commits](https://github.com/golang/crypto/compare/v0.24.0...v0.28.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 15 +++++++++------ 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index cbfa0d2..78b8240 100644 --- a/go.mod +++ b/go.mod @@ -49,7 +49,7 @@ require ( github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431 github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b github.com/writefreely/go-nodeinfo v1.2.0 - golang.org/x/crypto v0.24.0 + golang.org/x/crypto v0.28.0 golang.org/x/net v0.26.0 ) @@ -84,8 +84,8 @@ require ( github.com/writeas/go-writeas/v2 v2.0.2 // indirect github.com/writeas/openssl-go v1.0.0 // indirect github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect gopkg.in/ini.v1 v1.62.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index fcb69cf..2966afc 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,7 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI= @@ -108,9 +109,11 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec h1:ZXWuspqypleMuJy4bzYEqlMhJnGAYpLrWe5p7W3CdvI= github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1:voECJzdraJmolzPBgL9Z7ANwXf4oMXaTCsIkdiPpR/g= github.com/mailgun/mailgun-go v2.0.0+incompatible h1:0FoRHWwMUctnd8KIR3vtZbqdfjpIMxOZgcSa51s8F8o= @@ -215,8 +218,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -263,8 +266,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -279,8 +282,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= From 4f3bacb182ef1c5ed520e813e55cf769334741ab Mon Sep 17 00:00:00 2001 From: vtyeh Date: Tue, 15 Oct 2024 16:23:28 -0700 Subject: [PATCH 08/24] Fix broken links in drafts loaded with "load more" by dynamically creating edit link based on single_user bool in config.ini --- static/js/posts.js | 4 ++-- templates/user/articles.tmpl | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/static/js/posts.js b/static/js/posts.js index dfc30b7..17a31a8 100644 --- a/static/js/posts.js +++ b/static/js/posts.js @@ -188,7 +188,7 @@ var movePostHTML = function(postID) { } return $tmpl.innerHTML.replace(/POST_ID/g, postID); } -var createPostEl = function(post, owned) { +var createPostEl = function(post, owned, singleUser) { var $post = document.createElement('div'); let p = H.createPost(post.id, "", post.body) var title = (post.title || p.title || post.id); @@ -202,7 +202,7 @@ var createPostEl = function(post, owned) { posted = getFormattedDate(new Date(post.created)) } var hasDraft = H.exists('draft' + post.id); - $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete '+movePostHTML(post.id)+'

'; + $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete '+movePostHTML(post.id)+'

'; if (post.error) { $post.innerHTML += '

Sync error: ' + post.error + '

'; diff --git a/templates/user/articles.tmpl b/templates/user/articles.tmpl index 92f9c40..3e50863 100644 --- a/templates/user/articles.tmpl +++ b/templates/user/articles.tmpl @@ -202,8 +202,9 @@ function loadMorePosts() { if (http.readyState == 4) { if (http.status == 200) { var data = JSON.parse(http.responseText); + var singleUser = {{ .SingleUser }}; for (var i=0; i Date: Mon, 21 Oct 2024 09:59:58 +0530 Subject: [PATCH 09/24] Fix glibc issue --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 852090b..9f9ae97 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ GITREV=`git describe | cut -c 2-` -LDFLAGS=-ldflags="-s -w -X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)'" +LDFLAGS=-ldflags="-s -w -X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)' -extldflags '-static'" GOCMD=go GOINSTALL=$(GOCMD) install $(LDFLAGS) From 6b47fd9e35f40ebc9bacde3dbf5ad32bf4119406 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 13:41:58 +0000 Subject: [PATCH 10/24] Bump golang.org/x/net from 0.26.0 to 0.30.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.26.0 to 0.30.0. - [Commits](https://github.com/golang/net/compare/v0.26.0...v0.30.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 51b74c9..47d5f5a 100644 --- a/go.mod +++ b/go.mod @@ -50,7 +50,7 @@ require ( github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b github.com/writefreely/go-nodeinfo v1.2.0 golang.org/x/crypto v0.28.0 - golang.org/x/net v0.26.0 + golang.org/x/net v0.30.0 ) require ( diff --git a/go.sum b/go.sum index 1e6729e..084e2c1 100644 --- a/go.sum +++ b/go.sum @@ -242,8 +242,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 0ce5d3ba2671e4e1fecd9dc98e3759bbfeda948a Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:28:35 -0400 Subject: [PATCH 11/24] Accept Like activities from the fediverse This includes database changes; update with `writefreely db migrate`. Ref T906 --- activitypub.go | 116 ++++++++++++++++++++++++++++++++++++++- database_activitypub.go | 49 +++++++++++++++++ migrations/migrations.go | 1 + migrations/v16.go | 38 +++++++++++++ 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 database_activitypub.go create mode 100644 migrations/v16.go diff --git a/activitypub.go b/activitypub.go index 6a3b0a1..323bf90 100644 --- a/activitypub.go +++ b/activitypub.go @@ -22,6 +22,7 @@ import ( "net/http/httputil" "net/url" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -45,6 +46,11 @@ const ( apCacheTime = time.Minute ) +var ( + apCollectionPostIRIRegex = regexp.MustCompile("/api/collections/([a-z0-9\\-]+)/posts/([a-z0-9\\-]+)$") + apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-]{12,16})$") +) + var instanceColl *Collection func initActivityPub(app *App) { @@ -351,11 +357,76 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request a := streams.NewAccept() p := c.PersonObject() var to *url.URL - var isFollow, isUnfollow bool + var isFollow, isUnfollow, isLike bool + var likePostID string fullActor := &activitystreams.Person{} var remoteUser *RemoteUser res := &streams.Resolver{ + LikeCallback: func(l *streams.Like) error { + isLike = true + + // 1) Use the Like concrete type here + // 2) Errors are propagated to res.Deserialize call below + m["@context"] = []string{activitystreams.Namespace} + b, _ := json.Marshal(m) + if debugging { + log.Info("Like: %s", b) + } + + _, likeID := l.GetId() + if likeID == nil { + log.Error("Didn't resolve Like ID") + } + if p := l.HasObject(0); p == streams.NoPresence { + return fmt.Errorf("no object for Like activity at index 0") + } + + obj := l.Raw().GetObjectIRI(0) + /* + // TODO: handle this more robustly + l.ResolveObject(&streams.Resolver{ + LinkCallback: func(link *streams.Link) error { + return nil + }, + }, 0) + */ + + // Get post ID from URL + var collAlias, slug string + if m := apCollectionPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 3 { + collAlias = m[1] + slug = m[2] + } else if m = apDraftPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 2 { + likePostID = m[1] + } else { + return fmt.Errorf("unable to match objectIRI: %s", obj) + } + + // Get postID if all we have is collection and slug + if collAlias != "" && slug != "" { + c, err := app.db.GetCollection(collAlias) + if err != nil { + return err + } + p, err := app.db.GetPost(slug, c.ID) + if err != nil { + return err + } + likePostID = p.ID + } + + // Finally, get actor information + _, from := l.GetActor(0) + if from == nil { + return fmt.Errorf("No valid actor string") + } + fullActor, remoteUser, err = getActor(app, from.String()) + if err != nil { + return err + } + return nil + }, FollowCallback: func(f *streams.Follow) error { isFollow = true @@ -435,6 +506,48 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request return err } + // Handle synchronous activities + if isLike { + t, err := app.db.Begin() + if err != nil { + log.Error("Unable to start transaction: %v", err) + return fmt.Errorf("unable to start transaction: %v", err) + } + + var remoteUserID int64 + if remoteUser != nil { + remoteUserID = remoteUser.ID + } else { + remoteUserID, err = apAddRemoteUser(app, t, fullActor) + } + + // Add like + _, err = t.Exec("INSERT INTO remote_likes (post_id, remote_user_id, created) VALUES (?, ?, NOW())", likePostID, remoteUserID) + if err != nil { + if !app.db.isDuplicateKeyErr(err) { + t.Rollback() + log.Error("Couldn't add like in DB: %v\n", err) + return fmt.Errorf("Couldn't add like in DB: %v", err) + } else { + t.Rollback() + log.Error("Couldn't add like in DB: %v\n", err) + return fmt.Errorf("Couldn't add like in DB: %v", err) + } + } + + err = t.Commit() + if err != nil { + t.Rollback() + log.Error("Rolling back after Commit(): %v\n", err) + return fmt.Errorf("Rolling back after Commit(): %v\n", err) + } + + if debugging { + log.Info("Successfully liked post %s by remote user %s", likePostID, remoteUser.URL) + } + return impart.RenderActivityJSON(w, "", http.StatusOK) + } + go func() { if to == nil { if debugging { @@ -469,6 +582,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request if remoteUser != nil { followerID = remoteUser.ID } else { + // TODO: use apAddRemoteUser() here, instead! // Add follower locally, since it wasn't found before res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url) VALUES (?, ?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox, fullActor.URL) if err != nil { diff --git a/database_activitypub.go b/database_activitypub.go new file mode 100644 index 0000000..9df3724 --- /dev/null +++ b/database_activitypub.go @@ -0,0 +1,49 @@ +/* + * Copyright © 2024 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package writefreely + +import ( + "database/sql" + "fmt" + "github.com/writeas/web-core/activitystreams" + "github.com/writeas/web-core/log" +) + +func apAddRemoteUser(app *App, t *sql.Tx, fullActor *activitystreams.Person) (int64, error) { + // Add remote user locally, since it wasn't found before + res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url) VALUES (?, ?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox, fullActor.URL) + if err != nil { + t.Rollback() + return -1, fmt.Errorf("couldn't add new remoteuser in DB: %v", err) + } + + remoteUserID, err := res.LastInsertId() + if err != nil { + t.Rollback() + return -1, fmt.Errorf("no lastinsertid for followers, rolling back: %v", err) + } + + // Add in key + _, err = t.Exec("INSERT INTO remoteuserkeys (id, remote_user_id, public_key) VALUES (?, ?, ?)", fullActor.PublicKey.ID, remoteUserID, fullActor.PublicKey.PublicKeyPEM) + if err != nil { + if !app.db.isDuplicateKeyErr(err) { + t.Rollback() + log.Error("Couldn't add follower keys in DB: %v\n", err) + return -1, fmt.Errorf("couldn't add follower keys in DB: %v", err) + } else { + t.Rollback() + log.Error("Couldn't add follower keys in DB: %v\n", err) + return -1, fmt.Errorf("couldn't add follower keys in DB: %v", err) + } + } + + return remoteUserID, nil +} diff --git a/migrations/migrations.go b/migrations/migrations.go index fc638ee..6b5b094 100644 --- a/migrations/migrations.go +++ b/migrations/migrations.go @@ -71,6 +71,7 @@ var migrations = []Migration{ New("support newsletters", supportLetters), // V12 -> V13 New("support password resetting", supportPassReset), // V13 -> V14 New("speed up blog post retrieval", addPostRetrievalIndex), // V14 -> V15 + New("support ActivityPub likes", supportRemoteLikes), // V15 -> V16 (v0.16.0) } // CurrentVer returns the current migration version the application is on diff --git a/migrations/v16.go b/migrations/v16.go new file mode 100644 index 0000000..03ce78a --- /dev/null +++ b/migrations/v16.go @@ -0,0 +1,38 @@ +/* + * Copyright © 2024 Musing Studio LLC. + * + * This file is part of WriteFreely. + * + * WriteFreely is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, included + * in the LICENSE file in this source code package. + */ + +package migrations + +func supportRemoteLikes(db *datastore) error { + t, err := db.Begin() + if err != nil { + t.Rollback() + return err + } + + _, err = t.Exec(`CREATE TABLE remote_likes ( + post_id ` + db.typeChar(16) + ` NOT NULL, + remote_user_id ` + db.typeInt() + ` NOT NULL, + created ` + db.typeDateTime() + ` NOT NULL, + PRIMARY KEY (post_id,remote_user_id) +)`) + if err != nil { + t.Rollback() + return err + } + + err = t.Commit() + if err != nil { + t.Rollback() + return err + } + + return nil +} From 7f1cc6bf8f6fb74028bc5c4f9d6fa34789d4294b Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:40:18 -0400 Subject: [PATCH 12/24] Support un-liking posts from the fediverse Ref T906 --- activitypub.go | 116 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 89 insertions(+), 27 deletions(-) diff --git a/activitypub.go b/activitypub.go index 323bf90..267f929 100644 --- a/activitypub.go +++ b/activitypub.go @@ -357,8 +357,8 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request a := streams.NewAccept() p := c.PersonObject() var to *url.URL - var isFollow, isUnfollow, isLike bool - var likePostID string + var isFollow, isUnfollow, isLike, isUnlike bool + var likePostID, unlikePostID string fullActor := &activitystreams.Person{} var remoteUser *RemoteUser @@ -392,29 +392,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request }, 0) */ - // Get post ID from URL - var collAlias, slug string - if m := apCollectionPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 3 { - collAlias = m[1] - slug = m[2] - } else if m = apDraftPostIRIRegex.FindStringSubmatch(obj.String()); len(m) == 2 { - likePostID = m[1] - } else { - return fmt.Errorf("unable to match objectIRI: %s", obj) - } - - // Get postID if all we have is collection and slug - if collAlias != "" && slug != "" { - c, err := app.db.GetCollection(collAlias) - if err != nil { - return err - } - p, err := app.db.GetPost(slug, c.ID) - if err != nil { - return err - } - likePostID = p.ID - } + likePostID, err = parsePostIDFromURL(app, obj) // Finally, get actor information _, from := l.GetActor(0) @@ -465,8 +443,6 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request return impart.RenderActivityJSON(w, m, http.StatusOK) }, UndoCallback: func(u *streams.Undo) error { - isUnfollow = true - m["@context"] = []string{activitystreams.Namespace} b, _ := json.Marshal(m) if debugging { @@ -474,6 +450,31 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request } a.AppendObject(u.Raw()) + + // Check type -- we handle Undo:Like and Undo:Follow + _, err := u.ResolveObject(&streams.Resolver{ + LikeCallback: func(like *streams.Like) error { + isUnlike = true + + _, from := like.GetActor(0) + obj := like.Raw().GetObjectIRI(0) + unlikePostID, err = parsePostIDFromURL(app, obj) + fullActor, remoteUser, err = getActor(app, from.String()) + if err != nil { + return err + } + return nil + }, + // TODO: add FollowCallback for more robust handling + }, 0) + if err != nil { + return err + } + if isUnlike { + return nil + } + + isUnfollow = true _, to = u.GetActor(0) // TODO: get actor from object.object, not object obj := u.Raw().GetObjectIRI(0) @@ -546,6 +547,39 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request log.Info("Successfully liked post %s by remote user %s", likePostID, remoteUser.URL) } return impart.RenderActivityJSON(w, "", http.StatusOK) + } else if isUnlike { + t, err := app.db.Begin() + if err != nil { + log.Error("Unable to start transaction: %v", err) + return fmt.Errorf("unable to start transaction: %v", err) + } + + var remoteUserID int64 + if remoteUser != nil { + remoteUserID = remoteUser.ID + } else { + remoteUserID, err = apAddRemoteUser(app, t, fullActor) + } + + // Add follow + _, err = t.Exec("DELETE FROM remote_likes WHERE post_id = ? AND remote_user_id = ?", unlikePostID, remoteUserID) + if err != nil { + t.Rollback() + log.Error("Couldn't delete Like from DB: %v\n", err) + return fmt.Errorf("Couldn't delete Like from DB: %v", err) + } + + err = t.Commit() + if err != nil { + t.Rollback() + log.Error("Rolling back after Commit(): %v\n", err) + return fmt.Errorf("Rolling back after Commit(): %v\n", err) + } + + if debugging { + log.Info("Successfully un-liked post %s by remote user %s", unlikePostID, remoteUser.URL) + } + return impart.RenderActivityJSON(w, "", http.StatusOK) } go func() { @@ -1078,6 +1112,34 @@ func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error { return nil } +func parsePostIDFromURL(app *App, u *url.URL) (string, error) { + // Get post ID from URL + var collAlias, slug, postID string + if m := apCollectionPostIRIRegex.FindStringSubmatch(u.String()); len(m) == 3 { + collAlias = m[1] + slug = m[2] + } else if m = apDraftPostIRIRegex.FindStringSubmatch(u.String()); len(m) == 2 { + postID = m[1] + } else { + return "", fmt.Errorf("unable to match objectIRI: %s", u) + } + + // Get postID if all we have is collection and slug + if collAlias != "" && slug != "" { + c, err := app.db.GetCollection(collAlias) + if err != nil { + return "", err + } + p, err := app.db.GetPost(slug, c.ID) + if err != nil { + return "", err + } + postID = p.ID + } + + return postID, nil +} + func setCacheControl(w http.ResponseWriter, ttl time.Duration) { w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", ttl.Seconds())) } From 984e5bc4158fc623f38bf16b392d3fdb90e70da2 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:49:04 -0400 Subject: [PATCH 13/24] Show number of Likes on posts and Stats pages Ref T906 --- database.go | 19 +++++++++++++++++++ posts.go | 3 +++ templates/collection-post.tmpl | 1 + templates/user/stats.tmpl | 2 ++ 4 files changed, 25 insertions(+) diff --git a/database.go b/database.go index c5f239f..d715fd4 100644 --- a/database.go +++ b/database.go @@ -115,6 +115,7 @@ type writestore interface { DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error) ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error) + GetPostLikeCounts(postID string) (int64, error) GetPostsCount(c *CollectionObj, includeFuture bool) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) @@ -1174,6 +1175,12 @@ func (db *datastore) GetPost(id string, collectionID int64) (*PublicPost, error) return nil, ErrPostUnpublished } + // Get additional information needed before processing post data + p.LikeCount, err = db.GetPostLikeCounts(p.ID) + if err != nil { + return nil, err + } + res := p.processPost() if ownerName.Valid { res.Owner = &PublicUser{Username: ownerName.String} @@ -1236,6 +1243,18 @@ func (db *datastore) GetPostProperty(id string, collectionID int64, property str return res, nil } +func (db *datastore) GetPostLikeCounts(postID string) (int64, error) { + var count int64 + err := db.QueryRow("SELECT COUNT(*) FROM remote_likes WHERE post_id = ?", postID).Scan(&count) + switch { + case err == sql.ErrNoRows: + count = 0 + case err != nil: + return 0, err + } + return count, nil +} + // GetPostsCount modifies the CollectionObj to include the correct number of // standard (non-pinned) posts. It will return future posts if `includeFuture` // is true. diff --git a/posts.go b/posts.go index 19f01fd..1cfc1f0 100644 --- a/posts.go +++ b/posts.go @@ -105,6 +105,7 @@ type ( Created time.Time `db:"created" json:"created"` Updated time.Time `db:"updated" json:"updated"` ViewCount int64 `db:"view_count" json:"-"` + LikeCount int64 `db:"like_count" json:"likes"` Title zero.String `db:"title" json:"title"` HTMLTitle template.HTML `db:"title" json:"-"` Content string `db:"content" json:"body"` @@ -127,6 +128,7 @@ type ( IsTopLevel bool `json:"-"` DisplayDate string `json:"-"` Views int64 `json:"views"` + Likes int64 `json:"likes"` Owner *PublicUser `json:"-"` IsOwner bool `json:"-"` URL string `json:"url,omitempty"` @@ -1184,6 +1186,7 @@ func fetchPostProperty(app *App, w http.ResponseWriter, r *http.Request) error { func (p *Post) processPost() PublicPost { res := &PublicPost{Post: p, Views: 0} res.Views = p.ViewCount + res.Likes = p.LikeCount // TODO: move to own function loc := monday.FuzzyLocale(p.Language.String) res.DisplayDate = monday.Format(p.Created, monday.LongFormatsByLocale[loc], loc) diff --git a/templates/collection-post.tmpl b/templates/collection-post.tmpl index 54d5298..455dcfd 100644 --- a/templates/collection-post.tmpl +++ b/templates/collection-post.tmpl @@ -55,6 +55,7 @@ {{range .PinnedPosts}}{{.PlainDisplayTitle}}{{end}} {{end}} {{ if and .IsOwner .IsFound }}{{largeNumFmt .Views}} {{pluralize "view" "views" .Views}} + {{if .Likes}}{{largeNumFmt .Likes}} {{pluralize "like" "likes" .Likes}}{{end}} Edit {{if .IsPinned}}Unpin{{end}} {{ end }} diff --git a/templates/user/stats.tmpl b/templates/user/stats.tmpl index b7f3322..a0c08ec 100644 --- a/templates/user/stats.tmpl +++ b/templates/user/stats.tmpl @@ -51,11 +51,13 @@ td.none { Post {{if not .Collection}}Blog{{end}} Total Views + {{if .Federation}}Likes{{end}} {{range .TopPosts}} {{if ne .DisplayTitle ""}}{{.DisplayTitle}}{{else}}{{.ID}}{{end}} {{ if not $.Collection }}{{if .Collection}}{{.Collection.Title}}{{else}}Draft{{end}}{{ end }} {{.ViewCount}} + {{if $.Federation}}{{.LikeCount}}{{end}} {{end}} From 74a0947fdbae5dd52968d867aef5a00b29419809 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 11:58:23 -0400 Subject: [PATCH 14/24] Fix whitespace in collection-post.tmpl --- templates/collection-post.tmpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/templates/collection-post.tmpl b/templates/collection-post.tmpl index 455dcfd..280ab0e 100644 --- a/templates/collection-post.tmpl +++ b/templates/collection-post.tmpl @@ -4,7 +4,7 @@ {{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}} - + {{if .CustomCSS}}{{end}} @@ -45,7 +45,7 @@ - +
@@ -61,7 +61,7 @@ {{ end }}
- + {{if .Silenced}} {{template "user-silenced"}} {{end}} @@ -71,7 +71,7 @@

{{ end }} - + {{if .Collection.CanShowScript}} {{range .Collection.ExternalScripts}}{{end}} {{if .Collection.Script}}{{end}} From 9c0a2f8b13d4e2f69dea53174c84479e9499b781 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 12:18:21 -0400 Subject: [PATCH 15/24] Fix post ID extraction regex Ref T906 --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 267f929..6b3b8ee 100644 --- a/activitypub.go +++ b/activitypub.go @@ -48,7 +48,7 @@ const ( var ( apCollectionPostIRIRegex = regexp.MustCompile("/api/collections/([a-z0-9\\-]+)/posts/([a-z0-9\\-]+)$") - apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-]{12,16})$") + apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-])$") ) var instanceColl *Collection From 1b20d3704f96002db8b3aa47d2556bda121f7891 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 12:23:39 -0400 Subject: [PATCH 16/24] Catch errors around Like/Unlike actions Previously, we'd get nil panics or insert blank post IDs Ref T906 --- activitypub.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/activitypub.go b/activitypub.go index 6b3b8ee..33ec2fe 100644 --- a/activitypub.go +++ b/activitypub.go @@ -392,7 +392,13 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request }, 0) */ + if obj == nil { + return fmt.Errorf("didn't get ObjectIRI to Like") + } likePostID, err = parsePostIDFromURL(app, obj) + if err != nil { + return err + } // Finally, get actor information _, from := l.GetActor(0) @@ -458,7 +464,13 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request _, from := like.GetActor(0) obj := like.Raw().GetObjectIRI(0) + if obj == nil { + return fmt.Errorf("didn't get ObjectIRI for Undo Like") + } unlikePostID, err = parsePostIDFromURL(app, obj) + if err != nil { + return err + } fullActor, remoteUser, err = getActor(app, from.String()) if err != nil { return err From 8193a410825e9437ae900b096ac1518330047542 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 21 Oct 2024 12:37:37 -0400 Subject: [PATCH 17/24] Fix post ID extraction regex, actually Ref T906 --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 33ec2fe..1f7ec63 100644 --- a/activitypub.go +++ b/activitypub.go @@ -48,7 +48,7 @@ const ( var ( apCollectionPostIRIRegex = regexp.MustCompile("/api/collections/([a-z0-9\\-]+)/posts/([a-z0-9\\-]+)$") - apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-])$") + apDraftPostIRIRegex = regexp.MustCompile("/api/posts/([a-z0-9\\-]+)$") ) var instanceColl *Collection From 94f12dfc29856e47b42e05c479d0fb377d0d3204 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 22 Oct 2024 14:01:40 -0400 Subject: [PATCH 18/24] Fix bad regex on /api/posts/{post} endpoint --- routes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes.go b/routes.go index f3b454a..3abe88c 100644 --- a/routes.go +++ b/routes.go @@ -159,7 +159,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { // Handle posts write.HandleFunc("/api/posts", handler.All(newPost)).Methods("POST") posts := write.PathPrefix("/api/posts/").Subrouter() - posts.HandleFunc("/{post:[a-zA-Z0-9]}", handler.AllReader(fetchPost)).Methods("GET") + posts.HandleFunc("/{post:[a-zA-Z0-9]+}", handler.AllReader(fetchPost)).Methods("GET") posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST", "PUT") posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(deletePost)).Methods("DELETE") posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET") From 6e01bb7d94b6c5874690844f3f30aa0621d68d02 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 22 Oct 2024 14:04:05 -0400 Subject: [PATCH 19/24] Remove length restriction on other methods on /api/posts/{post} --- routes.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/routes.go b/routes.go index 3abe88c..efa79ea 100644 --- a/routes.go +++ b/routes.go @@ -160,9 +160,9 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { write.HandleFunc("/api/posts", handler.All(newPost)).Methods("POST") posts := write.PathPrefix("/api/posts/").Subrouter() posts.HandleFunc("/{post:[a-zA-Z0-9]+}", handler.AllReader(fetchPost)).Methods("GET") - posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(existingPost)).Methods("POST", "PUT") - posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}", handler.All(deletePost)).Methods("DELETE") - posts.HandleFunc("/{post:[a-zA-Z0-9]{10}}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET") + posts.HandleFunc("/{post:[a-zA-Z0-9]+}", handler.All(existingPost)).Methods("POST", "PUT") + posts.HandleFunc("/{post:[a-zA-Z0-9]+}", handler.All(deletePost)).Methods("DELETE") + posts.HandleFunc("/{post:[a-zA-Z0-9]+}/{property}", handler.AllReader(fetchPostProperty)).Methods("GET") posts.HandleFunc("/claim", handler.All(addPost)).Methods("POST") posts.HandleFunc("/disperse", handler.All(dispersePost)).Methods("POST") From c2ea61c82ee90beef1ddc7ce16ceced6d329a866 Mon Sep 17 00:00:00 2001 From: don Piotr Talarczyk Date: Tue, 22 Oct 2024 20:09:43 +0200 Subject: [PATCH 20/24] Fix two error messages: user create and user create --admin --- app.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index 7630254..3b4756a 100644 --- a/app.go +++ b/app.go @@ -892,12 +892,12 @@ func CreateUser(apper Apper, username, password string, isAdmin bool) error { if isAdmin { // Abort if trying to create admin user, but one already exists if firstUser != nil { - return fmt.Errorf("Admin user already exists (%s). Create a regular user with: writefreely --create-user", firstUser.Username) + return fmt.Errorf("Admin user already exists (%s). Create a regular user with: writefreely user create [USER]:[PASSWORD]", firstUser.Username) } } else { // Abort if trying to create regular user, but no admin exists yet if firstUser == nil { - return fmt.Errorf("No admin user exists yet. Create an admin first with: writefreely --create-admin") + return fmt.Errorf("No admin user exists yet. Create an admin first with: writefreely user create --admin [USER]:[PASSWORD]") } } From ec7d336bc2da56fe2465f1c08200a905fc18ee7a Mon Sep 17 00:00:00 2001 From: vtyeh Date: Tue, 22 Oct 2024 13:42:17 -0700 Subject: [PATCH 21/24] Update title link to be dynamic with new singleUser bool --- static/js/posts.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/js/posts.js b/static/js/posts.js index 17a31a8..c3c91e3 100644 --- a/static/js/posts.js +++ b/static/js/posts.js @@ -193,16 +193,17 @@ var createPostEl = function(post, owned, singleUser) { let p = H.createPost(post.id, "", post.body) var title = (post.title || p.title || post.id); title = title.replace(/' + title + ''; + $post.innerHTML = '

' + title + '

'; var posted = ""; if (post.created) { posted = getFormattedDate(new Date(post.created)) } var hasDraft = H.exists('draft' + post.id); - $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete '+movePostHTML(post.id)+'

'; + $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete '+movePostHTML(post.id)+'

'; if (post.error) { $post.innerHTML += '

Sync error: ' + post.error + '

'; From 82de4558de297098be66bfbfcac99abadace4ac1 Mon Sep 17 00:00:00 2001 From: vtyeh Date: Wed, 23 Oct 2024 10:32:04 -0700 Subject: [PATCH 22/24] Link title directly to post page --- static/js/posts.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/posts.js b/static/js/posts.js index c3c91e3..d44f19c 100644 --- a/static/js/posts.js +++ b/static/js/posts.js @@ -193,7 +193,7 @@ var createPostEl = function(post, owned, singleUser) { let p = H.createPost(post.id, "", post.body) var title = (post.title || p.title || post.id); title = title.replace(/' + title + ''; @@ -203,7 +203,7 @@ var createPostEl = function(post, owned, singleUser) { posted = getFormattedDate(new Date(post.created)) } var hasDraft = H.exists('draft' + post.id); - $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete '+movePostHTML(post.id)+'

'; + $post.innerHTML += '

' + posted + ' edit' + (hasDraft ? 'ed' : '') + ' delete '+movePostHTML(post.id)+'

'; if (post.error) { $post.innerHTML += '

Sync error: ' + post.error + '

'; From 6b4179fa01d94cf43f61b3d3a1f0290d39f0133d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 1 Dec 2024 18:15:58 -0500 Subject: [PATCH 23/24] Fix INSERT remote_likes query for SQLite --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 1f7ec63..960b863 100644 --- a/activitypub.go +++ b/activitypub.go @@ -535,7 +535,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request } // Add like - _, err = t.Exec("INSERT INTO remote_likes (post_id, remote_user_id, created) VALUES (?, ?, NOW())", likePostID, remoteUserID) + _, err = t.Exec("INSERT INTO remote_likes (post_id, remote_user_id, created) VALUES (?, ?, "+app.db.now()+")", likePostID, remoteUserID) if err != nil { if !app.db.isDuplicateKeyErr(err) { t.Rollback() From eca7bcda0a8ed5067c9474b1d70a1691fc640c31 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Sun, 1 Dec 2024 18:30:32 -0500 Subject: [PATCH 24/24] Fix comment in activitypub.go --- activitypub.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.go b/activitypub.go index 960b863..2bbc7ad 100644 --- a/activitypub.go +++ b/activitypub.go @@ -573,7 +573,7 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request remoteUserID, err = apAddRemoteUser(app, t, fullActor) } - // Add follow + // Remove like _, err = t.Exec("DELETE FROM remote_likes WHERE post_id = ? AND remote_user_id = ?", unlikePostID, remoteUserID) if err != nil { t.Rollback()