Compare commits

...
Sign in to create a new pull request.

164 commits

Author SHA1 Message Date
Matt Baer
72fa575fee
Merge pull request #1372 from writefreely/fix-context-security
Fix security context getting removed on AP collection endpoint
2025-06-04 14:26:57 -04:00
Matt Baer
01e239e657
Merge pull request #1373 from writefreely/fix-ghost-follow
Fix ActivityPub following from Ghost
2025-06-03 16:04:19 -04:00
Matt Baer
79ab0a3786
Merge pull request #1131 from writefreely/smtp
Support SMTP email configuration
2025-06-03 16:02:22 -04:00
Matt Baer
188b41ef53
Merge pull request #1153 from jbgi/smtp-fix
Fixes for smtp branch
2025-05-19 13:49:47 -04:00
Matt Baer
c249abdb10 Fix ActivityPub following from Ghost
This makes Follow request parsing more robust. Previously, this only worked if the `object`
was a URI, which is what many platforms send. Now, we can also handle objects here.
2025-05-08 14:05:44 -04:00
Matt Baer
2b79ab0313 Fix security context getting removed on AP collection endpoint 2025-05-07 17:20:22 -04:00
Matt Baer
db66a885fb
Merge pull request #1134 from sahilmulla/fix-edit-link
Fix edit link missing on mobile browser
2025-02-18 16:02:15 -05:00
Matt Baer
79a66ff140 Merge branch 'develop' into fix-edit-link 2025-02-18 16:00:16 -05:00
Matt Baer
4601951bbf
Merge pull request #1035 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.27
Bump github.com/microcosm-cc/bluemonday from 1.0.26 to 1.0.27
2025-02-18 14:31:06 -05:00
dependabot[bot]
e1db89311d
Bump github.com/microcosm-cc/bluemonday from 1.0.26 to 1.0.27
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.26 to 1.0.27.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.26...v1.0.27)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-18 19:30:40 +00:00
Matt Baer
f7966996d7
Merge pull request #1129 from writefreely/compile-darwin-arm64
Build for macOS arm64
2025-02-18 14:28:51 -05:00
Matt Baer
a7fa19f2e4
Merge pull request #1247 from eyberg/develop
adding a link to the nanos unikernel package repository
2025-02-12 11:48:17 -05:00
Ian Eyberg
7b5d583b76 updating pkg location 2025-02-11 16:31:22 -08:00
Ian Eyberg
b765c02166 adding a link to the nanos unikernel package repository 2025-01-27 17:19:35 -08:00
Jean-Baptiste Giraudeau
0f9c32161c
Add fieldName when constructing SmtpMessage + go fmt 2025-01-25 12:57:53 +01:00
Jean-Baptiste Giraudeau
63963b6b19
Send smtp msg to recipients individualy instead of using BCC 2025-01-25 12:50:56 +01:00
Jean-Baptiste Giraudeau
d9deb29730
Implement remaining plumbing for sending stmp emails to subscribers 2025-01-25 12:50:53 +01:00
Jean-Baptiste Giraudeau
e65b73dc73
app.go: Use cfg.Email.Enabled() to check for email config
before starting publishJobsQueue.

 Also fix Email.Enabled to handle smtp config.
2025-01-25 12:47:14 +01:00
Matt Baer
0be229cdaa
Merge pull request #1155 from freesteph/fix/allow-eu-emails
email: allow sending emails from EU Mailgun domains
2025-01-24 16:21:49 -05:00
Matt Baer
4504617c1a
Merge pull request #1165 from c7io-dev/patch2
Fix admin/user pagination
2025-01-24 15:48:37 -05:00
Matt Baer
17039e620e
Merge pull request #1128 from writefreely/archive-page
Add Archive page

Closes T873
2025-01-24 15:41:15 -05:00
Matt Baer
fbf505cff0 Merge branch 'develop' into archive-page 2025-01-24 15:40:25 -05:00
Matt Baer
3966f9fa40 Update date in README 2025-01-17 15:37:32 -05:00
Matt Baer
387ddac892 Merge branch 'develop' of github.com:writefreely/writefreely into develop 2025-01-17 15:36:33 -05:00
Matt Baer
e3b94d7fb5
Merge pull request #1237 from c7io-dev/patch3
Fix api/posts routing as previous change breaks existing function
2025-01-17 15:36:07 -05:00
Matt Baer
121a21d900 Merge branch 'develop' of github.com:writefreely/writefreely into develop 2025-01-17 15:29:37 -05:00
snullp
eab66ee5fc Fix api/posts routing as previous changes breaks existing function 2025-01-05 19:32:46 +00:00
Stéphane Maniaci
83e0a57338
email: allow sending emails from EU Mailgun domains
The Mailgun API requires being told when a EU email domain is used[1]
so introduce a new `mailgun_europe` config parameter to indicate that.

[1]: https://github.com/mailgun/mailgun-go?tab=readme-ov-file#usage
2024-12-09 14:56:24 +01:00
snullp
b643e0520f Fix admin/user pagination
Integer division floor the page count but the ceil operation is needed.
Implemented without float conversion.
2024-12-09 05:22:21 +00:00
Matt Baer
f88aa393c5
Merge pull request #1122 from writefreely/ap-likes
Support ActivityPub Likes

Closes T906
2024-12-01 18:34:31 -05:00
Matt Baer
eca7bcda0a Fix comment in activitypub.go 2024-12-01 18:30:32 -05:00
Matt Baer
6b4179fa01 Fix INSERT remote_likes query for SQLite 2024-12-01 18:15:58 -05:00
Matt Baer
e29f371232
Merge pull request #1127 from DonPiotr/develop
Fix two error messages: user create and user create --admin
2024-10-30 13:31:18 -04:00
sam-pc
b7de165b76 Fix edit link missing on mobile browser 2024-10-28 17:24:14 +05:30
Matt Baer
f49c0b1c4c Send password resets and login emails with new email layer
(Untested)

Ref T905 T731
2024-10-24 16:13:36 -04:00
Matt Baer
2fcd45819f Send subscription confirmation email through new email layer
(Untested)

Ref T905 T731
2024-10-24 16:08:43 -04:00
Matt Baer
d06077c432 Add basic email abstraction layer
(Untested.) This will allow us to send via any supported provider within
WriteFreely.

Ref T731 T905
2024-10-24 15:53:11 -04:00
Matt Baer
5b6d17c9b9 Add SMTP configuration values
Ref T905
2024-10-24 15:52:14 -04:00
Matt Baer
c046dd04e7 Build for macOS arm64
Part of #1003
2024-10-24 14:38:38 -04:00
Matt Baer
6384f4667b
Merge pull request #1103 from vtyeh/drafts-with-load-more-have-broken-links-1054
Fix broken links in drafts loaded with "load more" #1054
2024-10-24 11:39:36 -04:00
vtyeh
82de4558de Link title directly to post page 2024-10-23 10:32:04 -07:00
vtyeh
ec7d336bc2 Update title link to be dynamic with new singleUser bool 2024-10-22 13:42:17 -07:00
Matt Baer
6d57d9d6a1 Fix trailing whitespace in database.go 2024-10-22 15:56:48 -04:00
Matt Baer
7fbf49a0f0 Fix whitespace in modified LESS files 2024-10-22 15:56:32 -04:00
Matt Baer
76818287d6 Add Archive page for all blogs
This adds a special page at `blog-url/archive/` that lists all posts
on a blog in descending order.

It includes stylesheet changes. Update with `make ui`.

Ref T873
2024-10-22 15:56:14 -04:00
Matt Baer
646fff775c
Merge pull request #1034 from writefreely/dependabot/go_modules/github.com/go-sql-driver/mysql-1.8.1
Bump github.com/go-sql-driver/mysql from 1.7.1 to 1.8.1
2024-10-22 15:06:44 -04:00
Matt Baer
ec8e0b4a8b
Merge pull request #1036 from writefreely/dependabot/go_modules/github.com/fatih/color-1.17.0
Bump github.com/fatih/color from 1.16.0 to 1.17.0
2024-10-22 15:05:03 -04:00
Matt Baer
d1c4efc143
Merge pull request #988 from Anish-Parkhi/fix-response-text-plain
fix: removed unnecessary strict post number checking causing error
2024-10-22 14:33:58 -04:00
don Piotr Talarczyk
c2ea61c82e Fix two error messages: user create and user create --admin 2024-10-22 20:09:43 +02:00
Matt Baer
6e01bb7d94 Remove length restriction on other methods on /api/posts/{post} 2024-10-22 14:04:05 -04:00
Matt Baer
94f12dfc29 Fix bad regex on /api/posts/{post} endpoint 2024-10-22 14:02:52 -04:00
Matt Baer
9421cfd422
Merge pull request #1116 from sahilmulla/fix-glibc
Fix glibc issue
2024-10-22 10:42:36 -04:00
Matt Baer
8193a41082 Fix post ID extraction regex, actually
Ref T906
2024-10-21 12:37:37 -04:00
Matt Baer
1b20d3704f Catch errors around Like/Unlike actions
Previously, we'd get nil panics or insert blank post IDs

Ref T906
2024-10-21 12:23:39 -04:00
Matt Baer
9c0a2f8b13 Fix post ID extraction regex
Ref T906
2024-10-21 12:18:21 -04:00
Matt Baer
74a0947fdb Fix whitespace in collection-post.tmpl 2024-10-21 11:58:23 -04:00
Matt Baer
984e5bc415 Show number of Likes on posts and Stats pages
Ref T906
2024-10-21 11:49:04 -04:00
Matt Baer
7f1cc6bf8f Support un-liking posts from the fediverse
Ref T906
2024-10-21 11:40:18 -04:00
Matt Baer
0ce5d3ba26 Accept Like activities from the fediverse
This includes database changes; update with `writefreely db migrate`.

Ref T906
2024-10-21 11:28:35 -04:00
Matt Baer
8d3d7419cd
Merge pull request #1061 from writefreely/dependabot/go_modules/golang.org/x/net-0.30.0
Bump golang.org/x/net from 0.26.0 to 0.30.0
2024-10-21 09:43:53 -04:00
dependabot[bot]
6b47fd9e35
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] <support@github.com>
2024-10-21 13:41:58 +00:00
Matt Baer
25808c7281
Merge pull request #1062 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.28.0
Bump golang.org/x/crypto from 0.24.0 to 0.28.0
2024-10-21 09:40:36 -04:00
sam-pc
2f37c666fb Fix glibc issue 2024-10-21 09:59:58 +05:30
vtyeh
4f3bacb182 Fix broken links in drafts loaded with "load more" by dynamically creating edit link based on single_user bool in config.ini 2024-10-15 16:23:28 -07:00
Matt Baer
71033ecc3d
Merge pull request #1038 from leo9800/docker-prod
Docker for Production
2024-10-09 15:46:12 -04:00
Matt Baer
f80139456e
Merge pull request #1047 from writefreely/use-gosimple-slug
Switch to gosimple/slug from writeas/slug
2024-10-09 15:33:46 -04:00
Matt Baer
5198add7aa Merge branch 'develop' of github.com:writefreely/writefreely into develop 2024-10-05 14:49:48 -04:00
dependabot[bot]
096430f1cd
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] <support@github.com>
2024-10-05 18:40:49 +00:00
Matt Baer
8d14c09dc6
Merge pull request #1060 from writefreely/hotfix-0.15.1
Hotfix 0.15.1
2024-10-05 14:39:25 -04:00
Matt Baer
0adffb98a6
Merge branch 'develop' into hotfix-0.15.1 2024-10-05 14:39:08 -04:00
Matt Baer
deec706f14 Use Go 1.21 in make build-* 2024-10-05 14:17:30 -04:00
Matt Baer
ceb84bfc3c Bump version to 0.15.1 2024-10-05 13:43:02 -04:00
Matt Baer
401f70c7ee
Merge pull request #1050 from writefreely/nodeinfo-handle
Fix `follow` handle in nodeinfo
2024-10-05 13:41:41 -04:00
Matt Baer
0233a62f91 Use @writefreely handle for nodeinfo
Instead of @write_as
2024-10-01 18:42:46 -04:00
Matt Baer
4753eef550 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.
2024-09-27 19:20:43 -04:00
Matt Baer
d159e0a1ca Merge branch 'develop' of github.com:writefreely/writefreely into develop 2024-09-27 19:12:54 -04:00
Leo
52d6ea60f3 Create Dockerfile & sample docker-compose.yml for production build 2024-09-02 12:45:55 +10:00
Leo
2a668d18d3 Update config/setup.go for docker environment 2024-09-02 12:43:31 +10:00
dependabot[bot]
b9f50883a9
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] <support@github.com>
2024-09-01 22:25:17 +00:00
dependabot[bot]
680c0f5564
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] <support@github.com>
2024-09-01 22:25:11 +00:00
Matt Baer
b8d652eb1a
Merge pull request #1024 from writefreely/fix-sitemap
Correctly show 404 page on /sitemap.xml on multi-user instances
2024-08-16 21:59:30 -04:00
Matt Baer
884a479de6
Merge pull request #1001 from CDN18/oauth-patch
add client_id and client_secret to exchangeOauthCode form
2024-08-15 20:21:16 -04:00
Matt Baer
2bef7a2100
Merge pull request #1000 from CDN18/actor-patch
fix: do second actor deref from main-key endpoint against publickey.Owner
2024-08-15 20:18:27 -04:00
Matt Baer
c6d54665ac
Merge pull request #1025 from writefreely/bump-go
Bump Go from v1.19 to v1.21
2024-08-15 18:54:14 -04:00
Matt Baer
24e94302d9
Merge pull request #1026 from writefreely/dependabot/go_modules/github.com/urfave/cli/v2-2.27.4
Bump github.com/urfave/cli/v2 from 2.27.1 to 2.27.4
2024-08-15 18:53:53 -04:00
dependabot[bot]
8372386abd
Bump github.com/urfave/cli/v2 from 2.27.1 to 2.27.4
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.27.1 to 2.27.4.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.27.1...v2.27.4)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-15 22:51:32 +00:00
Matt Baer
23f0669f7a
Merge pull request #997 from writefreely/dependabot/go_modules/github.com/fatih/color-1.17.0
Bump github.com/fatih/color from 1.16.0 to 1.17.0
2024-08-15 18:51:28 -04:00
Matt Baer
bfb9e3c4fb Bump Go from v1.19 to v1.21 2024-08-15 18:44:18 -04:00
Matt Baer
1b888e0c04
Merge pull request #1011 from writefreely/dependabot/go_modules/golang.org/x/net-0.26.0
Bump golang.org/x/net from 0.22.0 to 0.26.0
2024-08-15 18:25:43 -04:00
Matt Baer
4e60f0fe6a
Merge pull request #984 from writefreely/dependabot/go_modules/github.com/go-sql-driver/mysql-1.8.1
Bump github.com/go-sql-driver/mysql from 1.7.1 to 1.8.1
2024-08-15 18:24:39 -04:00
Matt Baer
0eb7c3bf47
Merge pull request #1010 from writefreely/dependabot/go_modules/github.com/gorilla/schema-1.4.1
Bump github.com/gorilla/schema from 1.2.1 to 1.4.1
2024-08-15 18:18:41 -04:00
dependabot[bot]
de167b162c
Bump github.com/gorilla/schema from 1.2.1 to 1.4.1
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.2.1 to 1.4.1.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.2.1...v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/schema
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-15 22:17:13 +00:00
Matt Baer
d03ae0e93d
Merge pull request #1013 from writefreely/dependabot/go_modules/github.com/gorilla/sessions-1.3.0
Bump github.com/gorilla/sessions from 1.2.2 to 1.3.0
2024-08-15 18:11:39 -04:00
Matt Baer
47c584d0a2
Merge pull request #1014 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.24.0
Bump golang.org/x/crypto from 0.21.0 to 0.24.0
2024-08-15 18:08:42 -04:00
Matt Baer
e338880609 Correctly show 404 page on /sitemap.xml on multi-user instances
Sitemaps are only meant for individual blogs right now, so instead
of invalid HTML getting returned, we'll properly catch the issue and
show a user-friendly "not found" page.

Fixes #941
2024-08-15 18:00:13 -04:00
Matt Baer
1dd37bc56d
Merge pull request #894 from claabs/docker-healthcheck-fix
Fix Docker healthcheck with wget
2024-08-13 15:38:50 -04:00
Matt Baer
226bb14716
Merge pull request #933 from CDN18/develop
Add linux/arm64 platform support for docker
2024-08-13 15:16:20 -04:00
Matt Baer
68d43f7af9
Merge pull request #1023 from writefreely/update-phabricator
Update Phabricator URL in docs and PR template
2024-08-13 14:42:14 -04:00
Matt Baer
5cc89b6795 Update Phabricator URL in docs and PR template
Moved from phabricator.write.as to todo.musing.studio
2024-08-13 14:40:24 -04:00
dependabot[bot]
f5d9839a70
Bump golang.org/x/crypto from 0.21.0 to 0.24.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.21.0 to 0.24.0.
- [Commits](https://github.com/golang/crypto/compare/v0.21.0...v0.24.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 22:11:22 +00:00
dependabot[bot]
57497f9542
Bump github.com/gorilla/sessions from 1.2.2 to 1.3.0
Bumps [github.com/gorilla/sessions](https://github.com/gorilla/sessions) from 1.2.2 to 1.3.0.
- [Release notes](https://github.com/gorilla/sessions/releases)
- [Commits](https://github.com/gorilla/sessions/compare/v1.2.2...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/gorilla/sessions
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 22:11:18 +00:00
dependabot[bot]
8cf0d9c02c
Bump golang.org/x/net from 0.22.0 to 0.26.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.22.0 to 0.26.0.
- [Commits](https://github.com/golang/net/compare/v0.22.0...v0.26.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-01 22:11:10 +00:00
CDN18
bdcf369b3d
add client_id and client_secret to exchangeOauthCode form 2024-06-10 14:05:15 +08:00
CDN18
69eb4d6b0a
fix: do second actor deref from main-key endpoint against publickey.Owner 2024-06-10 13:57:22 +08:00
dependabot[bot]
d11270a340
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] <support@github.com>
2024-06-01 22:36:15 +00:00
Anish-Parkhi
1b69a89c59 fix: removed unnecessary strict post number checking causing error 2024-04-24 19:16:11 +05:30
Matt Baer
038a80c25e
Merge pull request #893 from writefreely/consistent-reader-nav
Fix Admin and Invite links never showing on Reader nav
2024-04-17 12:44:28 -04:00
Matt Baer
9ece6682ef
Merge pull request #930 from tkngaejcpi/develop
support more image formats
2024-04-17 12:43:50 -04:00
Matt Baer
b0b06ec945 Merge branch 'develop' of github.com:writefreely/writefreely into develop 2024-04-03 14:35:34 -04:00
Matt Baer
41e1989345
Merge pull request #982 from writefreely/dependabot/go_modules/golang.org/x/net-0.22.0
Bump golang.org/x/net from 0.20.0 to 0.22.0
2024-04-03 14:25:17 -04:00
Matt Baer
34d902062f
Merge pull request #927 from writefreely/dependabot/go_modules/github.com/stretchr/testify-1.9.0
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
2024-04-03 14:23:13 -04:00
dependabot[bot]
5b7e2a6f2f
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/v1.8.1/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] <support@github.com>
2024-04-01 22:46:52 +00:00
dependabot[bot]
ed9ff51b68
Bump golang.org/x/net from 0.20.0 to 0.22.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.20.0 to 0.22.0.
- [Commits](https://github.com/golang/net/compare/v0.20.0...v0.22.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 22:46:41 +00:00
CDN18
e36db46eb6 Add linux/arm64 platform support for docker 2024-03-06 19:17:10 +08:00
Riley Chang
83ffea7fa0
support more image formats 2024-03-04 22:48:51 +08:00
dependabot[bot]
3dd0a9b8dc
Bump github.com/stretchr/testify from 1.8.4 to 1.9.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.4 to 1.9.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.4...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-01 22:40:53 +00:00
Matt Baer
427f4980b9
Merge pull request #874 from claabs/docker-fixes
Version number and healthcheck fixes for Docker image
2024-02-20 10:19:30 -05:00
charlocharlie
0bf67639c1 Change Docker healthcheck to wget
curl is not available in the Alpine image
2024-02-20 09:08:33 -06:00
Matt Baer
e34a58d0ef Fix Admin and Invite links never showing on Reader nav 2024-02-20 10:02:14 -05:00
Matt Baer
6d547040ef
Merge pull request #878 from c7io-dev/snullp-patch-1
Add "Import posts" to base.tmpl to be consistent with /me/* nav bar
2024-02-20 09:55:00 -05:00
Matt Baer
216f36f47b
Merge pull request #883 from elkcityhazard/develop
add f.created to join, add Created to Scan
2024-02-20 09:32:20 -05:00
Andrew M McCall
a352a3518a add f.created to join, add Created to Scan 2024-02-14 19:48:47 -05:00
Big Squirrel
1a3f3f0ec6
Add "Import posts" to base.tmpl to be consistent with /me/* nav bar 2024-02-09 12:51:00 -08:00
charlocharlie
306ca173c6 Include .git context in Docker build for UI version number
Fixes #873
2024-02-06 14:31:18 -06:00
Matt Baer
1d89dea72e Exclude hidden files from release builds
For directories: templates, pages, static
2024-02-02 16:20:36 +01:00
Matt Baer
22de459a72
Merge pull request #854 from writefreely/api-inconsistencies
Fix Collection property serialization on API
2024-02-02 09:01:57 -05:00
Matt Baer
5be1f2451c Bump version to 0.15 2024-02-02 14:50:48 +01:00
Matt Baer
3a53353ed8
Merge pull request #861 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.21
Bump github.com/mattn/go-sqlite3 from 1.14.19 to 1.14.21
2024-02-01 18:53:18 -05:00
dependabot[bot]
56cad35b19
Bump github.com/mattn/go-sqlite3 from 1.14.19 to 1.14.21
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.19 to 1.14.21.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.19...v1.14.21)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-02-01 22:40:03 +00:00
Matt Baer
ff84c7aa4d
Merge pull request #826 from andi1984/issue-612-rtl
fix: RTL support on post textarea
2024-02-01 08:29:18 -05:00
Andreas Sander
4c6169d55d fix: RTL support on post textarea
Fixing right to left (short: RTL) support for respective RTL languages
by adding auto-detection for the user content's directionality based on
the text's language.

Fixes #612
2024-01-10 23:30:04 +01:00
Matt Baer
ab1b2922cc
Merge pull request #856 from writefreely/dependabot/go_modules/golang.org/x/net-0.20.0
Bump golang.org/x/net from 0.17.0 to 0.20.0
2024-01-10 16:13:39 -05:00
dependabot[bot]
9401d047d6
Bump golang.org/x/net from 0.17.0 to 0.20.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.17.0 to 0.20.0.
- [Commits](https://github.com/golang/net/compare/v0.17.0...v0.20.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:13:04 +00:00
Matt Baer
54b46b61db
Merge pull request #855 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.18.0
Bump golang.org/x/crypto from 0.14.0 to 0.18.0
2024-01-10 16:12:18 -05:00
Matt Baer
235a3ee143
Merge pull request #849 from writefreely/dependabot/go_modules/github.com/urfave/cli/v2-2.27.1
Bump github.com/urfave/cli/v2 from 2.25.7 to 2.27.1
2024-01-10 16:11:41 -05:00
Matt Baer
f4accd5064
Merge pull request #848 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.19
Bump github.com/mattn/go-sqlite3 from 1.14.17 to 1.14.19
2024-01-10 16:10:37 -05:00
Matt Baer
bc00ae1963
Merge pull request #841 from writefreely/dependabot/go_modules/github.com/gorilla/schema-1.2.1
Bump github.com/gorilla/schema from 1.2.0 to 1.2.1
2024-01-10 16:09:38 -05:00
dependabot[bot]
775d86cb00
Bump github.com/gorilla/schema from 1.2.0 to 1.2.1
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/schema
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:08:38 +00:00
Matt Baer
90e564870d
Merge pull request #838 from writefreely/dependabot/go_modules/github.com/gorilla/feeds-1.1.2
Bump github.com/gorilla/feeds from 1.1.1 to 1.1.2
2024-01-10 16:06:24 -05:00
dependabot[bot]
62c26e78ba
Bump github.com/gorilla/feeds from 1.1.1 to 1.1.2
Bumps [github.com/gorilla/feeds](https://github.com/gorilla/feeds) from 1.1.1 to 1.1.2.
- [Release notes](https://github.com/gorilla/feeds/releases)
- [Commits](https://github.com/gorilla/feeds/compare/v1.1.1...v1.1.2)

---
updated-dependencies:
- dependency-name: github.com/gorilla/feeds
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:04:53 +00:00
dependabot[bot]
69002fdcbf
Bump golang.org/x/crypto from 0.14.0 to 0.18.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.14.0 to 0.18.0.
- [Commits](https://github.com/golang/crypto/compare/v0.14.0...v0.18.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:04:33 +00:00
dependabot[bot]
4acf08d9e9
Bump github.com/mattn/go-sqlite3 from 1.14.17 to 1.14.19
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.17 to 1.14.19.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.17...v1.14.19)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:04:27 +00:00
Matt Baer
df7fee2018
Merge pull request #837 from writefreely/dependabot/go_modules/github.com/fatih/color-1.16.0
Bump github.com/fatih/color from 1.15.0 to 1.16.0
2024-01-10 16:03:54 -05:00
Matt Baer
c64c7c77ae
Merge pull request #836 from writefreely/dependabot/go_modules/github.com/gorilla/sessions-1.2.2
Bump github.com/gorilla/sessions from 1.2.1 to 1.2.2
2024-01-10 16:03:40 -05:00
dependabot[bot]
e788b90b04
Bump github.com/gorilla/sessions from 1.2.1 to 1.2.2
Bumps [github.com/gorilla/sessions](https://github.com/gorilla/sessions) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/gorilla/sessions/releases)
- [Commits](https://github.com/gorilla/sessions/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: github.com/gorilla/sessions
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-10 21:02:58 +00:00
Matt Baer
66f049cc39
Merge pull request #834 from writefreely/dependabot/go_modules/github.com/gorilla/mux-1.8.1
Bump github.com/gorilla/mux from 1.8.0 to 1.8.1
2024-01-10 16:01:06 -05:00
Matt Baer
ff07c447ee
Merge pull request #833 from writefreely/dependabot/go_modules/github.com/gorilla/csrf-1.7.2
Bump github.com/gorilla/csrf from 1.7.1 to 1.7.2
2024-01-10 16:00:16 -05:00
Matt Baer
d33a556732
Merge pull request #823 from writefreely/contact-links
Add Contact page links to footers
2024-01-10 15:57:49 -05:00
Matt Baer
737d76176a Fix indentation in footer.tmpl 2024-01-10 15:57:31 -05:00
dependabot[bot]
8e6ddc1993
Bump github.com/urfave/cli/v2 from 2.25.7 to 2.27.1
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.25.7 to 2.27.1.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.25.7...v2.27.1)

---
updated-dependencies:
- dependency-name: github.com/urfave/cli/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-01 22:29:53 +00:00
Matt Baer
b85afa1ea6
Merge pull request #828 from d4rklynk/dockerfile
Dockerfile
2023-12-01 17:49:54 -05:00
dependabot[bot]
6b8cc591cc
Bump github.com/fatih/color from 1.15.0 to 1.16.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/fatih/color
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 22:47:13 +00:00
dependabot[bot]
859a4b37e5
Bump github.com/gorilla/mux from 1.8.0 to 1.8.1
Bumps [github.com/gorilla/mux](https://github.com/gorilla/mux) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/gorilla/mux/releases)
- [Commits](https://github.com/gorilla/mux/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/gorilla/mux
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 22:46:48 +00:00
dependabot[bot]
3caa33b9bf
Bump github.com/gorilla/csrf from 1.7.1 to 1.7.2
Bumps [github.com/gorilla/csrf](https://github.com/gorilla/csrf) from 1.7.1 to 1.7.2.
- [Release notes](https://github.com/gorilla/csrf/releases)
- [Commits](https://github.com/gorilla/csrf/compare/v1.7.1...v1.7.2)

---
updated-dependencies:
- dependency-name: github.com/gorilla/csrf
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-01 22:46:41 +00:00
Matt Baer
e932467ac9
Merge pull request #822 from writefreely/custom-css-config
Look for custom CSS in static_parent_dir
2023-12-01 17:34:03 -05:00
d4rklynk
aac4514577 Fix healthcheck URL 2023-11-18 14:30:54 +01:00
d4rklynk
21f5073717 Fix port 2023-11-18 14:24:37 +01:00
d4rklynk
64d1a2f536 Update Dockerfile 2023-11-18 14:18:39 +01:00
Matt Baer
e4e059cb13 Fix Collection property serialization on API
Use standard string instead of sql.NullString for `style_sheet`, `script`, and `signature`.

Addresses #820
2023-11-07 10:54:16 -05:00
Matt Baer
feab841609 Add Contact page links to footers 2023-11-07 10:21:24 -05:00
Matt Baer
3e7d236c6d
Merge pull request #528 from isaacsu/protect-drafts
Protect drafts if they are part of a Private or Protected collection
2023-11-07 10:12:19 -05:00
Matt Baer
bf213cd0b0 Fix drafts never showing, even when not part of private/protected blog 2023-10-06 12:40:46 -04:00
Matt Baer
815500ab78 Merge branch 'develop' into protect-drafts 2023-10-06 12:19:37 -04:00
Isaac Su
df7be46417 Protect drafts if they are part of a Private or Protected collection 2022-01-11 16:31:11 +11:00
48 changed files with 1093 additions and 211 deletions

View file

@ -1,2 +1 @@
Dockerfile
.git

View file

@ -2,4 +2,4 @@
---
- [ ] I have signed the [CLA](https://phabricator.write.as/L1)
- [ ] I have signed the [CLA](https://todo.musing.studio/L1)

View file

@ -30,6 +30,14 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
# Set up QEMU for cross-building
- name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0
# Set up Docker Buildx
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
@ -56,6 +64,7 @@ jobs:
uses: docker/build-push-action@v5.0.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View file

@ -18,11 +18,11 @@ First, you'll want to clone the WriteFreely repo, install development dependenci
### Starting development
Next, [join our forum](https://discuss.write.as) so you can discuss development with the team. Then take a look at [our roadmap on Phabricator](https://phabricator.write.as/tag/write_freely/) to see where the project is today and where it's headed.
Next, [join our forum](https://discuss.write.as) so you can discuss development with the team. Then take a look at [our roadmap on Phabricator](https://todo.musing.studio/tag/writefreely/) to see where the project is today and where it's headed.
When you find something you want to work on, start a new topic on the forum or jump into an existing discussion, if there is one. The team will respond and continue the conversation there.
Lastly, **before submitting any code**, please sign our [contributor's agreement](https://phabricator.write.as/L1) so we can accept your contributions. It is substantially similar to the _Apache Individual Contributor License Agreement_. If you'd like to know about the rationale behind this requirement, you can [read more about that here](https://phabricator.write.as/w/writefreely/cla/).
Lastly, **before submitting any code**, please sign our [contributor's agreement](https://todo.musing.studio/L1) so we can accept your contributions. It is substantially similar to the _Apache Individual Contributor License Agreement_. If you'd like to know about the rationale behind this requirement, you can [read more about that here](https://todo.musing.studio/w/writefreely/cla/).
### Branching

View file

@ -1,13 +1,14 @@
# Build image
FROM golang:1.19-alpine as build
FROM golang:1.21-alpine3.18 as build
LABEL org.opencontainers.image.source=https://github.com/writefreely/writefreely
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 add --update nodejs npm make g++ git
RUN npm install -g less less-plugin-clean-css
RUN apk -U upgrade \
&& apk add --no-cache nodejs npm make g++ git \
&& npm install -g less less-plugin-clean-css \
&& mkdir -p /go/src/github.com/writefreely/writefreely
RUN mkdir -p /go/src/github.com/writefreely/writefreely
WORKDIR /go/src/github.com/writefreely/writefreely
COPY . .
@ -18,9 +19,9 @@ ENV GO111MODULE=on
ENV NODE_OPTIONS=--openssl-legacy-provider
RUN make build \
&& make ui
RUN mkdir /stage && \
cp -R /go/bin \
&& make ui \
&& mkdir /stage \
&& cp -R /go/bin \
/go/src/github.com/writefreely/writefreely/templates \
/go/src/github.com/writefreely/writefreely/static \
/go/src/github.com/writefreely/writefreely/pages \
@ -29,9 +30,11 @@ RUN mkdir /stage && \
/stage
# Final image
FROM alpine:3
FROM alpine:3.18.4
RUN apk -U upgrade \
&& apk add --no-cache openssl ca-certificates
RUN apk add --no-cache openssl ca-certificates
COPY --from=build --chown=daemon:daemon /stage /go
WORKDIR /go
@ -40,3 +43,6 @@ EXPOSE 8080
USER daemon
ENTRYPOINT ["cmd/writefreely/writefreely"]
HEALTHCHECK --start-period=5s --interval=15s --timeout=5s \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/ || exit 1

34
Dockerfile.prod Normal file
View file

@ -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"]

View file

@ -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)
@ -27,37 +27,43 @@ build-linux: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.21.x -out writefreely -pkg ./cmd/writefreely .
build-windows: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.21.x -out writefreely -pkg ./cmd/writefreely .
build-darwin: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.21.x -out writefreely -pkg ./cmd/writefreely .
build-darwin-arm64: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=darwin/arm64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.21.x -out writefreely -pkg ./cmd/writefreely .
build-arm6: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.21.x -out writefreely -pkg ./cmd/writefreely .
build-arm7: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.21.x -out writefreely -pkg ./cmd/writefreely .
build-arm64: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi
xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.21.x -out writefreely -pkg ./cmd/writefreely .
build-docker :
$(DOCKERCMD) build -t $(IMAGE_NAME):latest -t $(IMAGE_NAME):$(GITREV) .
@ -83,9 +89,9 @@ install : build
release : clean ui
mkdir -p $(BUILDPATH)
cp -r templates $(BUILDPATH)
cp -r pages $(BUILDPATH)
cp -r static $(BUILDPATH)
rsync -av --exclude=".*" templates $(BUILDPATH)
rsync -av --exclude=".*" pages $(BUILDPATH)
rsync -av --exclude=".*" static $(BUILDPATH)
rm -r $(BUILDPATH)/static/local
scripts/invalidate-css.sh $(BUILDPATH)
mkdir $(BUILDPATH)/keys
@ -109,6 +115,10 @@ release : clean ui
mv build/$(BINARY_NAME)-darwin-10.12-amd64 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_macos_amd64.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-darwin-arm64
mv build/$(BINARY_NAME)-darwin-arm64 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_macos_arm64.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-windows
mv build/$(BINARY_NAME)-windows-4.0-amd64.exe $(BUILDPATH)/$(BINARY_NAME).exe
cd build; zip -r ../$(BINARY_NAME)_$(GITREV)_windows_amd64.zip ./$(BINARY_NAME)

View file

@ -69,6 +69,7 @@ For common platforms, start with our [pre-built binaries](https://github.com/wri
You can also find WriteFreely in these package repositories, thanks to our wonderful community!
* [Arch User Repository](https://aur.archlinux.org/packages/writefreely/)
* [Nanos Repository](https://repo.ops.city/v2/packages/eyberg/writefreely/show)
## Documentation
@ -86,4 +87,4 @@ Before contributing anything, please read our [Contributing Guide](https://githu
## License
Copyright © 2018-2022 [Musing Studio LLC](https://musing.studio) and contributing authors. Licensed under the [AGPL](https://github.com/writefreely/writefreely/blob/develop/LICENSE).
Copyright © 2018-2025 [Musing Studio LLC](https://musing.studio) and contributing authors. Licensed under the [AGPL](https://github.com/writefreely/writefreely/blob/develop/LICENSE).

View file

@ -13,7 +13,7 @@ package writefreely
import (
"encoding/json"
"fmt"
"github.com/mailgun/mailgun-go"
"github.com/writefreely/writefreely/mailer"
"github.com/writefreely/writefreely/spam"
"html/template"
"net/http"
@ -1378,13 +1378,19 @@ func handleResetPasswordInit(app *App, w http.ResponseWriter, r *http.Request) e
func emailPasswordReset(app *App, toEmail, token string) error {
// Send email
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
mlr, err := mailer.New(app.cfg.Email)
if err != nil {
return err
}
footerPara := "Didn't request this password reset? Your account is still safe, and you can safely ignore this email."
plainMsg := fmt.Sprintf("We received a request to reset your password on %s. Please click the following link to continue (or copy and paste it into your browser): %s/reset?t=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara)
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-password@"+app.cfg.Email.Domain+">", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail))
m, err := mlr.NewMessage(app.cfg.App.SiteName+" <noreply-password@"+app.cfg.Email.Domain+">", "Reset Your "+app.cfg.App.SiteName+" Password", plainMsg, fmt.Sprintf("<%s>", toEmail))
if err != nil {
return err
}
m.AddTag("Password Reset")
m.SetHtml(fmt.Sprintf(`<html>
m.SetHTML(fmt.Sprintf(`<html>
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
@ -1394,8 +1400,7 @@ func emailPasswordReset(app *App, toEmail, token string) error {
</div>
</body>
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.SiteName, app.cfg.App.Host, token, footerPara))
_, _, err := gun.Send(m)
return err
return mlr.Send(m)
}
func loginViaEmail(app *App, alias, redirectTo string) error {
@ -1424,15 +1429,21 @@ func loginViaEmail(app *App, alias, redirectTo string) error {
}
// Send email
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
mlr, err := mailer.New(app.cfg.Email)
if err != nil {
return err
}
toEmail := u.EmailClear(app.keys)
footerPara := "This link will only work once and expires in 15 minutes. Didn't ask us to log in? You can safely ignore this email."
plainMsg := fmt.Sprintf("Log in to %s here: %s/login?to=%s&with=%s\n\n%s", app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, footerPara)
m := mailgun.NewMessage(app.cfg.App.SiteName+" <noreply-login@"+app.cfg.Email.Domain+">", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail))
m, err := mlr.NewMessage(app.cfg.App.SiteName+" <noreply-login@"+app.cfg.Email.Domain+">", "Log in to "+app.cfg.App.SiteName, plainMsg, fmt.Sprintf("<%s>", toEmail))
if err != nil {
return err
}
m.AddTag("Email Login")
m.SetHtml(fmt.Sprintf(`<html>
m.SetHTML(fmt.Sprintf(`<html>
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
<div style="margin:0 auto; max-width: 40em; font-size: 1.2em;">
<h1 style="font-size:1.75em"><a style="text-decoration:none;color:#000;" href="%s">%s</a></h1>
@ -1441,9 +1452,7 @@ func loginViaEmail(app *App, alias, redirectTo string) error {
</div>
</body>
</html>`, app.cfg.App.Host, app.cfg.App.SiteName, app.cfg.App.Host, redirectTo, t, app.cfg.App.SiteName, footerPara))
_, _, err = gun.Send(m)
return err
return mlr.Send(m)
}
func saveTempInfo(app *App, key, val string, r *http.Request, w http.ResponseWriter) error {

View file

@ -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\\-]+)$")
)
var instanceColl *Collection
func initActivityPub(app *App) {
@ -195,7 +201,7 @@ func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Reques
ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "outbox", res.TotalPosts, p)
ocp.OrderedItems = []interface{}{}
posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false)
posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false, "")
for _, pp := range *posts {
pp.Collection = res
o := pp.ActivityObject(app)
@ -351,11 +357,60 @@ 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, isUnlike bool
var likePostID, unlikePostID 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)
*/
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)
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
@ -381,6 +436,17 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request
a.AppendObject(f.Raw())
_, to = f.GetActor(0)
obj := f.Raw().GetObjectIRI(0)
if obj == nil {
if debugging {
log.Error("GetObjectIRI on Follow for actor is empty; trying object")
}
ao := f.Raw().GetObject(0)
if ao == nil {
log.Error("Fell back to GetObject and none parsed, so no actor ID! Follow request probably FAILED!")
} else {
obj = ao.GetId()
}
}
a.AppendActor(obj)
// First get actor information
@ -394,8 +460,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 {
@ -403,6 +467,37 @@ 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)
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
}
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)
@ -435,6 +530,81 @@ 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 (?, ?, "+app.db.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)
} 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)
}
// Remove like
_, 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() {
if to == nil {
if debugging {
@ -469,6 +639,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 {
@ -822,13 +993,28 @@ func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser,
log.Info("Not found; fetching actor %s remotely", actorIRI)
actorResp, err := resolveIRI(app.cfg.App.Host, actorIRI)
if err != nil {
log.Error("Unable to get actor! %v", err)
log.Error("Unable to get base actor! %v", err)
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."}
}
if err := unmarshalActor(actorResp, actor); err != nil {
log.Error("Unable to unmarshal actor! %v", err)
log.Error("Unable to unmarshal base actor! %v", err)
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."}
}
baseActor := &activitystreams.Person{}
if err := unmarshalActor(actorResp, baseActor); err != nil {
log.Error("Unable to unmarshal actual actor! %v", err)
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actual actor."}
}
// Fetch the actual actor using the owner field from the publicKey object
actualActorResp, err := resolveIRI(app.cfg.App.Host, baseActor.PublicKey.Owner)
if err != nil {
log.Error("Unable to get actual actor! %v", err)
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actual actor."}
}
if err := unmarshalActor(actualActorResp, actor); err != nil {
log.Error("Unable to unmarshal actual actor! %v", err)
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actual actor."}
}
} else {
return nil, nil, err
}
@ -949,6 +1135,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()))
}

View file

@ -208,7 +208,7 @@ func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Requ
p.Flashes, _ = getSessionFlashes(app, w, r, nil)
p.TotalUsers = app.db.GetAllUsersCount()
ttlPages := p.TotalUsers / adminUsersPerPage
ttlPages := (p.TotalUsers - 1) / adminUsersPerPage + 1
p.TotalPages = []int{}
for i := 1; i <= int(ttlPages); i++ {
p.TotalPages = append(p.TotalPages, i)

17
app.go
View file

@ -49,6 +49,7 @@ const (
staticDir = "static"
assumedTitleLen = 80
postsPerPage = 10
postsPerArchPage = 40
serverSoftware = "WriteFreely"
softwareURL = "https://writefreely.org"
@ -58,7 +59,7 @@ var (
debugging bool
// Software version can be set from git env using -ldflags
softwareVer = "0.14.0"
softwareVer = "0.15.1"
// DEPRECATED VARS
isSingleUser bool
@ -428,15 +429,11 @@ func Initialize(apper Apper, debug bool) (*App, error) {
initActivityPub(apper.App())
if apper.App().cfg.Email.Domain != "" || apper.App().cfg.Email.MailgunPrivate != "" {
if apper.App().cfg.Email.Domain == "" {
log.Error("[FAILED] Starting publish jobs queue: no [letters]domain config value set.")
} else if apper.App().cfg.Email.MailgunPrivate == "" {
log.Error("[FAILED] Starting publish jobs queue: no [letters]mailgun_private config value set.")
} else {
if apper.App().cfg.Email.Enabled() {
log.Info("Starting publish jobs queue...")
go startPublishJobsQueue(apper.App())
}
} else {
log.Error("[FAILED] Starting publish jobs queue: no email provider is configured.")
}
// Handle local timeline, if enabled
@ -892,12 +889,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]")
}
}

View file

@ -114,9 +114,9 @@ type (
Alias *string `schema:"alias" json:"alias"`
Title *string `schema:"title" json:"title"`
Description *string `schema:"description" json:"description"`
StyleSheet *sql.NullString `schema:"style_sheet" json:"style_sheet"`
Script *sql.NullString `schema:"script" json:"script"`
Signature *sql.NullString `schema:"signature" json:"signature"`
StyleSheet *string `schema:"style_sheet" json:"style_sheet"`
Script *string `schema:"script" json:"script"`
Signature *string `schema:"signature" json:"signature"`
Monetization *string `schema:"monetization_pointer" json:"monetization_pointer"`
Verification *string `schema:"verification_link" json:"verification_link"`
LetterReply *string `schema:"letter_reply" json:"letter_reply"`
@ -608,7 +608,7 @@ func fetchCollectionPosts(app *App, w http.ResponseWriter, r *http.Request) erro
}
}
ps, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false)
ps, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false, "")
if err != nil {
return err
}
@ -828,15 +828,18 @@ func checkUserForCollection(app *App, cr *collectionReq, r *http.Request, isPost
return u, nil
}
func newDisplayCollection(c *Collection, cr *collectionReq, page int) *DisplayCollection {
func newDisplayCollection(c *Collection, cr *collectionReq, page int) (*DisplayCollection, error) {
coll := &DisplayCollection{
CollectionObj: NewCollectionObj(c),
CurrentPage: page,
Prefix: cr.prefix,
IsTopLevel: isSingleUser,
}
c.db.GetPostsCount(coll.CollectionObj, cr.isCollOwner)
return coll
err := c.db.GetPostsCount(coll.CollectionObj, cr.isCollOwner)
if err != nil {
return nil, err
}
return coll, nil
}
// getCollectionPage returns the collection page as an int. If the parsed page value is not
@ -881,16 +884,29 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
// Serve ActivityStreams data now, if requested
if IsActivityPubRequest(r) {
ac := c.PersonObject()
ac.Context = []interface{}{activitystreams.Namespace}
setCacheControl(w, apCacheTime)
return impart.RenderActivityJSON(w, ac, http.StatusOK)
}
// Fetch extra data about the Collection
// TODO: refactor out this logic, shared in collection.go:fetchCollection()
coll := newDisplayCollection(c, cr, page)
coll, err := newDisplayCollection(c, cr, page)
if err != nil {
return err
}
coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(coll.Format.PostsPerPage())))
var ct PostType
if isArchiveView(r) {
ct = postArch
}
// FIXME: this number will be off when user has pinned posts but isn't a Pro user
ppp := coll.Format.PostsPerPage()
if ct == postArch {
ppp = postsPerArchPage
}
coll.TotalPages = int(math.Ceil(float64(coll.TotalPosts) / float64(ppp)))
if coll.TotalPages > 0 && page > coll.TotalPages {
redirURL := fmt.Sprintf("/page/%d", coll.TotalPages)
if !app.cfg.App.SingleUser {
@ -899,7 +915,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
return impart.HTTPError{http.StatusFound, redirURL}
}
coll.Posts, _ = app.db.GetPosts(app.cfg, c, page, cr.isCollOwner, false, false)
coll.Posts, _ = app.db.GetPosts(app.cfg, c, page, cr.isCollOwner, false, false, "")
// Serve collection
displayPage := CollectionPage{
@ -958,6 +974,9 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
collTmpl := "collection"
if app.cfg.App.Chorus {
collTmpl = "chorus-collection"
} else if isArchiveView(r) {
displayPage.NavSuffix = "/archive/"
collTmpl = "collection-archive"
}
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
if err != nil {
@ -984,6 +1003,10 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro
return err
}
func isArchiveView(r *http.Request) bool {
return strings.HasSuffix(r.RequestURI, "/archive/") || mux.Vars(r)["archive"] == "archive"
}
func handleViewMention(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
handle := vars["handle"]
@ -1019,7 +1042,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
return err
}
coll := newDisplayCollection(c, cr, page)
coll, _ := newDisplayCollection(c, cr, page)
taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner)
if err != nil {
@ -1117,7 +1140,7 @@ func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request)
return err
}
coll := newDisplayCollection(c, cr, page)
coll, _ := newDisplayCollection(c, cr, page)
coll.Language = lang
coll.NavSuffix = fmt.Sprintf("/lang:%s", lang)

View file

@ -171,8 +171,17 @@ type (
}
EmailCfg struct {
// SMTP configuration values
Host string `ini:"smtp_host"`
Port int `ini:"smtp_port"`
Username string `ini:"smtp_username"`
Password string `ini:"smtp_password"`
EnableStartTLS bool `ini:"smtp_enable_start_tls"`
// Mailgun configuration values
Domain string `ini:"domain"`
MailgunPrivate string `ini:"mailgun_private"`
MailgunEurope bool `ini:"mailgun_europe"`
}
// Config holds the complete configuration for running a writefreely instance
@ -242,7 +251,8 @@ func (ac *AppCfg) LandingPath() string {
}
func (lc EmailCfg) Enabled() bool {
return lc.Domain != "" && lc.MailgunPrivate != ""
return (lc.Domain != "" && lc.MailgunPrivate != "") ||
lc.Username != "" && lc.Password != "" && lc.Host != "" && lc.Port > 0
}
func (ac AppCfg) SignupPath() string {

View file

@ -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()
}

View file

@ -14,15 +14,18 @@ import (
"context"
"database/sql"
"fmt"
"github.com/go-sql-driver/mysql"
"github.com/writeas/web-core/silobridge"
wf_db "github.com/writefreely/writefreely/db"
"github.com/writefreely/writefreely/parse"
"net/http"
"net/url"
"strings"
"time"
"github.com/writeas/monday"
"github.com/go-sql-driver/mysql"
"github.com/writeas/web-core/silobridge"
wf_db "github.com/writefreely/writefreely/db"
"github.com/writefreely/writefreely/parse"
"github.com/guregu/null"
"github.com/guregu/null/zero"
uuid "github.com/nu7hatch/gouuid"
@ -114,8 +117,9 @@ type writestore interface {
DispersePosts(userID int64, postIDs []string) (*[]ClaimPostResult, error)
ClaimPosts(cfg *config.Config, userID int64, collAlias string, posts *[]ClaimPostRequest) (*[]ClaimPostResult, error)
GetPostsCount(c *CollectionObj, includeFuture bool)
GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error)
GetPostLikeCounts(postID string) (int64, error)
GetPostsCount(c *CollectionObj, includeFuture bool) error
GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool, contentType PostType) (*[]PublicPost, error)
GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error)
GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error)
@ -905,9 +909,9 @@ func (db *datastore) UpdateCollection(app *App, c *SubmittedCollection, alias st
q := query.NewUpdate().
SetStringPtr(c.Title, "title").
SetStringPtr(c.Description, "description").
SetNullString(c.StyleSheet, "style_sheet").
SetNullString(c.Script, "script").
SetNullString(c.Signature, "post_signature")
SetStringPtr(c.StyleSheet, "style_sheet").
SetStringPtr(c.Script, "script").
SetStringPtr(c.Signature, "post_signature")
if c.Format != nil {
cf := &CollectionFormat{Format: c.Format.String}
@ -1173,6 +1177,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}
@ -1235,10 +1245,22 @@ 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.
func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) error {
var count int64
timeCondition := ""
if !includeFuture {
@ -1251,16 +1273,18 @@ func (db *datastore) GetPostsCount(c *CollectionObj, includeFuture bool) {
case err != nil:
log.Error("Failed selecting from collections: %v", err)
c.TotalPosts = 0
return err
}
c.TotalPosts = int(count)
return nil
}
// GetPosts retrieves all posts for the given Collection.
// It will return future posts if `includeFuture` is true.
// It will include only standard (non-pinned) posts unless `includePinned` is true.
// TODO: change includeFuture to isOwner, since that's how it's used
func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) {
func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool, contentType PostType) (*[]PublicPost, error) {
collID := c.ID
cf := c.NewFormat()
@ -1274,6 +1298,9 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
if page == 0 {
start = 0
pagePosts = 1000
} else if contentType == postArch {
pagePosts = postsPerArchPage
start = page*pagePosts - pagePosts
}
limitStr := ""
@ -1288,6 +1315,7 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
if !includePinned {
pinnedCondition = "AND pinned_position IS NULL"
}
// FUTURE: handle different post contentType's here
rows, err := db.Query("SELECT "+postCols+" FROM posts WHERE collection_id = ? "+pinnedCondition+" "+timeCondition+" ORDER BY created "+order+limitStr, collID)
if err != nil {
log.Error("Failed selecting from posts: %v", err)
@ -1308,7 +1336,13 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
p.augmentContent(c)
p.formatContent(cfg, c, includeFuture, false)
posts = append(posts, p.processPost())
pubPost := p.processPost()
if contentType == postArch {
// Overwrite DisplayDate with special Archive page version
loc := monday.FuzzyLocale(pubPost.Language.String)
pubPost.DisplayDate = monday.Format(pubPost.Created, monday.LongNoYrFormatsByLocale[loc], loc)
}
posts = append(posts, pubPost)
}
err = rows.Err()
if err != nil {
@ -1497,7 +1531,7 @@ ORDER BY created `+order+limitStr, collID, lang)
}
func (db *datastore) GetAPFollowers(c *Collection) (*[]RemoteUser, error) {
rows, err := db.Query("SELECT actor_id, inbox, shared_inbox FROM remotefollows f INNER JOIN remoteusers u ON f.remote_user_id = u.id WHERE collection_id = ?", c.ID)
rows, err := db.Query("SELECT actor_id, inbox, shared_inbox, f.created FROM remotefollows f INNER JOIN remoteusers u ON f.remote_user_id = u.id WHERE collection_id = ?", c.ID)
if err != nil {
log.Error("Failed selecting from followers: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve followers."}
@ -1507,7 +1541,7 @@ func (db *datastore) GetAPFollowers(c *Collection) (*[]RemoteUser, error) {
followers := []RemoteUser{}
for rows.Next() {
f := RemoteUser{}
err = rows.Scan(&f.ActorID, &f.Inbox, &f.SharedInbox)
err = rows.Scan(&f.ActorID, &f.Inbox, &f.SharedInbox, &f.Created)
followers = append(followers, f)
}
return &followers, nil

49
database_activitypub.go Normal file
View file

@ -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
}

25
docker-compose.prod.yml Normal file
View file

@ -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

View file

@ -14,6 +14,7 @@ import (
"database/sql"
"encoding/json"
"fmt"
"github.com/writefreely/writefreely/mailer"
"html/template"
"net/http"
"strings"
@ -21,7 +22,6 @@ import (
"github.com/aymerick/douceur/inliner"
"github.com/gorilla/mux"
"github.com/mailgun/mailgun-go"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/impart"
"github.com/writeas/web-core/data"
@ -307,8 +307,14 @@ Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.Ca
Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%`
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
m := mailgun.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg)
mlr, err := mailer.New(app.cfg.Email)
if err != nil {
return err
}
m, err := mlr.NewMessage(p.Collection.DisplayTitle()+" <"+p.Collection.Alias+"@"+app.cfg.Email.Domain+">", stripmd.Strip(p.DisplayTitle()), plainMsg)
if err != nil {
return err
}
replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo)
if replyTo != "" {
m.SetReplyTo(replyTo)
@ -405,13 +411,13 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un
return err
}
m.SetHtml(html)
m.SetHTML(html)
log.Info("[email] Adding %d recipient(s)", len(subs))
for _, s := range subs {
e := s.FinalEmail(app.keys)
log.Info("[email] Adding %s", e)
err = m.AddRecipientAndVariables(e, map[string]interface{}{
err = m.AddRecipientAndVariables(e, map[string]string{
"id": s.ID,
"to": e,
"token": s.Token,
@ -421,8 +427,8 @@ Sent to %recipient.to%. Unsubscribe: ` + p.Collection.CanonicalURL() + `email/un
}
}
res, _, err := gun.Send(m)
log.Info("[email] Send result: %s", res)
err = mlr.Send(m)
log.Info("[email] Email sent")
if err != nil {
log.Error("Unable to send post email: %v", err)
return err
@ -437,17 +443,23 @@ func sendSubConfirmEmail(app *App, c *Collection, email, subID, token string) er
}
// Send email
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
mlr, err := mailer.New(app.cfg.Email)
if err != nil {
return err
}
plainMsg := "Confirm your subscription to " + c.DisplayTitle() + ` (` + c.CanonicalURL() + `) to start receiving future posts. Simply click the following link (or copy and paste it into your browser):
` + c.CanonicalURL() + "email/confirm/" + subID + "?t=" + token + `
If you didn't subscribe to this site or you're not sure why you're getting this email, you can delete it. You won't be subscribed or receive any future emails.`
m := mailgun.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email))
m, err := mlr.NewMessage(c.DisplayTitle()+" <"+c.Alias+"@"+app.cfg.Email.Domain+">", "Confirm your subscription to "+c.DisplayTitle(), plainMsg, fmt.Sprintf("<%s>", email))
if err != nil {
return err
}
m.AddTag("Email Verification")
m.SetHtml(`<html>
m.SetHTML(`<html>
<body style="font-family:Lora, 'Palatino Linotype', Palatino, Baskerville, 'Book Antiqua', 'New York', 'DejaVu serif', serif; font-size: 100%%; margin:1em 2em;">
<div style="font-size: 1.2em;">
<p>Confirm your subscription to <a href="` + c.CanonicalURL() + `">` + c.DisplayTitle() + `</a> to start receiving future posts:</p>
@ -456,7 +468,10 @@ If you didn't subscribe to this site or you're not sure why you're getting this
</div>
</body>
</html>`)
gun.Send(m)
err = mlr.Send(m)
if err != nil {
return err
}
return nil
}

View file

@ -119,7 +119,7 @@ func compileFullExport(app *App, u *User) *ExportUser {
var collObjs []CollectionObj
for _, c := range *colls {
co := &CollectionObj{Collection: c}
co.Posts, err = app.db.GetPosts(app.cfg, &c, 0, true, false, true)
co.Posts, err = app.db.GetPosts(app.cfg, &c, 0, true, false, true, "")
if err != nil {
log.Error("unable to get collection posts: %v", err)
}

View file

@ -67,7 +67,7 @@ func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
if tag != "" {
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, 1, false)
} else {
coll.Posts, _ = app.db.GetPosts(app.cfg, c, 1, false, true, false)
coll.Posts, _ = app.db.GetPosts(app.cfg, c, 1, false, true, false, "")
}
author := ""

52
go.mod
View file

@ -8,25 +8,26 @@ 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.15.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-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
github.com/gorilla/csrf v1.7.1
github.com/gorilla/feeds v1.1.1
github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0
github.com/gorilla/sessions v1.2.1
github.com/gorilla/csrf v1.7.2
github.com/gorilla/feeds v1.1.2
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
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec
github.com/mailgun/mailgun-go v2.0.0+incompatible
github.com/manifoldco/promptui v0.9.0
github.com/mattn/go-sqlite3 v1.14.17
github.com/microcosm-cc/bluemonday v1.0.26
github.com/mattn/go-sqlite3 v1.14.21
github.com/microcosm-cc/bluemonday v1.0.27
github.com/mitchellh/go-wordwrap v1.0.1
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d
github.com/onsi/ginkgo v1.16.4 // indirect
@ -34,8 +35,8 @@ require (
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.25.7
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.4
github.com/writeas/activity v0.1.2
github.com/writeas/activityserve v0.0.0-20230428180247-dc13a4f4d835
github.com/writeas/go-strip-markdown/v2 v2.1.1
@ -45,48 +46,53 @@ 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
golang.org/x/crypto v0.14.0
golang.org/x/net v0.17.0
golang.org/x/crypto v0.28.0
golang.org/x/net v0.30.0
)
require github.com/xhit/go-simple-mail/v2 v2.16.0
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
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe // indirect
github.com/gofrs/uuid v3.3.0+incompatible // indirect
github.com/gologme/log v1.2.0 // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/css v1.0.1 // 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
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.3.2 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
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-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.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.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
)
go 1.19
go 1.21

100
go.sum
View file

@ -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=
@ -20,8 +22,8 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/clbanning/mxj v1.8.3/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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=
@ -35,8 +37,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.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
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=
@ -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=
@ -71,23 +73,30 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
github.com/gorilla/feeds v1.1.2 h1:pxzZ5PD3RJdhFH2FsJJ4x6PqMqbgFk1+Vez4XWBW8Iw=
github.com/gorilla/feeds v1.1.2/go.mod h1:WMib8uJP3BbY+X8Szd1rA5Pzhdfh+HCCAYT2z7Fza6Y=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
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=
@ -103,11 +112,13 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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=
@ -117,13 +128,13 @@ github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GW
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.21 h1:IXocQLOykluc3xPE0Lvy8FtggMz1G+U3mEjg+0zGizc=
github.com/mattn/go-sqlite3 v1.14.21/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4=
github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
@ -148,8 +159,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/rogpeppe/go-internal v1.3.2 h1:XU784Pr0wdahMY2bYcyK6N1KuaRAdLtqD4qd8D18Bfs=
github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
@ -164,10 +176,12 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY=
github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0=
github.com/writeas/activityserve v0.0.0-20230428180247-dc13a4f4d835 h1:bm/7gYo6y3GxtTa1qyUFyCk29CTnBAKt7z4D2MASYrw=
@ -201,8 +215,10 @@ github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ5
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M=
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss=
github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/xhit/go-simple-mail/v2 v2.16.0 h1:ouGy/Ww4kuaqu2E2UrDw7SvLaziWTB60ICLkIkNVccA=
github.com/xhit/go-simple-mail/v2 v2.16.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -211,8 +227,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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
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=
@ -231,8 +247,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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
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=
@ -259,8 +275,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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
@ -275,8 +291,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.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
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=

View file

@ -111,7 +111,7 @@ func handleGopherCollection(app *App, w gopher.ResponseWriter, r *gopher.Request
w.WriteInfo(c.Description)
}
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false)
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false, "")
if err != nil {
return err
}

View file

@ -672,6 +672,26 @@ body#collection article, body#subpage article {
}
}
}
#wrapper.archive {
h1 {
margin: 0 !important;
}
ul {
list-style: none;
li {
display: flex;
justify-content: space-between;
line-height: 1.4;
margin: 0.5em 0;
}
.year {
font-weight: bold;
font-size: 1.5em;
}
}
}
body#post article {
p.badge {
font-size: 0.9em;

View file

@ -30,7 +30,7 @@ body {
}
}
article, pre, .hljs {
article, pre, .hljs, #wrapper.archive ul {
padding: 0.5em 2rem 1.5em;
}
body#post article, pre, .hljs {

181
mailer/mailer.go Normal file
View file

@ -0,0 +1,181 @@
/*
* 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 mailer
import (
"fmt"
"github.com/mailgun/mailgun-go"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/config"
mail "github.com/xhit/go-simple-mail/v2"
"strings"
)
type (
// Mailer holds configurations for the preferred mailing provider.
Mailer struct {
smtp *mail.SMTPServer
mailGun *mailgun.MailgunImpl
}
// Message holds the email contents and metadata for the preferred mailing provider.
Message struct {
mgMsg *mailgun.Message
smtpMsg *SmtpMessage
}
SmtpMessage struct {
from string
replyTo string
subject string
recipients []Recipient
html string
text string
}
Recipient struct {
email string
vars map[string]string
}
)
// New creates a new Mailer from the instance's config.EmailCfg, returning an error if not properly configured.
func New(eCfg config.EmailCfg) (*Mailer, error) {
m := &Mailer{}
if eCfg.Domain != "" && eCfg.MailgunPrivate != "" {
m.mailGun = mailgun.NewMailgun(eCfg.Domain, eCfg.MailgunPrivate)
if eCfg.MailgunEurope {
m.mailGun.SetAPIBase("https://api.eu.mailgun.net/v3")
}
} else if eCfg.Username != "" && eCfg.Password != "" && eCfg.Host != "" && eCfg.Port > 0 {
m.smtp = mail.NewSMTPClient()
m.smtp.Host = eCfg.Host
m.smtp.Port = eCfg.Port
m.smtp.Username = eCfg.Username
m.smtp.Password = eCfg.Password
if eCfg.EnableStartTLS {
m.smtp.Encryption = mail.EncryptionSTARTTLS
}
// To allow sending multiple email
m.smtp.KeepAlive = true
} else {
return nil, fmt.Errorf("no email provider is configured")
}
return m, nil
}
// NewMessage creates a new Message from the given parameters.
func (m *Mailer) NewMessage(from, subject, text string, to ...string) (*Message, error) {
msg := &Message{}
if m.mailGun != nil {
msg.mgMsg = m.mailGun.NewMessage(from, subject, text, to...)
} else if m.smtp != nil {
msg.smtpMsg = &SmtpMessage{
from: from,
replyTo: "",
subject: subject,
recipients: make([]Recipient, len(to)),
html: "",
text: text,
}
for _, r := range to {
msg.smtpMsg.recipients = append(msg.smtpMsg.recipients, Recipient{r, make(map[string]string)})
}
}
return msg, nil
}
// SetHTML sets the body of the message.
func (m *Message) SetHTML(html string) {
if m.smtpMsg != nil {
m.smtpMsg.html = html
} else if m.mgMsg != nil {
m.mgMsg.SetHtml(html)
}
}
func (m *Message) SetReplyTo(replyTo string) {
if m.smtpMsg != nil {
m.smtpMsg.replyTo = replyTo
} else {
m.mgMsg.SetReplyTo(replyTo)
}
}
// AddTag attaches a tag to the Message for providers that support it.
func (m *Message) AddTag(tag string) {
if m.mgMsg != nil {
m.mgMsg.AddTag(tag)
}
}
func (m *Message) AddRecipientAndVariables(r string, vars map[string]string) error {
if m.smtpMsg != nil {
m.smtpMsg.recipients = append(m.smtpMsg.recipients, Recipient{r, vars})
return nil
} else {
varsInterfaces := make(map[string]interface{}, len(vars))
for k, v := range vars {
varsInterfaces[k] = v
}
return m.mgMsg.AddRecipientAndVariables(r, varsInterfaces)
}
}
// Send sends the given message via the preferred provider.
func (m *Mailer) Send(msg *Message) error {
if m.smtp != nil {
client, err := m.smtp.Connect()
if err != nil {
return err
}
emailSent := false
for _, r := range msg.smtpMsg.recipients {
customMsg := mail.NewMSG()
customMsg.SetFrom(msg.smtpMsg.from)
if msg.smtpMsg.replyTo != "" {
customMsg.SetReplyTo(msg.smtpMsg.replyTo)
}
customMsg.SetSubject(msg.smtpMsg.subject)
customMsg.AddTo(r.email)
cText := msg.smtpMsg.text
cHtml := msg.smtpMsg.html
for v, value := range r.vars {
placeHolder := fmt.Sprintf("%%recipient.%s%%", v)
cText = strings.ReplaceAll(cText, placeHolder, value)
cHtml = strings.ReplaceAll(cHtml, placeHolder, value)
}
customMsg.SetBody(mail.TextHTML, cHtml)
customMsg.AddAlternative(mail.TextPlain, cText)
e := customMsg.Error
if e == nil {
e = customMsg.Send(client)
}
if e == nil {
emailSent = true
} else {
log.Error("Unable to send email to %s: %v", r.email, e)
err = e
}
}
if !emailSent {
// only send an error if no email could be sent (to avoid retry of successfully sent emails)
return err
}
} else if m.mailGun != nil {
_, _, err := m.mailGun.Send(msg.mgMsg)
if err != nil {
return err
}
}
return nil
}

View file

@ -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

38
migrations/v16.go Normal file
View file

@ -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
}

View file

@ -46,7 +46,7 @@ func nodeInfoConfig(db *datastore, cfg *config.Config) *nodeinfo.Config {
Software: nodeinfo.SoftwareMeta{
HomePage: softwareURL,
GitHub: "https://github.com/writefreely/writefreely",
Follow: "https://writing.exchange/@write_as",
Follow: "https://writing.exchange/@writefreely",
},
MaxBlogs: cfg.App.MaxBlogs,
PublicReader: cfg.App.LocalTimeline,

View file

@ -70,6 +70,8 @@ func (c genericOauthClient) buildLoginURL(state string) (string, error) {
func (c genericOauthClient) exchangeOauthCode(ctx context.Context, code string) (*TokenResponse, error) {
form := url.Values{}
form.Add("client_id", c.ClientID)
form.Add("client_secret", c.ClientSecret)
form.Add("grant_type", "authorization_code")
form.Add("redirect_uri", c.CallbackLocation)
form.Add("scope", c.Scope)

View file

@ -13,7 +13,7 @@ package writefreely
import (
"context"
"errors"
"github.com/writeas/slug"
"github.com/gosimple/slug"
"net/http"
"net/url"
"strings"

View file

@ -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"
@ -49,6 +49,12 @@ const (
postIDLen = 10
postMetaDateFormat = "2006-01-02 15:04:05"
)
type PostType string
const (
postArch PostType = "archive"
shortCodePaid = "<!--paid-->"
)
@ -105,6 +111,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 +134,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"`
@ -314,6 +322,8 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
// Display reserved page if that is requested resource
if t, ok := pages[r.URL.Path[1:]+".tmpl"]; ok {
return handleTemplatedPage(app, w, r, t)
} else if r.URL.Path == "/sitemap.xml" && !app.cfg.App.SingleUser {
return impart.HTTPError{Status: http.StatusNotFound, Message: "Page not found."}
} else if (strings.Contains(r.URL.Path, ".") && !isRaw && !isMarkdown) || r.URL.Path == "/robots.txt" || r.URL.Path == "/manifest.json" {
// Serve static file
app.shttp.ServeHTTP(w, r)
@ -341,6 +351,7 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
}
var ownerID sql.NullInt64
var collectionID sql.NullInt64
var title string
var content string
var font string
@ -356,7 +367,7 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
return impart.HTTPError{http.StatusFound, fmt.Sprintf("/%s%s", fixedID, ext)}
}
err := app.db.QueryRow("SELECT owner_id, title, content, text_appearance, view_count, language, rtl FROM posts WHERE id = ?", friendlyID).Scan(&ownerID, &title, &content, &font, &views, &language, &rtl)
err := app.db.QueryRow("SELECT owner_id, collection_id, title, content, text_appearance, view_count, language, rtl FROM posts WHERE id = ?", friendlyID).Scan(&ownerID, &collectionID, &title, &content, &font, &views, &language, &rtl)
switch {
case err == sql.ErrNoRows:
found = false
@ -426,6 +437,16 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
}
}
var protectDraft bool
if found && collectionID.Valid {
collection, err := app.db.GetCollectionByID(collectionID.Int64)
if err != nil {
log.Error("view post: %v", err)
}
protectDraft = collection.IsPrivate() || collection.IsProtected()
}
// Check if post has been unpublished
if title == "" && content == "" {
gone = true
@ -490,6 +511,10 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error {
if !page.IsOwner && silenced {
return ErrPostNotFound
}
if !page.IsOwner && protectDraft {
return ErrPostNotFound
}
page.Silenced = silenced
err = templates["post"].ExecuteTemplate(w, "post", page)
if err != nil {
@ -1167,6 +1192,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)
@ -1490,6 +1516,10 @@ func viewCollectionPost(app *App, w http.ResponseWriter, r *http.Request) error
// User tried to access blog feed without a trailing slash, and
// there's no post with a slug "feed"
return impart.HTTPError{http.StatusFound, c.CanonicalURL() + "feed/"}
} else if slug == "archive" {
// User tried to access blog Archive without a trailing slash, and
// there's no post with a slug "archive"
return impart.HTTPError{http.StatusFound, c.CanonicalURL() + "archive/"}
}
po := &Post{
@ -1654,7 +1684,7 @@ func (rp *RawPost) Updated8601() string {
return rp.Updated.Format("2006-01-02T15:04:05Z")
}
var imageURLRegex = regexp.MustCompile(`(?i)[^ ]+\.(gif|png|jpg|jpeg|image)$`)
var imageURLRegex = regexp.MustCompile(`(?i)[^ ]+\.(gif|png|jpg|jpeg|avif|avifs|webp|jxl|image)$`)
func (p *Post) extractImages() {
p.Images = extractImages(p.Content)

View file

@ -229,11 +229,9 @@ func showLocalTimeline(app *App, w http.ResponseWriter, r *http.Request, page in
TotalPages: ttlPages,
SelTopic: tag,
}
if app.cfg.App.Chorus {
u := getUserSession(app, r)
d.IsAdmin = u != nil && u.IsAdmin()
d.CanInvite = canUserInvite(app.cfg, d.IsAdmin)
}
c, err := getReaderSection(app)
if err != nil {
return err

View file

@ -159,12 +159,12 @@ 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]{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("/claim", handler.All(addPost)).Methods("POST")
posts.HandleFunc("/disperse", handler.All(dispersePost)).Methods("POST")
posts.HandleFunc("/{post:[a-zA-Z0-9]+}", handler.AllReader(fetchPost)).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")
write.HandleFunc("/auth/signup", handler.Web(handleWebSignup, UserLevelNoneRequired)).Methods("POST")
write.HandleFunc("/auth/login", handler.Web(webLogin, UserLevelNoneRequired)).Methods("POST")
@ -221,6 +221,8 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
func RouteCollections(handler *Handler, r *mux.Router) {
r.HandleFunc("/logout", handler.Web(handleLogOutCollection, UserLevelOptional))
r.HandleFunc("/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader))
r.HandleFunc("/archive/", handler.Web(handleViewCollection, UserLevelReader))
r.HandleFunc("/{archive:archive}/page/{page:[0-9]+}", handler.Web(handleViewCollection, UserLevelReader))
r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional))
r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", handler.Web(handleViewCollectionLang, UserLevelOptional))
r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))

View file

@ -66,7 +66,7 @@ func handleViewSitemap(app *App, w http.ResponseWriter, r *http.Request) error {
host = c.CanonicalURL()
sm := buildSitemap(host, pre)
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false)
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false, "")
if err != nil {
log.Error("Error getting posts: %v", err)
return err

View file

@ -188,21 +188,22 @@ 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);
title = title.replace(/</g, "&lt;");
var postLink = (singleUser ? '/d/' : '/') + post.id
$post.id = 'post-' + post.id;
$post.className = 'post';
$post.innerHTML = '<h3><a href="/' + post.id + '">' + title + '</a></h3>';
$post.innerHTML = '<h3><a href="' + postLink + '">' + title + '</a></h3>';
var posted = "";
if (post.created) {
posted = getFormattedDate(new Date(post.created))
}
var hasDraft = H.exists('draft' + post.id);
$post.innerHTML += '<h4><date>' + posted + '</date> <a class="action" href="/pad/' + post.id + '">edit' + (hasDraft ? 'ed' : '') + '</a> <a class="delete action" href="/' + post.id + '" onclick="delPost(event, \'' + post.id + '\'' + (owned === true ? ', true' : '') + ')">delete</a> '+movePostHTML(post.id)+'</h4>';
$post.innerHTML += '<h4><date>' + posted + '</date> <a class="action" href="' + postLink + '/edit">edit' + (hasDraft ? 'ed' : '') + '</a> <a class="delete action" href="/' + post.id + '" onclick="delPost(event, \'' + post.id + '\'' + (owned === true ? ', true' : '') + ')">delete</a> '+movePostHTML(post.id)+'</h4>';
if (post.error) {
$post.innerHTML += '<p class="error"><strong>Sync error:</strong> ' + post.error + ' <nav><a href="#" onclick="localPosts.dismissError(event, this)">dismiss</a> <a href="#" onclick="localPosts.deletePost(event, this, \''+post.id+'\')">remove post</a></nav></p>';

View file

@ -14,8 +14,8 @@ import (
"errors"
"html/template"
"io"
"os"
"net/http"
"os"
"path/filepath"
"strings"
@ -70,14 +70,14 @@ func initTemplate(parentDir, name string) {
filepath.Join(parentDir, templatesDir, "base.tmpl"),
filepath.Join(parentDir, templatesDir, "user", "include", "silenced.tmpl"),
}
if name == "collection" || name == "collection-tags" || name == "chorus-collection" || name == "read" {
if name == "collection" || name == "collection-tags" || name == "collection-archive" || name == "chorus-collection" || name == "read" {
// These pages list out collection posts, so we also parse templatesDir + "include/posts.tmpl"
files = append(files, filepath.Join(parentDir, templatesDir, "include", "posts.tmpl"))
}
if name == "chorus-collection" || name == "chorus-collection-post" {
files = append(files, filepath.Join(parentDir, templatesDir, "user", "include", "header.tmpl"))
}
if name == "collection" || name == "collection-tags" || name == "collection-post" || name == "post" || name == "chorus-collection" || name == "chorus-collection-post" {
if name == "collection" || name == "collection-tags" || name == "collection-archive" || name == "collection-post" || name == "post" || name == "chorus-collection" || name == "chorus-collection-post" {
files = append(files, filepath.Join(parentDir, templatesDir, "include", "post-render.tmpl"))
}
templates[name] = template.Must(template.New("").Funcs(funcMap).ParseFiles(files...))

View file

@ -14,7 +14,7 @@
<div id="overlay"></div>
<textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
<textarea dir="auto" id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
{{end}}{{.Post.Content}}</textarea>

View file

@ -28,6 +28,7 @@
<ul><li class="has-submenu"><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul>
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
<li><a href="/me/settings">Account settings</a></li>
<li><a href="/me/import">Import posts</a></li>
<li><a href="/me/export">Export</a></li>
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}}
<li class="separator"><hr /></li>

View file

@ -0,0 +1,118 @@
{{define "collection"}}<!DOCTYPE HTML>
<html>
<head prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#">
<meta charset="utf-8">
<title>Archive &mdash; {{.Collection.DisplayTitle}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="canonical" href="{{.CanonicalURL}}">
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">{{end}}
{{if lt .CurrentPage .TotalPages}}<link rel="next" href="{{.NextPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">{{end}}
{{if not .IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.DisplayTitle}} &raquo; Feed" href="{{.CanonicalURL}}feed/" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content="WriteFreely">
<meta name="description" content="{{.PlainDescription}}">
<meta itemprop="name" content="{{.DisplayTitle}}">
<meta itemprop="description" content="{{.PlainDescription}}">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{.DisplayTitle}}">
<meta name="twitter:image" content="{{.AvatarURL}}">
<meta name="twitter:description" content="{{.PlainDescription}}">
<meta property="og:title" content="{{.DisplayTitle}}" />
<meta property="og:site_name" content="{{.DisplayTitle}}" />
<meta property="og:type" content="article" />
<meta property="og:url" content="{{.CanonicalURL}}" />
<meta property="og:description" content="{{.PlainDescription}}" />
<meta property="og:image" content="{{.AvatarURL}}">
{{template "collection-meta" .}}
{{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}}
</head>
<body id="subpage">
<div id="overlay"></div>
<header>
<h1 dir="{{.Direction}}" id="blog-title"><a href="{{if .IsTopLevel}}/{{else}}/{{.Collection.Alias}}/{{end}}" class="h-card p-author">{{.Collection.DisplayTitle}}</a></h1>
<nav>
{{if .PinnedPosts}}
{{range .PinnedPosts}}<a class="pinned" href="{{if $.IsOwner}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL}}{{end}}">{{.DisplayTitle}}</a>{{end}}
{{end}}
</nav>
</header>
{{if .Posts -}}
<section id="wrapper" class="archive" itemscope itemtype="http://schema.org/Blog">
{{- else -}}
<div id="wrapper" class="archive">
{{- end}}
<h1>Archive</h1>
{{if .Flash}}
<div class="alert success flash">
<p>{{.Flash}}</p>
</div>
{{end}}
<ul>
{{ $curYear := 0 }}
{{ range $el := .Posts }}
{{if ne $curYear .Created.Year}}<li class="year">{{.Created.Year}}</li>{{ $curYear = .Created.Year }}{{end}}
<li>
{{if .HasTitleLink -}}
{{.HTMLTitleArrow}}
{{- else -}}
<a href="{{if $.SingleUser}}/{{else}}/{{$.Alias}}/{{end}}{{.Slug.String}}" itemprop="url" class="u-url">
{{- if .DisplayTitle -}}
{{- .DisplayTitle -}}
{{- else -}}
(Untitled)
{{end}}
</a>
{{- end}}
{{if .IsScheduled}}[Scheduled]{{end}}
{{if $.Format.ShowDates -}}
<time class="dt-published" datetime="{{.Created8601}}" pubdate itemprop="datePublished" content="{{.Created}}">
{{- if .HasTitleLink -}}
<a href="{{if $.SingleUser}}/{{else}}/{{$.Alias}}/{{end}}{{.Slug.String}}" itemprop="url">
{{- end -}}
{{.DisplayDate}}
{{- if .HasTitleLink -}}
{{- if .IsPaid}}{{template "paid-badge" (dict "CDNHost" $.CDNHost)}}{{end -}}</a>
{{- end -}}
</time>
{{- else -}}
{{- if .HasTitleLink -}}
<a href="{{if $.SingleUser}}/{{else}}/{{$.Alias}}/{{end}}{{.Slug.String}}" itemprop="url">view</a>
{{- end -}}
{{- end}}
</li>
{{end}}
</ul>
{{template "paging" .}}
{{if .Posts}}</section>{{else}}</div>{{end}}
{{if .ShowFooterBranding }}
<footer>
<hr />
<nav dir="ltr">
{{if not .SingleUser}}<a class="home pubd" href="/">{{.SiteName}}</a> &middot; {{end}}powered by <a style="margin-left:0" href="https://writefreely.org">writefreely</a>
</nav>
</footer>
{{ end }}
</body>
{{if .CanShowScript}}
{{range .ExternalScripts}}<script type="text/javascript" src="{{.}}" async></script>{{end}}
{{if .Collection.Script}}<script type="text/javascript">{{.ScriptDisplay}}</script>{{end}}
{{end}}
<script src="/js/localdate.js"></script>
</html>{{end}}

View file

@ -55,7 +55,8 @@
{{range .PinnedPosts}}<a class="pinned{{if eq .Slug.String $.Slug.String}} selected{{end}}" href="{{if not $.SingleUser}}/{{$.Collection.Alias}}/{{.Slug.String}}{{else}}{{.CanonicalURL $.Host}}{{end}}">{{.PlainDisplayTitle}}</a>{{end}}
{{end}}
{{ if and .IsOwner .IsFound }}<span class="views" dir="ltr"><strong>{{largeNumFmt .Views}}</strong> {{pluralize "view" "views" .Views}}</span>
<a class="xtra-feature" href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a>
{{if .Likes}}<span class="views" dir="ltr"><strong>{{largeNumFmt .Likes}}</strong> {{pluralize "like" "likes" .Likes}}</span>{{end}}
<a href="/{{if not .SingleUser}}{{.Collection.Alias}}/{{end}}{{.Slug.String}}/edit" dir="{{.Direction}}">Edit</a>
{{if .IsPinned}}<a class="xtra-feature unpin" href="/{{.Collection.Alias}}/{{.Slug.String}}/unpin" dir="{{.Direction}}" onclick="unpinPost(event, '{{.ID}}')">Unpin</a>{{end}}
{{ end }}
</nav>

View file

@ -8,6 +8,7 @@
<a href="/about">about</a>
{{if and .LocalTimeline .CanViewReader}}<a href="/read">reader</a>{{end}}
{{if .Username}}<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>{{end}}
<a href="/contact">contact</a>
<a href="/privacy">privacy</a>
<p style="font-size: 0.9em">powered by <a href="https://writefreely.org">writefreely</a></p>
{{else}}
@ -25,6 +26,7 @@
<ul>
<li><a href="/about">about</a></li>
{{if and (and (not .SingleUser) .LocalTimeline) .CanViewReader}}<a href="/read">reader</a>{{end}}
<li><a href="/contact">contact</a></li>
<li><a href="/privacy">privacy</a></li>
</ul>
</div>

View file

@ -62,3 +62,15 @@
{{ end }}
{{define "paid-badge"}}<img class="paid" alt="Paid article" src="/img/paidarticle.svg" /> {{end}}
{{define "paging"}}
{{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix">
{{if or (and .Format.Ascending (le .CurrentPage .TotalPages)) (isRTL .Direction)}}
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">&#8672; {{if and .Format.Ascending (le .CurrentPage .TotalPages)}}Previous{{else}}Newer{{end}}</a>{{end}}
{{if lt .CurrentPage .TotalPages}}<a style="float:right;" href="{{.NextPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">{{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Next{{else}}Older{{end}} &#8674;</a>{{end}}
{{else}}
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">&#8672; Older</a>{{end}}
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .Prefix .NavSuffix .CurrentPage .IsTopLevel}}">Newer &#8674;</a>{{end}}
{{end}}
</nav>{{end}}
{{end}}

View file

@ -14,7 +14,7 @@
<div id="overlay"></div>
<textarea id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
<textarea dir="auto" id="writer" placeholder="Write..." class="{{.Post.Font}}" autofocus>{{if .Post.Title}}# {{.Post.Title}}
{{end}}{{.Post.Content}}</textarea>

View file

@ -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<data.data.length; i++) {
$posts.el.appendChild(createPostEl(data.data[i], true));
$posts.el.appendChild(createPostEl(data.data[i], true, singleUser));
}
if (data.data.length < 10) {
$loadMore.el.parentNode.removeChild($loadMore.el);

View file

@ -11,6 +11,7 @@
{{if not .SingleUser}}<a href="/about">about</a>{{end}}
{{if and (not .SingleUser) .LocalTimeline}}<a href="/read">reader</a>{{end}}
<a href="https://writefreely.org/guide/{{.OfficialVersion}}" target="guide">writer's guide</a>
{{if not .SingleUser}}<a href="/contact">contact</a>{{end}}
{{if not .SingleUser}}<a href="/privacy">privacy</a>{{end}}
{{if .WFModesty}}
<p style="font-size: 0.9em">powered by <a href="https://writefreely.org">writefreely</a></p>

View file

@ -51,11 +51,13 @@ td.none {
<th>Post</th>
{{if not .Collection}}<th>Blog</th>{{end}}
<th class="num">Total Views</th>
{{if .Federation}}<th class="num">Likes</th>{{end}}
</tr>
{{range .TopPosts}}<tr>
<td style="word-break: break-all;"><a href="{{if .Collection}}{{.Collection.CanonicalURL}}{{.Slug.String}}{{else}}/{{.ID}}{{end}}">{{if ne .DisplayTitle ""}}{{.DisplayTitle}}{{else}}<em>{{.ID}}</em>{{end}}</a></td>
{{ if not $.Collection }}<td>{{if .Collection}}<a href="{{.Collection.CanonicalURL}}">{{.Collection.Title}}</a>{{else}}<em>Draft</em>{{end}}</td>{{ end }}
<td class="num">{{.ViewCount}}</td>
{{if $.Federation}}<td class="num">{{.LikeCount}}</td>{{end}}
</tr>{{end}}
</table>