Compare commits

...

1553 Commits

Author SHA1 Message Date
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 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] 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
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
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 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 289730e24a Look for custom CSS in static_parent_dir
Previously, it would only check the current directory instead of using the configured
`static_parent_dir`. This fixes that.

Closes #792
2023-11-07 09:06:50 -05:00
Matt Baer a1becfdc83
Merge pull request #799 from heyakyra/twitter-card-fix-large-preview
Conditionally use twitter large summary card format when an image is available
2023-11-06 16:31:10 -05:00
Matt Baer 0bf0b425ee
Merge pull request #811 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.26
Bump github.com/microcosm-cc/bluemonday from 1.0.25 to 1.0.26
2023-11-02 13:40:15 -04:00
dependabot[bot] 10994c532f
Bump github.com/microcosm-cc/bluemonday from 1.0.25 to 1.0.26
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.25 to 1.0.26.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.25...v1.0.26)

---
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>
2023-11-02 17:15:20 +00:00
Matt Baer ae70c2dbe4
Merge pull request #810 from writefreely/dependabot/go_modules/golang.org/x/net-0.17.0
Bump golang.org/x/net from 0.15.0 to 0.17.0
2023-11-02 13:14:39 -04:00
dependabot[bot] cdb1ffd1da
Bump golang.org/x/net from 0.15.0 to 0.17.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.15.0 to 0.17.0.
- [Commits](https://github.com/golang/net/compare/v0.15.0...v0.17.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>
2023-11-02 17:12:54 +00:00
Matt Baer d467fdf158
Merge pull request #809 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.14.0
Bump golang.org/x/crypto from 0.13.0 to 0.14.0
2023-11-02 13:11:36 -04:00
dependabot[bot] 643d025381
Bump golang.org/x/crypto from 0.13.0 to 0.14.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.13.0 to 0.14.0.
- [Commits](https://github.com/golang/crypto/compare/v0.13.0...v0.14.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>
2023-11-01 22:45:31 +00:00
Kyra ee485e0488 Conditionally use twitter large summary card format when an image is available. 2023-10-25 17:15:05 -05:00
Matt Baer 5204b3b752
Merge pull request #782 from writefreely/verify-collection-max-lengths
Prevent 500 errors on too-long collection title or description
2023-10-23 12:50:06 -04:00
Matt Baer 45ca9c4c2b
Merge pull request #781 from writefreely/fix-updates-masto
Ensure Update activities work with Mastodon
2023-10-23 12:49:30 -04:00
Matt Baer 71fd25870d
Merge pull request #793 from writefreely/fix-fedi-followers
Add missing methods for showing fediverse followers
2023-10-23 12:48:32 -04:00
Matt Baer dd797c8145 Add missing methods for showing fediverse followers
Fixes #791
2023-10-13 16:45:12 -04:00
Matt Baer 3870749e5e
Merge pull request #785 from blujan/develop
Fix use of NOW() when getting tagged posts
2023-10-11 10:55:51 -04:00
Brennan Lujan 87b3585c44 Fix use of NOW() when getting tagged posts 2023-10-06 20:20:40 -07: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
Matt Baer 4aad0338bf
Merge pull request #779 from writefreely/fix-ld-json-response-2
Correctly respond to application/ld+json requests, part 2
2023-10-03 12:06:10 -04:00
Matt Baer 711cb387a5
Merge pull request #778 from writefreely/better-indexing
Add index to improve post retrieval speed on large instances
2023-10-03 12:04:20 -04:00
Matt Baer e3323d11c8
Merge pull request #777 from writefreely/reset-password
Support resetting password via email

Closes T508
2023-10-03 12:03:08 -04:00
Matt Baer 076c4ae2f2 Set blog title maxlength on Customize page 2023-10-03 11:57:42 -04:00
Matt Baer 530a36fc53 Prevent 500 errors on too-long collection title or description
This truncates long titles and descriptions to the maximum column length, so
we don't get errors back from MySQL.

Fixes #600
2023-10-03 11:55:52 -04:00
Matt Baer 8207a25fa9 Tweak style of "Forgot" link on login page 2023-10-03 11:39:41 -04:00
Matt Baer 7b84dafea7 Correctly return on /reset submission when email isn't configured 2023-10-03 11:28:24 -04:00
Matt Baer ed60aea39e Catch and log emailPasswordReset errors 2023-10-03 11:25:05 -04:00
Matt Baer 8f02449ee8 Show friendly message on /reset when password-based login is disabled 2023-10-03 11:19:47 -04:00
Matt Baer 1e37f60d50 Hide "Reset?" link on login page when email disabled 2023-10-03 11:16:11 -04:00
Matt Baer c18987705c Display friendly message on /reset if email is disabled 2023-10-03 11:15:33 -04:00
Matt Baer 7db4b699e2
Merge pull request #776 from writefreely/passwordless-login
Plumbing: login via emailed link

Ref T731
2023-10-03 11:02:30 -04:00
Matt Baer 26ba79ff02
Merge pull request #775 from writefreely/subscriber-insights
Add Subscribers page

Closes T826
2023-10-03 10:59:21 -04:00
Matt Baer b232e7efd7 Fix indentation in subscribers.tmpl 2023-10-03 10:56:23 -04:00
Matt Baer 64dcb56793
Merge pull request #478 from writefreely/letters
Support email subscriptions
2023-10-03 10:50:34 -04:00
Matt Baer 273267343a Ensure Updated property can be omitted
Now, the web-core pkg uses a pointer instead of a var, so we don't send
a zero time.Time value out via ActivityPub.
2023-10-02 21:35:23 -04:00
Matt Baer 27e82f0409
Merge pull request #774 from writefreely/fix-no-fonts
Fix fonts not getting applied on first load
2023-10-02 19:43:50 -04:00
Matt Baer 167971771e Send `updated` parameter with `Update` activities
Per the Mastodon docs, this ensures the activity correctly updates posts there.
https://docs.joinmastodon.org/spec/activitypub/#supported-activities-for-statuses
2023-10-02 19:33:03 -04:00
Matt Baer 2275a288b9 Correctly respond to application/ld+json requests, part 2
This finishes the work started in #766, ensuring that requests to
canonical URLs of blogs and posts (not just at their API endpoints)
respond correctly to `application/ld+json;...` requests.

Fully addresses issue #564
2023-09-26 14:46:35 -04:00
Matt Baer f96f8268f0 Add index to improve post retrieval speed on large instances
On an instance with millions of posts across all users, a single blog with
thousands of posts on it can take a long time to render. This adds an index
to the `posts` table to speed up the basic GetPosts query.

Run: `writefreely db migrate`

Closes #741
2023-09-26 14:36:34 -04:00
Matt Baer 74f3ded250
Merge pull request #545 from clarfonthey/editorconfig
Add editorconfig
2023-09-26 11:52:43 -04:00
Matt Baer c1609cdb90
Merge pull request #658 from jsoref/spelling
Spelling
2023-09-26 11:50:19 -04:00
Matt Baer e96e657430 Fix copyright notices with wrong company name 2023-09-25 19:07:06 -04:00
Matt Baer f404f7b928 Support resetting password via email
This adds a self-serve password reset page. Users can enter their username
and receive an email with a link that will let them create a new password.
If they've never set a password, it will send them a one-time login link
(building on #776) that will then take them to their Account Settings page.
If they don't have an email associated with their account, they'll be
instructed to contact the admin, so they can manually reset the password.

Includes changes to the stylesheet and database, so run:

    make ui
    writefreely db migrate

Closes T508
2023-09-25 18:48:14 -04:00
Matt Baer 7dda53146d Add function for logging in via emailed link
This doesn't add any user-facing behavior, but provides the basic functionality
to generate a one-time use token and email it to a user, so they can log in with
a link instead of a password.
2023-09-25 18:21:20 -04:00
Matt Baer e2fde518ca Fix GetTemporaryOneTimeAccessToken query for SQLite 2023-09-25 18:18:01 -04:00
Matt Baer c75507ca8f Add Subscribers navigation for single-user instances
Ref T826
2023-09-25 17:04:08 -04:00
Matt Baer 82e7dcd3f3 Add Subscribers page
- Shows all fediverse followers and email subscribers
- Shows number of email subscribers on Stats page
- Links to Subscribers page from Stats page

Requires running `make ui` to regenerate stylesheet.

Ref T826
2023-09-25 16:55:57 -04:00
Matt Baer 361c887e2c Revert "use font-display:optional to optimize web font loading"
This reverts commit 059f0d4c54.
2023-09-25 15:58:55 -04:00
Matt Baer 13ca890709
Merge pull request #768 from writefreely/dependabot/go_modules/github.com/writeas/web-core-1.6.0
Bump github.com/writeas/web-core from 1.5.0 to 1.6.0
2023-09-25 15:52:35 -04:00
Matt Baer c6323dba8c Clean up SQLite to-do 2023-09-25 15:38:57 -04:00
Matt Baer dcc6f036c6 Clean up commented-out code 2023-09-25 15:31:31 -04:00
Matt Baer d7d44cb4e1 Catch subscription confirmation email errors 2023-09-25 15:31:10 -04:00
Matt Baer 2a496bd000 Fix subscriber created query for SQLite 2023-09-25 15:30:39 -04:00
Matt Baer 15047b7288 Fix jobs query in SQLite 2023-09-25 15:30:05 -04:00
Matt Baer d1afa44a2e Use standard SetCollectionAttribute method for saving email sub settings 2023-09-25 15:29:23 -04:00
Matt Baer ac40b2f733 Fix publishjobs `id` column in SQLite
Previously, didn't auto-increment or populate
2023-09-25 14:51:28 -04:00
Matt Baer e2b2ba4577 Rename Letters config to Email in collection.tmpl 2023-09-25 14:28:37 -04:00
Matt Baer cc75be1eb5 Rename Letters [letters] config section to Email [email] 2023-09-25 14:26:41 -04:00
Matt Baer 221d0d7dbb Make letters (v13) migration compatible with SQLite 2023-09-25 14:25:24 -04:00
Matt Baer cc9705447d Re-add letters migration 2023-09-25 14:00:18 -04:00
Matt Baer 06968e7341 Merge branch 'develop' into letters 2023-09-25 13:59:46 -04:00
Matt Baer 62f9b2948e Exclude local static files from release build 2023-09-22 17:10:42 -04:00
Matt Baer a8afa18ab2 Bump version to 0.14 2023-09-22 13:12:47 -04:00
Matt Baer b291b89904
Merge pull request #772 from writefreely/better-server-error
Instruct users to contact admin, not WF developers on 500 page
2023-09-22 13:08:20 -04:00
Matt Baer 96eb800eaa
Merge pull request #730 from testwill/loop
chore: slice replace loop
2023-09-22 13:01:36 -04:00
Matt Baer 36f4e30595
Merge pull request #729 from testwill/fmt
chore: unnecessary use of fmt.Sprintf
2023-09-22 13:01:15 -04:00
Matt Baer 177cbf2e57
Merge pull request #728 from testwill/ioutil
chore: remove refs to deprecated io/ioutil
2023-09-22 13:00:41 -04:00
Matt Baer 334d499fb3
Merge pull request #508 from writefreely/lang-posts-filter
Support filtering blog posts by language

Closes T805
2023-09-22 12:53:20 -04:00
Matt Baer 322d0d618a
Merge pull request #771 from writefreely/verification
Support rel=me verification on blogs
2023-09-22 12:47:01 -04:00
Matt Baer c9dc8d5a90 Fix bad copy pasta 2023-09-22 12:46:21 -04:00
Matt Baer d48262a6df Add a customizable Contact page 2023-09-22 12:37:15 -04:00
Matt Baer 83f230ddaf Instruct users to contact admin, not WF devevelopers on 500 page
Misconfigured or broken servers has directed people to the wrong place.

Fixes #684
2023-09-22 12:25:19 -04:00
Matt Baer efe669b874 Remove redundant query on post pages
Previously, we'd call GetCollectionAttribute for the monetization
attribute, when it's already in the collection data.
2023-09-22 11:58:11 -04:00
Matt Baer aa72bcba50 Fix funky comment after gofmt in posts.go 2023-09-22 11:53:57 -04:00
Matt Baer 8626aa12cc Fix post page rendering after rel=me changes
Ref T744
2023-09-22 11:52:14 -04:00
Matt Baer 264bef03b1 Support rel=me verification on blogs
This allows setting a URL, and then renders a <link> element
in the head of the blog. It requires a database migration.

Ref T744
2023-09-21 19:04:34 -04:00
Matt Baer e0c165ff1e Ensure SetCollectionAttribute also updates attributes
Previously, it would only INSERT.
2023-09-21 18:55:48 -04:00
Matt Baer 2986f83121
Merge pull request #770 from writefreely/passwordless-db
Support using MySQL without a password
2023-09-21 18:16:25 -04:00
Matt Baer 3d8b8ecc93 Support using MySQL without a password
Fixes #568
2023-09-21 17:18:15 -04:00
Matt Baer 5d4ebb59c7
Merge pull request #769 from writefreely/fix-mau
Fix monthly active user stats
2023-09-21 17:14:53 -04:00
Matt Baer 2b5318e7a6 Log any database errors when fetching stats
Previously, these errors were simply ignored
2023-09-21 17:08:57 -04:00
Matt Baer baf1d76475 Fix monthly active user stats query 2023-09-21 17:08:17 -04:00
Matt Baer 94bb566e4f
Merge pull request #766 from writefreely/fix-ld-json-response
Correctly respond to application/ld+json requests
2023-09-21 17:03:18 -04:00
Matt Baer d3f312a1e2 Use Chorus template (if configured) when filtering by language
Ref T805
2023-09-21 16:59:32 -04:00
Matt Baer ebeb45ac5a Support pagination when viewing language-filtered posts
Ref T805
2023-09-21 16:58:44 -04:00
Matt Baer 3dc515c249 Merge branch 'develop' into lang-posts-filter 2023-09-21 16:38:48 -04:00
dependabot[bot] 10a415a7ec
Bump github.com/writeas/web-core from 1.5.0 to 1.6.0
Bumps [github.com/writeas/web-core](https://github.com/writeas/web-core) from 1.5.0 to 1.6.0.
- [Commits](https://github.com/writeas/web-core/compare/v1.5.0...v1.6.0)

---
updated-dependencies:
- dependency-name: github.com/writeas/web-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-21 20:36:05 +00:00
Matt Baer a8c5468f65
Merge pull request #767 from writefreely/update-monday
Update writeas/monday to v1.3.0
2023-09-21 16:35:27 -04:00
Matt Baer 43ba111e21 Update writeas/moday to v1.3.0 2023-09-21 16:33:58 -04:00
Matt Baer 299686c13e
Merge pull request #482 from writefreely/rel-nofollow
Remove rel=nofollow attribute in certain situations
2023-09-21 16:26:17 -04:00
Matt Baer dff01a6136
Merge pull request #438 from Dak425/paginate-tag-collection
Add pagination to tag subpages
2023-09-21 16:25:05 -04:00
Matt Baer 8f03da0ec1 Correctly respond to application/ld+json requests
This returns ActivityStreams objects when the Accept header is
`application/ld+json; profile="https://www.w3.org/ns/activitystreams"`,
per the ActivityPub spec.

Fixes #564
2023-09-21 16:16:57 -04:00
Matt Baer 142c5d6cec Re-add ossl_legacy.cnf 2023-09-21 16:07:09 -04:00
Matt Baer 526db318c4 Merge branch 'develop' into letters 2023-09-21 16:03:13 -04:00
Matt Baer fe1f821422
Merge pull request #765 from writefreely/remove-coil
Remove mention of Coil on Customize page
2023-09-19 14:00:48 -04:00
Matt Baer 2fde648519 Remove mention of Coil on Customize page
Fixes #720
2023-09-19 13:41:52 -04:00
Matt Baer 3e21ecb53c
Merge pull request #760 from writefreely/dependabot/github_actions/docker/login-action-3.0.0
Bump docker/login-action from 2.2.0 to 3.0.0
2023-09-19 13:27:59 -04:00
Matt Baer 3ba29aaa2c
Merge pull request #759 from writefreely/dependabot/github_actions/docker/build-push-action-5.0.0
Bump docker/build-push-action from 4.1.1 to 5.0.0
2023-09-19 13:26:15 -04:00
dependabot[bot] c60d135060
Bump docker/login-action from 2.2.0 to 3.0.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.2.0...v3.0.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 22:25:40 +00:00
dependabot[bot] 4c48733a3a
Bump docker/build-push-action from 4.1.1 to 5.0.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4.1.1 to 5.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v4.1.1...v5.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-12 22:25:37 +00:00
Matt Baer f2474798bb
Merge pull request #750 from writefreely/dependabot/github_actions/actions/checkout-4
Bump actions/checkout from 3 to 4
2023-09-07 16:19:37 -04:00
Matt Baer 9c9fa8bf62
Merge pull request #753 from writefreely/dependabot/go_modules/golang.org/x/net-0.15.0
Bump golang.org/x/net from 0.13.0 to 0.15.0
2023-09-07 16:18:57 -04:00
dependabot[bot] 3981b6dddb
Bump golang.org/x/net from 0.13.0 to 0.15.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.13.0 to 0.15.0.
- [Commits](https://github.com/golang/net/compare/v0.13.0...v0.15.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>
2023-09-07 20:17:51 +00:00
Matt Baer da3e5d0606
Merge pull request #754 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.13.0
Bump golang.org/x/crypto from 0.12.0 to 0.13.0
2023-09-07 16:16:53 -04:00
Matt Baer 51c46621d8
Merge pull request #752 from lstellway/lstellway/connect-package-to-repo
Adds image labels to connect package with repository
2023-09-07 16:16:23 -04:00
dependabot[bot] 21a1c738d1
Bump golang.org/x/crypto from 0.12.0 to 0.13.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.12.0 to 0.13.0.
- [Commits](https://github.com/golang/crypto/compare/v0.12.0...v0.13.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>
2023-09-07 20:15:51 +00:00
Matt Baer 0814ec28dc
Merge pull request #749 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.12.0
Bump golang.org/x/crypto from 0.11.0 to 0.12.0
2023-09-07 16:14:52 -04:00
lstellway c7729a0432
Adds image labels to connect package with repository 2023-09-07 12:46:45 -07:00
dependabot[bot] a408f0f9ea
Bump actions/checkout from 3 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-09-04 22:05:03 +00:00
dependabot[bot] e9b03c9350
Bump golang.org/x/crypto from 0.11.0 to 0.12.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.11.0 to 0.12.0.
- [Commits](https://github.com/golang/crypto/compare/v0.11.0...v0.12.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>
2023-09-01 22:47:59 +00:00
Matt Baer 65ec6b44e1
Merge pull request #738 from writefreely/dependabot/go_modules/golang.org/x/net-0.13.0
Bump golang.org/x/net from 0.11.0 to 0.13.0
2023-08-02 11:12:26 -04:00
dependabot[bot] 21efde71f7
Bump golang.org/x/net from 0.11.0 to 0.13.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.11.0 to 0.13.0.
- [Commits](https://github.com/golang/net/compare/v0.11.0...v0.13.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>
2023-08-02 15:10:36 +00:00
Matt Baer 8755f1706c
Merge pull request #736 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.25
Bump github.com/microcosm-cc/bluemonday from 1.0.24 to 1.0.25
2023-08-02 11:09:24 -04:00
dependabot[bot] 41138e4ab2
Bump github.com/microcosm-cc/bluemonday from 1.0.24 to 1.0.25
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.24 to 1.0.25.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.24...v1.0.25)

---
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>
2023-08-02 15:06:48 +00:00
Matt Baer 0860d1db1f
Merge pull request #737 from writefreely/dependabot/go_modules/github.com/writeas/web-core-1.5.0
Bump github.com/writeas/web-core from 1.4.1 to 1.5.0
2023-08-02 11:06:04 -04:00
dependabot[bot] b54de10663
Bump github.com/writeas/web-core from 1.4.1 to 1.5.0
Bumps [github.com/writeas/web-core](https://github.com/writeas/web-core) from 1.4.1 to 1.5.0.
- [Commits](https://github.com/writeas/web-core/compare/v1.4.1...v1.5.0)

---
updated-dependencies:
- dependency-name: github.com/writeas/web-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-08-01 22:57:27 +00:00
guoguangwu 78e59b749b chore: slice replace loop 2023-07-11 12:17:34 +08:00
guoguangwu 20fec65e6b chore: unnecessary use of fmt.Sprintf 2023-07-11 12:10:40 +08:00
guoguangwu cf53730f6c chore: remove refs to deprecated io/ioutil 2023-07-10 17:55:04 +08:00
Matt Baer dbdbcfd100
Merge pull request #618 from Timshel/token
Widen oauth_users.access_token to prevent insertion failure
2023-07-08 00:39:58 -04:00
Matt Baer 54eb2db14d Fix tagged posts falsely showing Older link 2023-07-08 00:31:02 -04:00
Matt Baer e65086b635 Merge branch 'develop' into paginate-tag-collection 2023-07-08 00:01:40 -04:00
Matt Baer b753d41964
Merge pull request #521 from eli-oat/develop
Update 404 error message
2023-07-07 23:57:55 -04:00
Matt Baer 5d5a8536c8
Merge pull request #638 from zer-far/dev
Strip debugging information
2023-07-07 23:51:46 -04:00
Matt Baer 9580cffb3d
Merge pull request #556 from writefreely/dependabot/go_modules/github.com/guregu/null-4.0.0incompatible
Bump github.com/guregu/null from 3.5.0+incompatible to 4.0.0+incompatible
2023-06-30 18:11:44 -04:00
Matt Baer 1aee7ed125
Merge pull request #723 from writefreely/dependabot/go_modules/github.com/urfave/cli/v2-2.25.7
Bump github.com/urfave/cli/v2 from 2.23.5 to 2.25.7
2023-06-30 18:08:31 -04:00
Matt Baer 989d7eb2fc
Merge pull request #705 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.24
Bump github.com/microcosm-cc/bluemonday from 1.0.23 to 1.0.24
2023-06-30 18:07:52 -04:00
dependabot[bot] ba8aebaa6f
Bump github.com/microcosm-cc/bluemonday from 1.0.23 to 1.0.24
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.23 to 1.0.24.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.23...v1.0.24)

---
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>
2023-06-30 22:05:07 +00:00
Matt Baer 949f13bf66
Merge pull request #721 from writefreely/dependabot/go_modules/golang.org/x/net-0.11.0
Bump golang.org/x/net from 0.9.0 to 0.11.0
2023-06-30 18:03:47 -04:00
Matt Baer f92f7b13cb
Merge pull request #692 from writefreely/dependabot/go_modules/github.com/go-sql-driver/mysql-1.7.1
Bump github.com/go-sql-driver/mysql from 1.7.0 to 1.7.1
2023-06-30 18:03:20 -04:00
dependabot[bot] 98790ee371
Bump github.com/urfave/cli/v2 from 2.23.5 to 2.25.7
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.23.5 to 2.25.7.
- [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.23.5...v2.25.7)

---
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>
2023-06-30 21:57:55 +00:00
Matt Baer a9733c30cf
Merge pull request #708 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.17
Bump github.com/mattn/go-sqlite3 from 1.14.16 to 1.14.17
2023-06-30 17:57:48 -04:00
dependabot[bot] d3f935f693
Bump golang.org/x/net from 0.9.0 to 0.11.0
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.9.0 to 0.11.0.
- [Commits](https://github.com/golang/net/compare/v0.9.0...v0.11.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>
2023-06-30 21:57:43 +00:00
Matt Baer 3eb3146ae9
Merge pull request #709 from writefreely/dependabot/go_modules/github.com/stretchr/testify-1.8.4
Bump github.com/stretchr/testify from 1.8.1 to 1.8.4
2023-06-30 17:57:13 -04:00
Matt Baer 229607a5ab
Merge pull request #711 from writefreely/dependabot/github_actions/docker/login-action-2.2.0
Bump docker/login-action from 2.1.0 to 2.2.0
2023-06-30 17:56:23 -04:00
Matt Baer d476c3b2f7
Merge pull request #716 from writefreely/dependabot/github_actions/docker/metadata-action-4.6.0
Bump docker/metadata-action from 4.1.1 to 4.6.0
2023-06-30 17:55:59 -04:00
Matt Baer 6946d3b785
Merge pull request #717 from writefreely/dependabot/github_actions/docker/build-push-action-4.1.1
Bump docker/build-push-action from 3.2.0 to 4.1.1
2023-06-30 17:53:48 -04:00
Matt Baer e0372979d9
Merge pull request #691 from writefreely/netgo
Use Go DNS resolution in release binaries
2023-06-30 17:53:10 -04:00
dependabot[bot] 639770be4d
Bump docker/build-push-action from 3.2.0 to 4.1.1
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 4.1.1.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.2.0...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 23:00:23 +00:00
dependabot[bot] b0b166e827
Bump docker/metadata-action from 4.1.1 to 4.6.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.1.1 to 4.6.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4.1.1...v4.6.0)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-13 23:00:19 +00:00
dependabot[bot] e2237653bb
Bump docker/login-action from 2.1.0 to 2.2.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.1.0...v2.2.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-07 23:00:05 +00:00
dependabot[bot] 77823a382b
Bump github.com/stretchr/testify from 1.8.1 to 1.8.4
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.4.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-01 22:58:42 +00:00
dependabot[bot] b6d17a9594
Bump github.com/mattn/go-sqlite3 from 1.14.16 to 1.14.17
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.16 to 1.14.17.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.16...v1.14.17)

---
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>
2023-06-01 22:58:34 +00:00
dependabot[bot] e1e05e5f29
Bump github.com/go-sql-driver/mysql from 1.7.0 to 1.7.1
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.7.0 to 1.7.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.0...v1.7.1)

---
updated-dependencies:
- dependency-name: github.com/go-sql-driver/mysql
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-28 14:20:58 +00:00
Matt Baer 67dbc9b22b
Merge pull request #617 from writefreely/dependabot/go_modules/github.com/go-sql-driver/mysql-1.7.0
Bump github.com/go-sql-driver/mysql from 1.6.0 to 1.7.0
2023-04-28 10:11:32 -04:00
Matt Baer 3f5fd6e2d2
Merge pull request #644 from writefreely/dependabot/go_modules/github.com/dustin/go-humanize-1.0.1
Bump github.com/dustin/go-humanize from 1.0.0 to 1.0.1
2023-04-28 10:10:12 -04:00
Matt Baer 3a7554abe8 Use Go DNS resolution in release binaries
This builds with the `netgo` tag, ensuring WF binaries use Go DNS
resolution instead of libc, preventing unhelpful errors when the
application can't resolve addresses.

Closes #675
2023-04-28 10:07:00 -04:00
Matt Baer e350b7ce8a
Merge pull request #597 from ilteriseroglu-ty/fix-xbuilds
fix Makefile and Dockerfile to build on latest go versions
2023-04-07 12:14:07 -04:00
Matt Baer 1a61128dfc
Merge pull request #621 from writefreely/filepath-errors
Catch and output directory walking errors
2023-04-07 11:47:13 -04:00
Matt Baer ddabab041a
Merge pull request #543 from clarfonthey/unix-support
Add unix socket support
2023-04-07 11:46:08 -04:00
Matt Baer 2ba840634b
Merge pull request #660 from writefreely/fix-alt-text
Loosen restrictions on rendered img alt attribute
2023-04-07 11:26:43 -04:00
Matt Baer ac9c53cfff
Merge pull request #666 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.23
Bump github.com/microcosm-cc/bluemonday from 1.0.21 to 1.0.23
2023-04-07 11:25:57 -04:00
dependabot[bot] 1a4845aca8
Bump github.com/microcosm-cc/bluemonday from 1.0.21 to 1.0.23
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.21 to 1.0.23.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.21...v1.0.23)

---
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>
2023-04-07 15:24:24 +00:00
Matt Baer 7c0e69cf41
Merge pull request #672 from writefreely/dependabot/go_modules/golang.org/x/crypto-0.8.0
Bump golang.org/x/crypto from 0.0.0-20200622213623-75b288015ac9 to 0.8.0
2023-04-07 11:23:34 -04:00
dependabot[bot] cdaa13a260
Bump github.com/go-sql-driver/mysql from 1.6.0 to 1.7.0
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.6.0 to 1.7.0.
- [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.6.0...v1.7.0)

---
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>
2023-04-07 15:22:18 +00:00
dependabot[bot] 0dcfd1809d
Bump github.com/dustin/go-humanize from 1.0.0 to 1.0.1
Bumps [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/dustin/go-humanize/releases)
- [Commits](https://github.com/dustin/go-humanize/compare/v1.0.0...v1.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-07 15:22:17 +00:00
dependabot[bot] ad6c8f30bc
Bump golang.org/x/crypto from 0.0.0-20200622213623-75b288015ac9 to 0.8.0
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.0.0-20200622213623-75b288015ac9 to 0.8.0.
- [Release notes](https://github.com/golang/crypto/releases)
- [Commits](https://github.com/golang/crypto/commits/v0.8.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>
2023-04-07 15:22:13 +00:00
Matt Baer 86c76b0442
Merge pull request #670 from writefreely/dependabot/go_modules/github.com/fatih/color-1.15.0
Bump github.com/fatih/color from 1.13.0 to 1.15.0
2023-04-07 11:21:31 -04:00
dependabot[bot] 43176ed7ea
Bump github.com/fatih/color from 1.13.0 to 1.15.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.13.0 to 1.15.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.13.0...v1.15.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-04-01 22:59:14 +00:00
Matt Baer 64772aa203 Loosen restrictions on rendered img alt attribute
Previously, certain characters weren't allowed, and they would cause the entire
alt attribute to be elided from the rendered page. Since we safely sanitize the
content of this attribute anyway, this is unnecessary, so we now allow all text
entered there.

Fixes #649
2023-03-14 15:13:03 -04:00
Matt Baer 40b9c08c86
Merge pull request #657 from writefreely/dependabot/go_modules/github.com/writeas/web-core-1.4.1
Bump github.com/writeas/web-core from 1.4.1-0.20220118212728-0da0bcaf018e to 1.4.1
2023-03-07 13:37:34 -05:00
Josh Soref ea81e2c839 spelling: pattern
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:44:02 -05:00
Josh Soref 02fb079a9f spelling: optional
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:44:02 -05:00
Josh Soref 0746ec8567 spelling: modified
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:44:02 -05:00
Josh Soref 7e5d60043d spelling: miscellaneous
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref af875b4d87 spelling: message
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 8dd7b40c02 spelling: javascript
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 8834253502 spelling: into
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 7feea370ed spelling: highlight
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref 680f0d1e20 spelling: github
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref bc53300e33 spelling: dynamic
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
Josh Soref af0927cf5c spelling: consequences
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2023-03-05 02:24:29 -05:00
dependabot[bot] ee665c0c68
Bump github.com/writeas/web-core
Bumps [github.com/writeas/web-core](https://github.com/writeas/web-core) from 1.4.1-0.20220118212728-0da0bcaf018e to 1.4.1.
- [Release notes](https://github.com/writeas/web-core/releases)
- [Commits](https://github.com/writeas/web-core/commits/v1.4.1)

---
updated-dependencies:
- dependency-name: github.com/writeas/web-core
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-01 23:00:16 +00:00
Abdullah 83765d5cbc
Update Makefile 2023-01-31 21:27:56 +00:00
İlteriş Yağıztegin Eroğlu 77cc1cc816 fix Makefile and Dockerfile to build on latest go versions
Signed-off-by: İlteriş Yağıztegin Eroğlu <ilteris.eroglu@trendyol.com>
2023-01-20 11:36:11 +00:00
Matt Baer 118eb732f4 Merge branch 'develop' into letters 2023-01-08 11:49:57 -05:00
Matt Baer 99d72881cf Catch and output directory walking errors
Previously, app would panic and admins would see unhelpful errors.

This closes #620
2023-01-06 15:34:22 -05:00
Timshel fc5a79a6bc Use TEXT for oauth_users.access_token to prevent insertion failure 2023-01-03 19:32:21 +01:00
Matt Baer a0f1e1821f Delete socket file on server shutdown 2022-12-26 13:20:28 -05:00
Matt Baer f84b4b0f74 Use more idiomatic variable initialization for network/protocol 2022-12-26 13:18:45 -05:00
Matt Baer 7a84d27dca Re-use err variable, instead of creating new error vars 2022-12-26 13:17:56 -05:00
Matt Baer 3e6669828c
Merge pull request #605 from writefreely/dependabot/go_modules/github.com/go-ini/ini-1.67.0
Bump github.com/go-ini/ini from 1.66.4 to 1.67.0
2022-12-25 00:51:59 -05:00
Matt Baer bbcb61bc53
Merge pull request #531 from writefreely/markdown-descriptions
Support Markdown and rel=me links in blog descriptions
2022-12-25 00:48:46 -05:00
Matt Baer 8684ff04a4 Merge branch 'develop' into markdown-descriptions 2022-12-25 00:47:41 -05:00
Matt Baer 93d5fd152d
Merge pull request #610 from lpar/remove-go-bindata
Replace go-bindata with standard go:embed
2022-12-25 00:24:36 -05:00
mathew 6903dd4349 Replace go-bindata with standard go:embed 2022-12-24 17:59:19 -06:00
dependabot[bot] b5021f2b0c
Bump github.com/go-ini/ini from 1.66.4 to 1.67.0
Bumps [github.com/go-ini/ini](https://github.com/go-ini/ini) from 1.66.4 to 1.67.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.66.4...v1.67.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-01 22:02:03 +00:00
Matt Baer 29c898867a
Merge pull request #483 from writefreely/log-gone-user-out
Log user out when authenticated as deleted user
2022-11-14 23:45:33 -05:00
Matt Baer 17614b5e02
Merge pull request #533 from writefreely/custom-css
Support custom instance-level stylesheets with file
2022-11-14 23:43:32 -05:00
Matt Baer 950090c0d7 Fix whitespace in templates/user/include/header.tmpl 2022-11-14 23:42:37 -05:00
Matt Baer 01c920b253 Merge branch 'develop' into custom-css 2022-11-14 23:41:36 -05:00
Matt Baer 4c1678f91e
Merge pull request #537 from dariusk/fix-drop
Prevent dropping of external files onto editor
2022-11-14 23:38:38 -05:00
Matt Baer 4b33c51ece
Merge pull request #540 from writefreely/better-titles-stats
Show post excerpt in stats list when no post title
2022-11-14 23:37:21 -05:00
Matt Baer 99d17e5e97
Merge pull request #596 from writefreely/dependabot/go_modules/github.com/urfave/cli/v2-2.23.5
Bump github.com/urfave/cli/v2 from 2.5.1 to 2.23.5
2022-11-11 03:41:25 -05:00
dependabot[bot] 6347301867
Bump github.com/urfave/cli/v2 from 2.5.1 to 2.23.5
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.5.1 to 2.23.5.
- [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.5.1...v2.23.5)

---
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>
2022-11-11 08:36:55 +00:00
Matt Baer 7f83bb2706
Merge pull request #594 from writefreely/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.16
Bump github.com/mattn/go-sqlite3 from 1.14.6 to 1.14.16
2022-11-11 03:36:14 -05:00
Matt Baer 02383768ed
Merge pull request #591 from writefreely/dependabot/go_modules/github.com/stretchr/testify-1.8.1
Bump github.com/stretchr/testify from 1.7.0 to 1.8.1
2022-11-11 03:35:43 -05:00
Matt Baer f85241e037
Merge pull request #589 from writefreely/dependabot/github_actions/docker/metadata-action-4.1.1
Bump docker/metadata-action from 4.0.1 to 4.1.1
2022-11-11 03:35:05 -05:00
dependabot[bot] a080e51aaa
Bump github.com/mattn/go-sqlite3 from 1.14.6 to 1.14.16
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.6 to 1.14.16.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.6...v1.14.16)

---
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>
2022-11-11 08:34:57 +00:00
Matt Baer 57b12f31c9
Merge pull request #587 from writefreely/dependabot/github_actions/docker/login-action-2.1.0
Bump docker/login-action from 2.0.0 to 2.1.0
2022-11-11 03:34:36 -05:00
Matt Baer c58eedba7d
Merge pull request #586 from writefreely/dependabot/github_actions/docker/build-push-action-3.2.0
Bump docker/build-push-action from 3.0.0 to 3.2.0
2022-11-11 03:34:15 -05:00
Matt Baer 9767910b1f
Merge pull request #560 from writefreely/dependabot/go_modules/github.com/gorilla/csrf-1.7.1
Bump github.com/gorilla/csrf from 1.7.0 to 1.7.1
2022-11-11 03:33:46 -05:00
Matt Baer ac1b947b18
Merge pull request #563 from writefreely/dependabot/go_modules/github.com/fatih/color-1.13.0
Bump github.com/fatih/color from 1.10.0 to 1.13.0
2022-11-11 03:33:09 -05:00
dependabot[bot] a5c80b98e7
Bump github.com/guregu/null
Bumps [github.com/guregu/null](https://github.com/guregu/null) from 3.5.0+incompatible to 4.0.0+incompatible.
- [Release notes](https://github.com/guregu/null/releases)
- [Commits](https://github.com/guregu/null/compare/v3.5.0...v4.0.0)

---
updated-dependencies:
- dependency-name: github.com/guregu/null
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 08:29:20 +00:00
dependabot[bot] 7b5326ada9
Bump github.com/fatih/color from 1.10.0 to 1.13.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.10.0 to 1.13.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.10.0...v1.13.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>
2022-11-11 08:29:06 +00:00
Matt Baer 2c644dd262
Merge pull request #562 from writefreely/dependabot/go_modules/github.com/gorilla/sessions-1.2.1
Bump github.com/gorilla/sessions from 1.2.0 to 1.2.1
2022-11-11 03:28:28 -05:00
Matt Baer 7687341512
Merge pull request #554 from writefreely/dependabot/go_modules/github.com/manifoldco/promptui-0.9.0
Bump github.com/manifoldco/promptui from 0.8.0 to 0.9.0
2022-11-11 03:27:19 -05:00
dependabot[bot] beb964a9f1
Bump github.com/stretchr/testify from 1.7.0 to 1.8.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.7.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.7.0...v1.8.1)

---
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>
2022-11-11 07:58:29 +00:00
dependabot[bot] 42c7e22b98
Bump github.com/gorilla/csrf from 1.7.0 to 1.7.1
Bumps [github.com/gorilla/csrf](https://github.com/gorilla/csrf) from 1.7.0 to 1.7.1.
- [Release notes](https://github.com/gorilla/csrf/releases)
- [Commits](https://github.com/gorilla/csrf/compare/v1.7.0...v1.7.1)

---
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>
2022-11-11 07:58:27 +00:00
dependabot[bot] 4f2b17ddb1
Bump github.com/manifoldco/promptui from 0.8.0 to 0.9.0
Bumps [github.com/manifoldco/promptui](https://github.com/manifoldco/promptui) from 0.8.0 to 0.9.0.
- [Release notes](https://github.com/manifoldco/promptui/releases)
- [Changelog](https://github.com/manifoldco/promptui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/manifoldco/promptui/compare/v0.8.0...v0.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-11 07:58:27 +00:00
Matt Baer 63eb682a60 Update package-lock.json 2022-11-11 02:13:10 -05:00
Matt Baer ccef3bfdc7 Update go.mod to Go 1.19 and tidy
From Go 1.15
2022-11-11 02:12:54 -05:00
Matt Baer 2c44fb780a Upgrade web-core package
This fixes PEM key decoding issues, and federation
2022-11-11 02:11:55 -05:00
Matt Baer f43a3a8bfa
Merge pull request #593 from writefreely/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.21
Bump github.com/microcosm-cc/bluemonday from 1.0.5 to 1.0.21
2022-11-10 23:56:19 -05:00
Matt Baer 61d1537fce
Merge branch 'hotfix-0.13.2' into dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.21 2022-11-10 23:56:04 -05:00
Matt Baer d08f067e9c Change copyright notices to Musing Studio LLC
A Bunch Tell is now Musing Studio.
2022-11-10 23:49:16 -05:00
dependabot[bot] 3696483d91
Bump github.com/microcosm-cc/bluemonday from 1.0.5 to 1.0.21
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.5 to 1.0.21.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.5...v1.0.21)

---
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>
2022-11-01 22:07:53 +00:00
dependabot[bot] 11266dd87e
Bump docker/metadata-action from 4.0.1 to 4.1.1
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.0.1 to 4.1.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v4.0.1...v4.1.1)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-17 22:39:21 +00:00
dependabot[bot] de0c1085b4
Bump docker/login-action from 2.0.0 to 2.1.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 22:39:28 +00:00
dependabot[bot] 2cf7693a8e
Bump docker/build-push-action from 3.0.0 to 3.2.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.0.0 to 3.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.0.0...v3.2.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 22:39:26 +00:00
Matt Baer b74fd70ab5 Bump version to v0.13.2 2022-07-22 00:22:26 -04:00
dependabot[bot] 915351c4af
Bump github.com/gorilla/sessions from 1.2.0 to 1.2.1
Bumps [github.com/gorilla/sessions](https://github.com/gorilla/sessions) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/gorilla/sessions/releases)
- [Commits](https://github.com/gorilla/sessions/compare/v1.2.0...v1.2.1)

---
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>
2022-05-31 12:40:50 +00:00
Matt Baer 92504b6721
Merge pull request #553 from davralin/create-container-from-github-action
Create container from GitHub action
2022-05-31 08:39:36 -04:00
davralin 16c6788b62
Switch target branch 2022-05-27 14:01:54 +02:00
davralin 2433745504
Merge pull request #15 from davralin/develop
Update GH-actions
2022-05-25 19:47:18 +02:00
davralin 17c8e78a5c
Update GH-actions 2022-05-25 19:43:10 +02:00
davralin 84fea7abba
Remove unneeded file 2022-05-25 19:12:14 +02:00
davralin 59767804a9
Revert build 2022-05-25 18:45:59 +02:00
davralin dec0142a5b
Update Dockerfile 2022-05-25 18:43:13 +02:00
davralin f2dce539f4
Adjust default 2022-05-25 18:37:40 +02:00
davralin ea0703949d
Create docker-publish.yml 2022-05-25 18:30:26 +02:00
davralin 35ac24223d
Add stuff 2022-05-25 18:29:06 +02:00
ltdk 0a19dc1ec2 Add editorconfig 2022-05-11 13:11:22 -04:00
ltdk baaf0580f5 Add unix socket support
Enables listening on unix sockets by specifying a file path for the bind address
2022-05-10 23:51:13 -04:00
Matt Baer e5103d555f Use ini pkg on GitHub
This fixes "unknown revision v1.66.4" error
2022-04-29 14:28:55 -04:00
Matt Baer cab7fc8647 Update ini pkg to v1.66.4 2022-04-29 14:25:56 -04:00
Matt Baer face603a0e Move to go-gopher library on GitHub 2022-04-29 14:21:22 -04:00
Matt Baer 9a45030911 Change to code.as/wf/go-gopher library
This fixes build issues we're having due to the SSL certificate
on git.mills.io. It also updates to the latest version of the
go-gopher library.
2022-04-29 13:13:25 -04:00
Matt Baer 4680e2e046 Show post excerpt in stats list when no post title
Originally requested on the forum:
https://discuss.write.as/t/use-non-random-ids-for-untitled-posts-in-blog-stats/5046
2022-03-29 13:23:20 -04:00
Matt Baer c3ae4e6d3c Remove blog name in newsletter email subject
Originally requested on the forum:
https://discuss.write.as/t/minimize-subject-of-email-updates/3881
2022-03-29 13:00:53 -04:00
Darius Kazemi dd88083b2a Prevent dropping of external files onto editor
We now detect whether a file is being dropped by a user (by checking `event.dataTransfer.files.length`) and prevent the remaining drop behavior if this is the case. Otherwise, drop happens like normal (so a user can still drop text into the editor, or even an image that has been uploaded already via normal means and rendered in the editor).
2022-03-03 10:25:47 -08:00
Matt Baer fd44bc5707 Fix whitespace 2022-01-31 22:24:56 -05:00
Matt Baer 9ee83ae885 Support custom instance-level stylesheet
Will render if a file exists at: static/local/custom.css

Ref T563
2022-01-31 22:18:52 -05:00
Matt Baer e92c33aae4
Merge pull request #515 from gytisrepecka/fix-gitea-oauth-ext-user-id
#514 - Fix login with Gitea OAuth: external user ID not recorded
2022-01-28 08:59:07 -05:00
Matt Baer 0d554ce180
Merge pull request #525 from writefreely/fix-monetization-sqlite-query
Fix monetization_pointer upsert query for SQLite
2022-01-18 16:47:47 -05:00
Matt Baer a0e936ee1b Support Markdown and rel=me links in blog descriptions
Ref T874 T744
2022-01-18 16:43:17 -05:00
Matt Baer 46bb8e65a1 Add security policy
Closes #522
2022-01-11 09:41:25 -05:00
Isaac Su df7be46417 Protect drafts if they are part of a Private or Protected collection 2022-01-11 16:31:11 +11:00
Matt Baer d1e6daee16 Fix monetization_pointer upsert query for SQLite
Fixes #495
2021-12-29 18:23:31 -05:00
Matt Baer 43ca80f3eb
Merge pull request #503 from mnlg/hotfix/fix-date-format
Fix date format in anonymous posts
2021-12-29 18:05:36 -05:00
Matt Baer 1530bf37ef
Merge pull request #506 from writefreely/minor-fixes
Miscellaneous fixes
2021-12-29 17:53:16 -05:00
Matt Baer 401c8c1f4c
Merge pull request #520 from writefreely/slug-improvements
Elide Markdown when generating slug
2021-12-29 17:51:42 -05:00
Eli Mellen b190a1508b
Merge pull request #1 from eli-oat/eli-oat-remove-cheeky-404-message
Update 404-general.tmpl
2021-12-25 14:05:05 -05:00
Eli Mellen 27f68ef0cf
Update 404-general.tmpl
Remove gaslighting error message and replace with one that is a wee bit more respectful to the human using the software.
2021-12-25 14:03:40 -05:00
Matt Baer 69ab0d34e0 Elide Markdown when generating slug
This makes sure e.g. link URLs and image alt text don't get included
in the slug.

Ref T329
2021-12-21 13:55:10 -05:00
gytisrepecka 97a5121924
#514 - Fix login with Gitea OAuth problems: external user ID not properly recorded. 2021-11-28 13:38:30 +02:00
Matt Baer 129f428bfa Remove unnecessary "upgrade" collection template 2021-11-24 12:45:02 -05:00
Matt Baer 8c1785b904
Merge pull request #505 from lt3Dev/pr/relative-urls
Templates: use relative URLs for static assets
2021-11-24 11:39:06 -06:00
Matt Baer a2f9642238 Use underscores for em text in Rich editor
This fixes an issue where mixing strong and em text caused the text
to not render correctly as a post (e.g. *This is **bold text**.*
would fail).
2021-10-22 11:45:37 -05:00
Matt Baer 5b3d25b5cc Don't use dot import for github.com/gorilla/feeds 2021-09-29 12:48:50 -04:00
Matt Baer 6e5f7e87d2
Merge pull request #501 from mnlg/develop
Fix Gopher collections query
2021-09-29 11:38:13 -05:00
Matt Baer e91748c0bc Return correct count of currently-published lang posts
Previously, we'd include scheduled posts, too.

Ref T805
2021-09-16 15:53:07 -04:00
Matt Baer 414d5b0a1c Add pagination routes on lang post filter
Ref T805
2021-09-16 14:23:35 -04:00
Matt Baer c4b124e37c Limit lang filter to 2 characters
Ref T805
2021-09-16 14:05:52 -04:00
Matt Baer f4977c7a34 Support filtering blog posts by language
Ref T805
2021-09-13 18:36:36 -04:00
Matt Baer 6ad1f41cf4 Prevent crash on empty title
This title, typed directly in the plain editor, would previously crash
the app / show the user a "server error":

# #
2021-08-31 11:18:58 -04:00
Matt Baer 3270470b68 Ignore post `created` date when empty on publish 2021-08-30 17:48:30 -04:00
Matt Baer 2a0298cd46 Make metadata dropdown design consistent 2021-08-30 17:46:02 -04:00
Matt Baer a122e4e98a
Merge pull request #502 from egon0/develop
fix Dockerfile, nodejs-npm was renamed in alpine to npm
2021-08-23 13:08:32 -05:00
Matt Baer 44bfd4573e Support keyboard navigation on pad publish target dropdown 2021-08-23 14:07:25 -04:00
HeartDev cc69f9f2f1 Templates: use relative URLs for static assets
I noticed most asset links use relative URLs, except for a few. This commit remedies this inconsistency.
OpenGraph embeds were not changed, because in my experience, some embed scrapers require absolute URLs.

Motivation: The site I use has an onion and a clearnet version, and currently, visiting the onion loads fonts, styles, and the favicon
over the clearnet.
2021-08-21 10:18:27 +00:00
mnlg ae7e42e24e Fix date format in anonymous posts 2021-08-13 18:43:17 +02:00
Matt Baer fc8e209def Strip Markdown from Letter subjects
Ref T856
2021-08-10 18:05:24 -04:00
Matt Baer e963755393 Set 'To' addresses on Letter email after message is prepared
This works with mailgun.AddRecipientAndVariables, so we can safely send
emails to a large number of recipients beyond Mailgun's 1,000-recipient
limit.

Ref T856
2021-08-10 18:01:19 -04:00
Matt Baer 2288ccf2a2 Merge branch 'develop' into letters 2021-08-10 17:47:23 -04:00
Micha Gläß-Stöcker a58180543e fix Dockerfile, nodejs-npm was renamed in alpine to npm 2021-08-08 22:27:33 +02:00
mnlg 5be1938a8a Fix Gopher collections query 2021-08-05 16:53:20 +02:00
Matt Baer c42439886c
Merge pull request #497 from writefreely/fix-go-gopher-import
Update go-gopher import path to new location
2021-07-23 13:30:14 -04:00
Matt Baer adb4fdc5fe Update go-gopher import path to new location
Fixes #496
2021-07-21 17:28:48 -04:00
Matt Baer b7f732b915
Merge pull request #485 from writefreely/hotfix-0.13.1
0.13.1 hotfix
2021-06-30 13:17:02 -04:00
Matt Baer 940d220bf3 Bump version to 0.13.1 2021-06-30 10:33:08 -04:00
Matt Baer 48075fc183
Merge pull request #474 from writefreely/fix-gen-oauth-empty-user-id
Error when generic OAuth provider doesn't return a user ID
2021-06-30 10:12:50 -04:00
Matt Baer 577bdf14aa
Merge pull request #484 from writefreely/fix-classic-backslashes
Fix trailing backslash on lists in Classic editor
2021-06-30 09:58:02 -04:00
Matt Baer 672fa10b94
Merge pull request #477 from writefreely/fix-sqlite-reader-lock
Fix SQLite database lock on Reader
2021-06-30 09:56:16 -04:00
Matt Baer de5e91cb71 Fix trailing backslash on lists in Classic editor
Previously, when editing a post with an unordered list in it via the
Classic editor, backslashes (\) would get added to the end of each list
item. This fixes that.

Closes #480
2021-06-29 13:07:10 -04:00
Matt Baer 6291f4f155
Merge pull request #479 from writefreely/fix-stats-no-host-log
Fix "Collection.hostName is empty!" log on Stats page
2021-06-29 12:46:47 -04:00
Matt Baer 273c9cf418
Merge pull request #471 from writefreely/fix-title-lists
Don't render title as list item
2021-06-29 10:52:39 -04:00
Matt Baer fbb3000e4d
Merge pull request #476 from writefreely/strip-image-alt-meta
Don't include img alt text in post Summary()
2021-06-28 15:47:42 -04:00
Matt Baer 6b336e22aa Log user out when authenticated as deleted user
Now when we check for the user at certain times and find that the user
doesn't exist in the database, we log them out and send them back to
the home page.
2021-06-27 17:57:07 -04:00
Matt Baer cbc2427475 Don't apply "nofollow" to links on single-user instances 2021-06-27 10:51:53 -04:00
Matt Baer 276304d5b8 Rearrange applyMarkdownSpecial parameters 2021-06-27 10:35:36 -04:00
Matt Baer 65bc73e527
Merge pull request #475 from writefreely/fix-feed-redirect
Remove additional slash in /feed/ redirect
2021-06-26 09:37:18 -04:00
Matt Baer d37ab544e8 Prevent out of bounds error on title with only whitespace 2021-06-25 17:08:59 -04:00
Matt Baer 1bdcf7096a Fix "Collection.hostName is empty!" log on Stats page
Fixes #468
2021-06-25 12:39:59 -04:00
Matt Baer ed771380fb Fix SQLite database lock on Reader
Fetching posts for the Reader involves an additional query on each row,
which previously ran into our connection limit to the database and caused
it to lock up. This increases the connection limit from 1 to 2, to allow
this.

This is meant to be a quick, safe fix, but there could always be a better
solution.

Fixes #467
2021-06-25 12:10:19 -04:00
Matt Baer 720a8c1975 Don't include img alt text in post Summary()
Fixes #306
2021-06-25 11:16:03 -04:00
Matt Baer f933b36170 Prevent out of bounds error when post has no title 2021-06-23 17:38:22 -04:00
Matt Baer e91ffe2dcb Remove additional slash in /feed/ redirect 2021-06-23 16:50:23 -04:00
Matt Baer 3008668a7d Error when generic OAuth provider doesn't return a user ID
...on the OAuth access token inspection call. This returns an error and
privately (via logs) prompts the admin to add a `map_user_id` config value.

Fixes #469
2021-06-23 12:43:57 -04:00
Matt Baer 0ddca40529 Don't render title as list item
This fixes an issue where "12. April" would get rendered as "1. April" because
it looks like a Markdown list item to our renderer. Now, we parse titles as
titles, instead of standalone text, which causes the renderer to give us the
results we want. This also adds some basic tests for the applyBasicMarkdown()
func.

Closes #470
2021-06-22 16:06:04 -04:00
Matt Baer 2ea235f0c4 Support email subscriptions (base)
This adds beginning email subscription functionality, with only MySQL support,
Mailgun support, and incomplete support for private instances. It includes
database changes, so run:

    writefreely db migrate

to use this feature.

Ref T856
2021-06-21 18:24:40 -04:00
Matt Baer e983c4527f Update README links and intro text 2021-06-11 12:04:13 -04:00
Matt Baer 25e4d6448b Fix ineffectual assignments in migrations 2021-06-09 14:25:55 -04:00
Matt Baer 230c736583 Run gofmt on files that need it 2021-06-09 14:22:13 -04:00
Matt Baer e7245536f3
Merge pull request #463 from writefreely/wm-fix
Web Monetization fixes + exclusive content
2021-06-09 14:03:24 -04:00
Matt Baer 42db4b38f6 Truncate paid posts and show badge on Reader 2021-06-09 11:09:53 -04:00
Matt Baer c05f7056c4 Fix collection rendering in Chorus mode 2021-06-09 10:04:28 -04:00
Matt Baer e42ba392c6 Support Web Monetized split content
Ref T770
2021-06-07 15:52:24 -04:00
Matt Baer 9341784c0c Fix OAuth signup with collection description 2021-06-07 15:09:12 -04:00
Matt Baer f0697fd555 Merge branch 'develop' into wm-fix 2021-06-07 14:58:14 -04:00
Matt Baer 7695f8c2e4
Merge pull request #464 from writefreely/api-updates
Final API changes
2021-06-07 14:56:45 -04:00
Matt Baer 85fb2a952b Support setting `description` on user registration 2021-06-07 14:53:22 -04:00
Matt Baer 6740fbe097 Support publishing title-only posts 2021-05-25 17:04:17 -04:00
Matt Baer 2938bba15a Support updating collection only with monetization_pointer 2021-05-25 16:58:09 -04:00
Matt Baer ddc7087d1e Fix Web Monetization option not showing on Customize page 2021-05-25 10:17:57 -04:00
Matt Baer b010484493 Return `url` of new post on publish 2021-05-20 20:44:59 -04:00
Matt Baer 73e0b72878 Fix release build
This forces xgo to use Go 1.15, to work around a bug with Go modules:
https://github.com/techknowlogick/xgo/issues/109#issuecomment-835494720

This also uses the correct Darwin and Windows binary names to prevent
failures in the `make release` process.
2021-05-10 13:08:49 -04:00
Matt Baer 14f5100d6a Bump version to 0.13.0 2021-05-08 15:31:32 -04:00
Matt Baer 5c89812764
Merge pull request #460 from writefreely/add-funding-info
Enable GitHub Sponsor button
2021-05-08 12:27:39 -04:00
Matt Baer 7a71731274 Add GitHub Sponsors profile 2021-05-08 12:27:05 -04:00
Matt Baer b0f792c211 Add Open Collective funding profile 2021-05-07 12:44:50 -04:00
Matt Baer 73450a50e3
Merge pull request #356 from writefreely/draft-list-paging
Draft list paging
2021-05-04 09:39:22 -04:00
Matt Baer 895e04c8c4
Merge pull request #443 from writefreely/minor-fixes
Minor WYSIWYG and miscellaneous fixes
2021-04-30 11:31:06 -04:00
Matt Baer 4565c6dd90 Only use SameSite=None on Secure site
This fixes logging in when developing on newer versions of Chrome.
2021-04-30 11:03:42 -04:00
Matt Baer a7c4a318f3
Merge pull request #458 from writefreely/improve-gopher
Fix Gopher links and add blog info
2021-04-30 10:58:34 -04:00
Matt Baer 7c32dc1045
Merge pull request #457 from writefreely/intl-domain-support
Support international domain names
2021-04-30 09:45:42 -04:00
Matt Baer 2903c86875 Remove prose.bundle.js from repo
This file is compiled from other sources, and thus shouldn't be
included in the repo.
2021-04-30 09:41:18 -04:00
Matt Baer e5347dd924 Move Classic editor horizontal spacing inside .ProseMirror 2021-04-30 09:38:28 -04:00
Matt Baer c9c2adde0f Use Sans font for Classic editor menubar 2021-04-30 09:25:24 -04:00
Matt Baer b2c6c6c167 Rename wysiwyg editor to classic 2021-04-29 17:09:24 -04:00
Matt Baer 5a4ff2a9de Support blockquotes in Classic editor
Ref T727
2021-04-29 17:07:40 -04:00
Matt Baer c01fb585ba Support horizontal rules in Classic editor
Ref T727
2021-04-29 17:05:18 -04:00
Matt Baer affcd270bb
Merge pull request #204 from writefreely/T319-user-delete-acct
T319 user delete acct
2021-04-28 17:57:34 -04:00
Matt Baer 14a8961457 Show correct extracted title on loaded posts page
Ref T401
2021-04-28 17:19:22 -04:00
Matt Baer 4e0912b32a
Merge pull request #203 from writefreely/T319-admin-delete-acct
T319 admin delete acct
2021-04-28 09:46:56 -04:00
Matt Baer 02bb5013a7 Show blog title and description via Gopher 2021-04-27 11:39:28 -04:00
Matt Baer 7257af2905 Strip HTTP port from Gopher links
Previously, if running an instance on e.g. http://localhost:8080, the port
would show up in the Gopher links and potentially cause rendering to fail.
This fixes that.
2021-04-27 11:30:04 -04:00
Matt Baer 36455eea2b Remove debug log 2021-04-26 11:54:42 -04:00
Matt Baer 967ee9679c Support international domain names
This internally converts the configured host name into its Punycode ASCII
representation, while showing users the correct Unicode domain name.
2021-04-26 11:18:51 -04:00
Matt Baer d3d77cee54 Make open account deletion configurable
This adds a configuration option to the [app] section: open_deletion. When
true, users can delete their account on their own.

Ref T319
2021-04-22 13:13:47 -04:00
Matt Baer 7c1c1218b1 Tweak "deletion success" message and note it doesn't work
Ref T319
2021-04-22 12:45:55 -04:00
Matt Baer b092421f6e Add Cross-Site Request Forgery (CSRF) protection on account deletion
This requires admins to generate a new encryption key with:
  writefreely keys generate

Ref T319
2021-04-22 12:41:54 -04:00
Matt Baer a6c93c37da Move user account deletion to confirmation modal
This mimics the admin UI for deleting a user account.

Ref T319
2021-04-22 11:55:17 -04:00
Matt Baer 1d8facfe1c Prevent admin self-deletion in API
Ref T319
2021-04-22 11:37:02 -04:00
Matt Baer f689706baa Merge branch 'T319-admin-delete-acct' into T319-user-delete-acct 2021-04-22 10:14:48 -04:00
Matt Baer f06ab629d1 Make user deletion success message more concise
Ref T319
2021-04-22 10:11:18 -04:00
Matt Baer e4164cbf67 Move admin user deletion confirmation to modal
Ref T319
2021-04-22 10:08:05 -04:00
Matt Baer 3b58d77e67 Merge branch 'develop' into T319-admin-delete-acct 2021-04-22 09:07:40 -04:00
Matt Baer c0fdd8af49
Merge pull request #452 from writefreely/fix-ace-touchscreens
Disable Ace editor on touchscreen devices
2021-04-19 16:25:53 -04:00
Matt Baer c06a739f9b
Merge pull request #450 from writefreely/fix-markdown-import-safari
Accept general text/* files on post import
2021-04-19 16:23:45 -04:00
Matt Baer 4ec8ffa699
Merge pull request #449 from writefreely/fix-actions-on-mobile
Always show post actions on mobile devices
2021-04-19 16:22:44 -04:00
Matt Baer e0a0d71c84 Revert "Use Sans font for post action buttons"
This reverts commit af4e0b4f1c.
2021-04-19 16:22:01 -04:00
Matt Baer 3ab21f7834
Merge pull request #445 from writefreely/remove-nerds-dep
Remove writeas/nerds/store pkg dependency
2021-04-19 16:19:52 -04:00
Matt Baer 61974fadc0 Merge branch 'develop' into remove-nerds-dep 2021-04-19 16:18:33 -04:00
Matt Baer 439f8bd262
Merge pull request #444 from writefreely/log-out-pass-blog
Support logging out of password-protected blogs
2021-04-12 14:11:12 -04:00
Matt Baer 63fa8d299a Include 'move to...' action in loaded draft posts
Ref T401
2021-04-07 16:44:18 -04:00
Matt Baer 27b43ac2f1 Merge branch 'develop' into draft-list-paging 2021-04-07 15:58:25 -04:00
Matt Baer 51a83069c4 Disable Ace editor on touchscreen devices
Ace doesn't work well with touchscreen devices, so instead we fall back to a
plain textarea.
2021-04-07 14:58:09 -04:00
Matt Baer ac7583eadb
Merge pull request #384 from colin-axner/374-fix-silenced-post-accessibility
fix accessibility of silenced user posts
2021-04-07 14:01:04 -04:00
Colin Axnér 8ac2d0b310 merge develop 2021-04-07 18:33:02 +02:00
Colin Axnér 866a585119 fix compile error 2021-04-07 18:31:45 +02:00
Matt Baer 4228761eb3
Merge pull request #431 from Dak425/stop-federating-protected-and-private-blogs
Stop private and protected blogs from federating
2021-04-07 10:54:28 -04:00
Donald Feury 68297acb74 Moved guard clauses to stop federation before debug logging 2021-04-07 11:27:25 -04:00
Matt Baer de601e16ac
Merge pull request #451 from writefreely/move-repo
Update repo URL to writefreely org
2021-04-06 17:27:17 -04:00
Matt Baer 484d2736ce Update repo URL to writefreely org
From the writeas org on GitHub.
2021-04-06 17:24:07 -04:00
Matt Baer f8888df746 Accept general text/* files on post import
This fixes an issue with Safari not allowing users to select *.md files.

Closes #334
2021-04-06 15:59:32 -04:00
Matt Baer 0c7aba1f53
Merge pull request #442 from writeas/add-attachments
Include images as attachments in ActivityStreams data
2021-04-06 12:44:30 -04:00
Matt Baer 02490c798c Only set lang on initial publish in all editors
This fixes #280 in all editors, continuing work from #435.
2021-04-06 12:38:45 -04:00
Matt Baer 11e636359d Clean up lang field setting in pad.tmpl
This continues work by @cjeller1592 in #435.
2021-04-06 12:37:26 -04:00
Matt Baer 50c4e944a4 Merge branch 'develop' into minor-fixes 2021-04-06 12:30:37 -04:00
Matt Baer e58e457b25
Merge pull request #435 from cjeller1592/lang-metadata
Add conditional for preserving lang metadata
2021-04-06 12:17:35 -04:00
Matt Baer af4e0b4f1c Use Sans font for post action buttons 2021-04-06 12:11:45 -04:00
Matt Baer ed74228795
Merge pull request #434 from cjeller1592/compose-update-wf-image
Update writefreely-web image in docker-compose.yml
2021-04-06 11:59:26 -04:00
Matt Baer 2c1d3a51af
Merge pull request #429 from x4e/develop
Case insensitive language highlighting matching
2021-04-06 11:31:08 -04:00
Matt Baer 23818c6104
Merge pull request #426 from mostfunkyduck/develop
Fixes problem where all links were breaking in gopher
2021-04-06 11:26:46 -04:00
Matt Baer 5510ef15b5 Always show post actions on mobile devices
This maintains previous show-on-hover effect for post actions on blog
index, but always shows the actions when on a mobile device.

Fixes #309
2021-04-06 11:09:38 -04:00
Matt Baer 5ecf613cb5
Merge pull request #448 from writeas/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.5
Bump github.com/microcosm-cc/bluemonday from 1.0.4 to 1.0.5
2021-04-01 07:26:05 -04:00
dependabot[bot] 9cbd254d64
Bump github.com/microcosm-cc/bluemonday from 1.0.4 to 1.0.5
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.4...v1.0.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 11:24:37 +00:00
Matt Baer 733301d364
Merge pull request #447 from writeas/dependabot/go_modules/github.com/go-sql-driver/mysql-1.6.0
Bump github.com/go-sql-driver/mysql from 1.5.0 to 1.6.0
2021-04-01 07:24:26 -04:00
Matt Baer f1eae4007e
Merge pull request #446 from writeas/dependabot/go_modules/github.com/hashicorp/go-multierror-1.1.1
Bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1
2021-04-01 07:22:09 -04:00
dependabot[bot] f70fc0c4e2
Bump github.com/go-sql-driver/mysql from 1.5.0 to 1.6.0
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.5.0 to 1.6.0.
- [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.5.0...v1.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 05:14:03 +00:00
dependabot[bot] 2a9aa84366
Bump github.com/hashicorp/go-multierror from 1.1.0 to 1.1.1
Bumps [github.com/hashicorp/go-multierror](https://github.com/hashicorp/go-multierror) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/hashicorp/go-multierror/releases)
- [Commits](https://github.com/hashicorp/go-multierror/compare/v1.1.0...v1.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-01 05:13:09 +00:00
Matt Baer 64f1d71524 Remove writeas/nerds/store dependency 2021-03-30 12:49:12 -04:00
Matt Baer 5a3e8d59b6 Support logging out of password-protected blogs
Closes T492
2021-03-29 20:55:21 -04:00
Matt Baer 6f665e7e4b Fix light / dark mode toggle in WYSIWYG editor 2021-03-25 13:02:57 -04:00
Matt Baer d7c9f56b40 Rename ProseMirror writeAs Parser/Serializer to writeFreely 2021-03-25 13:02:57 -04:00
Matt Baer 47aa436caa Render `tight` Markdown lists in WYSIWYG editor 2021-03-25 13:02:57 -04:00
Matt Baer 424bd55816 Set SameSite=None on session cookie 2021-03-25 13:02:57 -04:00
Matt Baer 3e282e4c85 Rename MonetizationPointer field to Monetization 2021-03-25 13:02:35 -04:00
Matt Baer 85efbcccfc
Merge pull request #439 from writeas/support-authorized-fetch
Fix following from Mastodon with Authorized Fetch enabled
2021-03-25 08:46:23 -04:00
Matt Baer 4a58a94e26 Include images as attachments in ActivityStreams data
Ref T709
2021-03-24 16:00:52 -04:00
Matt Baer 9aa5fc4420 Fix ProseMirror failing to parse Markdown images 2021-03-19 17:02:00 -04:00
Matt Baer 636c9b35c0 Use WriteFreely ProseMirror packages 2021-03-19 17:00:21 -04:00
Matt Baer a6a4bd38c1 Fix overflowing WYSIWYG editor images 2021-03-19 16:59:01 -04:00
Matt Baer 811f996e84
Merge pull request #437 from writeas/post-signature-pinned-posts
Post signature improvements
2021-03-19 13:28:54 -04:00
Matt Baer 3984042905
Merge pull request #405 from dariusk/dariusk/inspectuser
Generic OAuth userinfo properies now configurable
2021-03-18 19:28:43 -04:00
Matt Baer 321c1af607
Merge pull request #436 from writeas/publish-with-slug
Support `slug` parameter when publishing a post
2021-03-09 09:59:57 -05:00
Matt Baer 9f525876f4 Fix instance-wide actor webfinger lookup
This skips the silenced-user check.

Ref T820
2021-03-08 13:02:59 -05:00
Matt Baer 9b336dee8c Fix instance-wide actor lookup
This skips the silenced-user check.

Ref T820
2021-03-08 12:54:50 -05:00
Matt Baer 9aeeb52bdb Fix nil pointer on instance-wide actor lookup
Ref T820
2021-03-08 12:50:08 -05:00
Matt Baer 9484880bca Sign actor fetch request
This fixes federation with Mastodon instances that have Authorized
Fetch turned on by signing the GET request to fetch the actor when
a blog is first followed.

Ref T820
2021-03-08 11:43:38 -05:00
Colin Axnér f2e3cd8bd7 merge develop 2021-03-08 11:42:00 +01:00
Colin Axnér 00f2152c2b update commit for web-core
Update go.mod to use latest commit on web-core
2021-03-08 11:37:22 +01:00
Matt Baer 4cf9500704
Merge pull request #418 from writeas/consistent-feeds
Use HTMLContent field in RSS feed
2021-03-06 17:27:34 -05:00
Matt Baer fbb67bc9ef
Merge pull request #383 from writeas/wysiwyg
Add WYSIWYG editor option
2021-03-06 16:48:56 -05:00
Matt Baer 4f32af2d7f
Merge pull request #220 from writeas/support-notes
Support ActivityPub Notes
2021-03-05 11:10:19 -05:00
Matt Baer 97242cd5ec
Merge pull request #433 from writeas/dependabot/go_modules/github.com/stretchr/testify-1.7.0
Bump github.com/stretchr/testify from 1.6.1 to 1.7.0
2021-03-05 08:59:24 -05:00
Matt Baer bd77145bf3
Merge pull request #423 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.6
Bump github.com/mattn/go-sqlite3 from 1.14.4 to 1.14.6
2021-03-04 17:05:52 -05:00
dependabot[bot] 1ea728b1e9
Bump github.com/stretchr/testify from 1.6.1 to 1.7.0
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.6.1...v1.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-04 18:03:38 +00:00
Matt Baer c813d08230
Merge pull request #414 from writeas/dependabot/go_modules/github.com/urfave/cli/v2-2.3.0
Bump github.com/urfave/cli/v2 from 2.2.0 to 2.3.0
2021-03-04 13:02:06 -05:00
dependabot[bot] 1ac5c4ab4d
Bump github.com/mattn/go-sqlite3 from 1.14.4 to 1.14.6
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.4 to 1.14.6.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.4...v1.14.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-04 16:46:50 +00:00
Matt Baer ff976a950e
Merge pull request #409 from writeas/dependabot/go_modules/github.com/fatih/color-1.10.0
Bump github.com/fatih/color from 1.9.0 to 1.10.0
2021-03-04 11:45:28 -05:00
Matt Baer 1f6d0e2e70 Merge branch 'develop' into support-notes 2021-03-04 11:42:49 -05:00
dependabot[bot] 5b2c350b5d
Bump github.com/fatih/color from 1.9.0 to 1.10.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.9.0 to 1.10.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.9.0...v1.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-04 00:22:20 +00:00
Matt Baer b3dd06c79b Merge branch 'develop' into wysiwyg 2021-03-03 17:38:28 -05:00
dependabot[bot] 71b211b11e
Bump github.com/urfave/cli/v2 from 2.2.0 to 2.3.0
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.2.0 to 2.3.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.2.0...v2.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-03 22:37:46 +00:00
Matt Baer 33d47ca420
Merge pull request #408 from writeas/dependabot/go_modules/gopkg.in/ini.v1-1.62.0
Bump gopkg.in/ini.v1 from 1.61.0 to 1.62.0
2021-03-03 17:36:30 -05:00
Matt Baer c2c6b69044 Update prose.bundle.js 2021-03-03 17:05:05 -05:00
Matt Baer 706ae9cc77 Style Read More section in WYSIWYG like link 2021-03-03 17:04:16 -05:00
Matt Baer 1abc9b643f Improve WYSIWYG editor modal style 2021-03-03 16:41:21 -05:00
Matt Baer 8a8288d2af Build ProseMirror library on `make ui` 2021-03-03 16:39:29 -05:00
Matt Baer e36e39cb73 Fix WYSIWYG editor script in Chrome <85
This uses String.replace() instead of String.replaceAll().
2021-03-03 16:38:49 -05:00
v 583693ed8d Updated prod prosemirror bundle. 2021-03-03 20:45:55 +01:00
v 19beabe2d1 Fixed Safari regex lookahead / lookbehind issue. 2021-03-03 20:29:23 +01:00
Donald Feury ebdb932090 I meant develop, not master 2021-02-24 23:57:35 -05:00
Donald Feury 4c0fcdf7c6 Setting activitypub.go back to master version 2021-02-24 23:55:56 -05:00
Donald Feury 9ed2687543 Added TagCollectionPage
* Implements PrevPageURL and NextPageURL
* This allows the collection-tag template to get proper urls for
  paginating using tags.
2021-02-24 23:49:15 -05:00
Donald Feury 530439772d Add Pagination to Tags Collection
Mostly copied the logic for pagination from non tag collection
2021-02-24 23:00:21 -05:00
Matt Baer 33cf9263f5 Support "nosig" shortcode to hide signature in post
If a post contains <!--nosig--> anywhere in the body, the post will render
without a post signature on it.

Ref T815
2021-02-24 12:49:28 -05:00
Matt Baer a10827cd50 Hide post signature on pinned posts
Ref T814
2021-02-23 17:36:35 -05:00
Matt Baer 65caaca659 Update ProseMirror install instructions 2021-02-23 16:54:38 -05:00
Matt Baer 2d38e8b65e Create coll post with the provided slug, if exists
Closes T811
2021-02-22 14:25:18 -05:00
Viktor Vaczi 8c0978419f using a branch from github with the markdown hashtag serializing fix 2021-02-13 13:27:15 +01:00
CJ Eller 391844fab9
Add conditional for preserving lang metadata
This makes it so that if a post is updated, it will retain the language metadata rather than revert back.
2021-02-10 18:01:32 -05:00
CJ Eller e6c36fc2ef
Update writefreely-web image
Updated with the official writeas/writefreely image on Docker Hub.
2021-02-01 12:01:49 -05:00
Donald Feury e6417d911c Stop private and protected blogs from federating
Fixes #403
2021-01-27 19:39:46 -05:00
x4e 795748457c
Case insensitive language highlighting matching
This automatically lowercases language names used in code blocks when finding highlighting scripts for them, since highlightJS defines all languages with lowercase names.
2021-01-24 01:24:12 +00:00
funkyduck 6c1ab93717 The gopher integration was not setting host or port, causing all links to break.
This will derive the host from the configured host by stripping the protocol from the URI
2021-01-14 09:46:20 -05:00
Viktor Vaczi 6049213661 added prettierrc 2021-01-08 01:41:36 +01:00
Viktor Vaczi 9a55d38e4b working newlines, link shortcut 2021-01-08 00:33:35 +01:00
Viktor Vaczi 676b673c94 Correct line ends 2021-01-05 15:06:19 +01:00
Viktor Vaczi b1cea637cb Removed unnecessary schema nodes, custom markdown parser/serializer, publish keyboard shortcut, readmore node 2021-01-04 17:19:24 +01:00
Matt Baer f31e4d650d Use HTMLContent field in RSS feed
This re-uses the HTMLContent feed for the full HTML content in the RSS
feed, instead of again generating HTML from Markdown.

This keeps things more consistent throughout the application and reduces
work when rendering the feed.
2020-12-15 09:33:14 -05:00
Matt Baer 53ea85dc86
Merge pull request #407 from conor-f/docker-fixes
Dockerfile/docker-compose fixes
2020-12-08 09:38:22 -05:00
Colin Axner fcf01a6039 add back else clause
Add back else clause after realizing the error check doesn't return after logging.
2020-12-07 11:28:51 +01:00
Colin Axner 30fc088cec reset cache after silencing user 2020-12-06 12:45:45 +01:00
Colin Axner 3aa621ee36 allow cache to be forcibly reset
Modify updateTimelineCache to allow a boolean to indicate that the cache should be forcibly reset
2020-12-06 12:30:54 +01:00
Matt Baer d52e2826f8
Merge pull request #389 from colin-axner/remove-unnecessary-var
remove unnecessary var in account.go
2020-11-16 12:06:47 -05:00
dependabot[bot] ed00417d8d
Bump gopkg.in/ini.v1 from 1.61.0 to 1.62.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.61.0 to 1.62.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.61.0...v1.62.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-01 05:31:06 +00:00
Conor Flynn 9f925c8138 Changes docker-compose image to use writefreely. 2020-10-26 13:52:04 +00:00
Conor Flynn 0eb1a2deec Fixes broken Docker/docker-compose structures.
Updates versions and uses maria DB instead of sqlite in the
docker-compose. Also fixes things related to networks, generating
config, etc.
2020-10-26 13:50:11 +00:00
Darius Kazemi b262fa144c Making changes per feedback 2020-10-22 13:10:51 -07:00
Darius Kazemi 0aafd0c368 Generic OAuth userinfo properies now configurable
When connecting to a generic OAuth provider, you are never sure what object you'll be receiving from the userinfo endpoint (it isn't actually specified anywhere). So this commit adds mapping values to the generic oauth configuration section of config.ini, allowing the user to specify which keys in the remote endpoint it expects to read the UserID, Username, DisplayName, and Email from. Default values if unspecified remain as they were before this commit.
2020-10-22 12:15:55 -07:00
Matt Baer 3493921837
Merge pull request #402 from dariusk/dariusk/mastodon
Adding scope field to generic OAuth
2020-10-13 12:16:32 -04:00
Matt Baer 7d4df23d3c
Merge pull request #397 from mrvdb/issue305
Drop the /tags/{tag} route
2020-10-13 08:55:04 -04:00
Matt Baer 3b91400b62
Merge pull request #387 from writeas/resize-customize-editor
Automatically resize CSS editor
2020-10-13 08:51:05 -04:00
Matt Baer bb008aa66c
Merge pull request #382 from Antolius/fix-post-summary-escaping
Fix escaping in post summary
2020-10-13 08:41:25 -04:00
Darius Kazemi 667cbb97ed Adding scope field to generic OAuth
Some OAuth providers (like Mastodon) do not use the default
"read_user" scope, instead offering a custom scope. The config.ini
for generic OAuth now contains a "scope" field, allowing the admin
to set the scope manually (it defaults to "read_user" if blank).
2020-10-12 20:54:48 -07:00
Matt Baer e1cde913e2
Merge pull request #369 from writeas/web-monetization
Support Web Monetization
2020-10-06 16:22:28 -04:00
Matt Baer 345313200e
Merge pull request #385 from writeas/fix-pinned-post-header-space
Fix header spacing on pinned posts
2020-10-03 11:52:44 -04:00
Matt Baer 211b02c209
Merge pull request #364 from Obayanju/fix-youtube-query-parameters
Fix removal of query parameters on youtube embed links
2020-10-03 11:48:30 -04:00
Matt Baer b1e22795b1
Merge pull request #378 from writeas/dependabot/go_modules/github.com/gorilla/mux-1.8.0
Bump github.com/gorilla/mux from 1.7.4 to 1.8.0
2020-10-03 11:38:53 -04:00
Matt Baer cf0403d955
Merge pull request #398 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.4
Bump github.com/mattn/go-sqlite3 from 1.14.2 to 1.14.4
2020-10-03 11:37:03 -04:00
Matt Baer c1aed45388
Merge pull request #396 from writeas/dependabot/go_modules/gopkg.in/ini.v1-1.61.0
Bump gopkg.in/ini.v1 from 1.57.0 to 1.61.0
2020-10-03 11:33:34 -04:00
dependabot[bot] 083d8c4d67
Bump github.com/mattn/go-sqlite3 from 1.14.2 to 1.14.4
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.2 to 1.14.4.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.2...v1.14.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-03 14:35:04 +00:00
Matt Baer e3c7a8ac3a
Merge pull request #395 from writeas/dependabot/go_modules/github.com/mitchellh/go-wordwrap-1.0.1
Bump github.com/mitchellh/go-wordwrap from 1.0.0 to 1.0.1
2020-10-03 10:33:30 -04:00
dependabot[bot] 454e781ed4
Bump github.com/mitchellh/go-wordwrap from 1.0.0 to 1.0.1
Bumps [github.com/mitchellh/go-wordwrap](https://github.com/mitchellh/go-wordwrap) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/mitchellh/go-wordwrap/releases)
- [Commits](https://github.com/mitchellh/go-wordwrap/compare/v1.0.0...v1.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-03 14:30:24 +00:00
Matt Baer 1b6f9b6742
Merge pull request #393 from writeas/dependabot/go_modules/github.com/manifoldco/promptui-0.8.0
Bump github.com/manifoldco/promptui from 0.7.0 to 0.8.0
2020-10-03 10:29:03 -04:00
Marcel van der Boom 5961eb8f27 Drop the /tags/{tag} route
fixes issue #305
2020-10-03 15:34:44 +02:00
dependabot[bot] f5f28550fb
Bump gopkg.in/ini.v1 from 1.57.0 to 1.61.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.57.0 to 1.61.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.57.0...v1.61.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 05:23:13 +00:00
dependabot[bot] c22a751ab7
Bump github.com/manifoldco/promptui from 0.7.0 to 0.8.0
Bumps [github.com/manifoldco/promptui](https://github.com/manifoldco/promptui) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/manifoldco/promptui/releases)
- [Changelog](https://github.com/manifoldco/promptui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/manifoldco/promptui/compare/v0.7.0...v0.8.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-01 05:22:43 +00:00
Matt Baer 2768ea9414 Make Monetization optional
Some WriteFreely instances are completely private, and thus have no need for public-
oriented features like Web Monetization. Like federation, this gives admins control
over whether or not the feature is enabled for users.

Ref T773
2020-09-30 15:18:21 -04:00
Matt Baer 13a3a68d54 Validate and trim spaces on WM pointer
Ref T773
2020-09-30 14:42:11 -04:00
Matt Baer ec7b299fd3 Enable updating WM payment pointer via API and Customize page
Ref T773
2020-09-30 14:40:13 -04:00
Matt Baer f534ee1dec
Merge pull request #391 from colin-axner/getCollectionPage-cleanup
update getCollectionPage
2020-09-29 10:52:19 -04:00
Colin Axner 678653ac30 update getCollectionPage
Update getCollectionPage godoc and reduce logic and variable assignments
2020-09-25 16:47:31 +02:00
Colin Axner 75a79d49bd remove unnecessary var
Remove createdWithPass var in account.go along with impossible if statement
2020-09-25 16:07:30 +02:00
Matt Baer 2908080b52 Automatically resize CSS editor
Closes T708
2020-09-24 15:08:08 -04:00
Matt Baer d6d510aec9 Fix `dated` header class on pinned posts
The header shouldn't include the `dated` property, since we don't display a date
on pinned posts. This fixes that.
2020-09-17 14:55:10 -04:00
Colin Axner 5ba0ea2b04 fix accessibility of silenced user posts
Change view post collection queries to verify that the authenticated user of a silenced collection is either the owner or admin
2020-09-17 12:50:06 +02:00
Matt Baer a96d4474ef Add auto-save to WYSIWYG editor 2020-09-09 17:46:47 -04:00
Matt Baer a7190795f7 Output prose.bundle.js to static/js/ dir, instead of dist/ 2020-09-09 10:47:01 -04:00
Matt Baer 70dbfcfba4 Fix webpack build issues
This upgrades @babel/preset-env to 7.9.0 from 7.8.7.

See: https://github.com/nodejs/node/issues/32852#issuecomment-613655150
2020-09-09 10:45:04 -04:00
Matt Baer da8c08668f Fix whitespace in prose.js 2020-09-09 10:02:00 -04:00
Matt Baer 61daca2b0d Merge branch 'develop' into wysiwyg 2020-09-09 09:53:38 -04:00
Dami f847ade1ef Use camelCase 2020-09-09 00:01:32 -06:00
Dami 3a789f5a00 Go to next regex match if url parsing error 2020-09-08 23:59:56 -06:00
Dami 79715891fb Merge branch 'develop' of https://github.com/writeas/writefreely into fix-youtube-query-parameters 2020-09-08 23:49:26 -06:00
Josip Antoliš eb76faa129 Fix escaping in post summary
Unescaping post content after sanitizing it. This will prevent double
escaping when summary is rendered by html/template package which does
escaping by default.
Fixes #340
2020-09-05 01:59:44 +02:00
Matt Baer 7c1244e6b1
Merge pull request #381 from writeas/fix-testpostlede
Truncate post lede at question mark
2020-09-04 16:36:14 -04:00
Matt Baer c31a87fb76
Merge pull request #336 from Antolius/static-assets-cache-control
Add Cache-Control header
2020-09-04 16:12:29 -04:00
Josip Antoliš 1b1d3064c9 Move cacheControl func into handle.go file 2020-09-04 22:03:42 +02:00
Josip Antoliš 3f36ede885 Add Cache-Control header
Add Cache-Control response header for static files in order to improve page loading speed.
Fixes  #322
2020-09-04 22:03:42 +02:00
Matt Baer f821ead3a1 Support only federating Notes
When setting `notes_only = true` in the `[app]` configuration section, WF will only send out `Note` objects. Otherwise, it will send out both `Note`s and `Article`s.
2020-09-02 10:36:00 -04:00
Matt Baer 8be71481c8 Merge branch 'develop' into support-notes 2020-09-02 10:29:59 -04:00
Matt Baer a8a6525006
Merge pull request #377 from writeas/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.4
Bump github.com/microcosm-cc/bluemonday from 1.0.3 to 1.0.4
2020-09-02 10:20:30 -04:00
Matt Baer 98d88b9a4b Truncate lede at question mark
This fixes TestPostLede and closes #316.
2020-09-02 09:38:02 -04:00
Matt Baer ac90cb2c80 Fix panic in TestViewOauthCallback 2020-09-02 09:33:16 -04:00
dependabot[bot] 00a5a4f7ab
Bump github.com/microcosm-cc/bluemonday from 1.0.3 to 1.0.4
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.3 to 1.0.4.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.3...v1.0.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 19:42:11 +00:00
Matt Baer 505b124db7
Merge pull request #379 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.2
Bump github.com/mattn/go-sqlite3 from 1.14.0 to 1.14.2
2020-09-01 15:40:35 -04:00
Matt Baer f75d4cb75d
Merge pull request #327 from Dar13/add-cors-headers-public-api
Add CORS header ('Access-Control-Allow-Origin') to public APIs
2020-09-01 15:01:33 -04:00
dependabot[bot] 21579cfa71
Bump github.com/mattn/go-sqlite3 from 1.14.0 to 1.14.2
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.0 to 1.14.2.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.0...v1.14.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 13:41:01 +00:00
dependabot[bot] 1779aeaf8c
Bump github.com/gorilla/mux from 1.7.4 to 1.8.0
Bumps [github.com/gorilla/mux](https://github.com/gorilla/mux) from 1.7.4 to 1.8.0.
- [Release notes](https://github.com/gorilla/mux/releases)
- [Commits](https://github.com/gorilla/mux/compare/v1.7.4...v1.8.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 13:40:59 +00:00
Matt Baer 62d29166f4
Merge pull request #376 from writeas/dependabot/go_modules/github.com/gorilla/schema-1.2.0
Bump github.com/gorilla/schema from 1.1.0 to 1.2.0
2020-09-01 09:39:26 -04:00
dependabot[bot] e60398f0b4
Bump github.com/gorilla/schema from 1.1.0 to 1.2.0
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.1.0...v1.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-01 06:32:10 +00:00
Matt Baer ce69117c79
Merge pull request #371 from writeas/design-update
Navigation improvements
2020-08-31 18:21:41 -05:00
Matt Baer d8019bba0d
Merge pull request #370 from writeas/oauth-cleanup
OAuth tidy-up and refactor
2020-08-31 17:08:33 -05:00
Matt Baer 820c5ae557 Remove Admin link from header on Account Settings page 2020-08-19 18:22:11 -04:00
Matt Baer 3a915ad8ea Make New Post button contextual on coll sub-sections 2020-08-19 18:21:53 -04:00
Matt Baer 8d27ee6d99 Only show "New Post" button in coll. nav bar on Blogs page 2020-08-19 18:20:26 -04:00
Matt Baer 6f8d70043f Move coll. navigation to main header for single-user sites
This uses the main navigation bar for the Customize and Stats pages, instead of the collection-navigation bar used for multi-user / multi-collection instances.
2020-08-19 18:19:28 -04:00
Matt Baer 9d0ba2bed4 Add contextual blog menu 2020-08-19 17:09:47 -04:00
Matt Baer cef51a7797 Remove accidental #user-nav color 2020-08-19 16:56:26 -04:00
Matt Baer 0ed9c9c746 Add inter-blog navigation
This makes it easier to navigate between different blog sections: Customize, Stats, View.
2020-08-19 16:24:37 -04:00
Matt Baer 217430e56b Redirect user to /me/settings on cancelled OAuth flow 2020-08-19 15:40:07 -04:00
Matt Baer 7a09a47de2 Include OAuth buttons on signup-via-invite page 2020-08-19 15:20:13 -04:00
Matt Baer 455e50c9a8 Use branded OAuth buttons
This includes the platform mark with each login button and uses
brand colors. It also uses the same style on the Account Settings
page. And it wraps buttons on login / signup pages.
2020-08-19 15:14:29 -04:00
Matt Baer a78b36b871 Fix whitespace in user/settings.tmpl 2020-08-19 14:55:16 -04:00
Matt Baer 00cceca104 Update signup-via-invite page
This updates signup.tmpl to include all supported OAuth methods and respect the new `DisablePasswordAuth` config value.
2020-08-19 13:35:21 -04:00
Matt Baer 4db2cb8986 Templatize OAuth buttons across signup and login pages
This moves fields into the `OAuthButtons` struct and puts the buttons into templates/includes/oauth.tmpl.
2020-08-19 13:31:07 -04:00
Matt Baer a773d94dc7 Reorder Gitea fields and structs in config.go 2020-08-19 13:26:15 -04:00
Matt Baer 04d404e61f Move text OAuth button styles to login.less 2020-08-19 12:38:44 -04:00
Matt Baer 21e9b4a667 Run `go fmt` on database.go 2020-08-19 12:29:23 -04:00
Matt Baer 63f023ea98 Fix GitLab & OAuth button labels on Login page
Previously, these display names were swapped.
2020-08-19 12:04:36 -04:00
Matt Baer ab32caa49c Include key names in Login page struct 2020-08-19 12:02:36 -04:00
Matt Baer 13eb51913e Support Web Monetization via backend attribute
This supports a new `monetization_pointer` collection attribute.
When present, we include the `monetization` meta tag on all
collection pages.
2020-08-19 09:28:44 -04:00
Matt Baer 95273697f4 Use consistent server User-Agent across application 2020-08-18 12:22:04 -04:00
Matt Baer dfa14c9c92
Merge pull request #317 from pascoual/feature/generic-oauth
Login with generic oauth feature++
2020-08-17 14:24:50 -04:00
prichier ab285644a0 Fix: signup methods mutually exclusive 2020-08-16 20:42:55 +02:00
Pascal Richier d3f1e40010
Merge branch 'develop' into feature/generic-oauth 2020-08-16 19:58:01 +02:00
Matt Baer 7e3eb9a87b
Merge pull request #319 from writeas/silo-mentions
Support AP-style mentions of centralized social media accounts
2020-08-13 12:36:42 -04:00
Matt Baer 7fa78c2255 Move fakeAPInstances to web-core/silobridge package
This adds support for mentioning profiles on the following sites:

- deviantart.com
- facebook.com
- flickr.com
- github.com
- instagram.com
- reddit.com
- wattpad.com
- youtube.com
2020-08-13 12:33:35 -04:00
Matt Baer c16414843a
Merge pull request #368 from writeas/RJ722-217-fix-h2-size
Use a bigger font size for h2
2020-08-13 12:24:41 -04:00
Matt Baer b2382b5422
Merge pull request #293 from gytisrepecka/oauth-gitea
Added Gitea OAuth login and account management.
2020-08-13 10:41:29 -04:00
gytisrepecka 731d4e8efe
Run gofmt for modified files. 2020-08-13 09:15:04 +03:00
Matt Baer fd3a6399b3
Merge pull request #355 from writeas/accessible-dropdowns
Add menu hover delay
2020-08-12 13:51:08 -04:00
Matt Baer 8b243e119f Change post page h2 size to 1.4em 2020-08-12 13:48:48 -04:00
Matt Baer 0c8b779afb Merge branch '217-fix-h2-size' of https://github.com/RJ722/writefreely into RJ722-217-fix-h2-size 2020-08-12 13:42:57 -04:00
Matt Baer 5f52c23a65
Merge pull request #181 from writeas/mathjax-in-binaries
Mathjax in binaries
2020-08-11 09:47:16 -04:00
Matt Baer e37bec6aa1
Merge pull request #196 from writeas/dark-mode-pad
Dark mode pad
2020-08-11 09:30:07 -04:00
Matt Baer 121d83d94d Clean up pad JS formatting, debug messages 2020-08-11 09:27:50 -04:00
Dami 9b614bc922 Fix removal of query parameters on youtube embed links
This uses go's html and url parser plus regex, instead of
using only a single regex for simplicity sake. A single regex expression
might be error prone, for example, when trying to matching html entities.

Fixes #328
2020-08-07 00:05:43 -06:00
Matt Baer 09e70e07f8 Support loading more draft posts
This adds a "load more" button to the bottom of the draft posts page,
which calls /api/me/posts with new parameters and the current page
number. It then populates the page accordingly.

Ref T696 - load anon. posts with ?anonymous=1&page=1
Ref T401 - completes UI for post loading
2020-07-30 16:46:01 -04:00
Matt Baer 7eeba4dc9e Limit initial draft post loading to 10 posts
Ref T401
2020-07-30 16:28:21 -04:00
Matt Baer 849e5b8503
Merge pull request #330 from writeas/post-signatures
Support post signatures

Ref T582
2020-07-30 11:53:27 -04:00
Matt Baer fee44e7c8d Add menu hover delay on user pages + editor 2020-07-30 11:26:29 -04:00
Matt Baer a32fc44153
Merge pull request #350 from writeas/dependabot/go_modules/github.com/fatih/color-1.9.0
Bump github.com/fatih/color from 1.7.0 to 1.9.0
2020-07-28 15:14:54 -04:00
Matt Baer bd387c6dec Merge branch 'develop' into dependabot/go_modules/github.com/fatih/color-1.9.0 2020-07-28 15:09:19 -04:00
Matt Baer cd6ccd257b
Merge pull request #343 from writeas/dependabot/go_modules/github.com/manifoldco/promptui-0.7.0
Bump github.com/manifoldco/promptui from 0.3.2 to 0.7.0
2020-07-28 15:05:21 -04:00
dependabot[bot] 9c835a2b9d
Bump github.com/fatih/color from 1.7.0 to 1.9.0
Bumps [github.com/fatih/color](https://github.com/fatih/color) from 1.7.0 to 1.9.0.
- [Release notes](https://github.com/fatih/color/releases)
- [Commits](https://github.com/fatih/color/compare/v1.7.0...v1.9.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 18:01:42 +00:00
Matt Baer 55ffb86ac2
Merge pull request #351 from writeas/dependabot/go_modules/github.com/go-sql-driver/mysql-1.5.0
Bump github.com/go-sql-driver/mysql from 1.4.1 to 1.5.0
2020-07-28 14:00:05 -04:00
Matt Baer 78e0d98589
Merge pull request #349 from writeas/dependabot/go_modules/github.com/guregu/null-3.5.0incompatible
Bump github.com/guregu/null from 3.4.0+incompatible to 3.5.0+incompatible
2020-07-28 13:54:58 -04:00
Matt Baer 6af24293d1 Merge branch 'develop' into dependabot/go_modules/github.com/guregu/null-3.5.0incompatible 2020-07-28 13:53:36 -04:00
Matt Baer 9d7783f80d
Merge pull request #348 from writeas/dependabot/go_modules/gopkg.in/ini.v1-1.57.0
Bump gopkg.in/ini.v1 from 1.55.0 to 1.57.0
2020-07-28 13:41:04 -04:00
Matt Baer 2eaf7493d7
Merge pull request #347 from writeas/dependabot/go_modules/github.com/urfave/cli/v2-2.2.0
Bump github.com/urfave/cli/v2 from 2.1.1 to 2.2.0
2020-07-28 13:19:43 -04:00
Matt Baer ab6d4bfb9d
Merge pull request #353 from writeas/dependabot/go_modules/github.com/writeas/import-0.2.1
Bump github.com/writeas/import from 0.2.0 to 0.2.1
2020-07-28 13:00:25 -04:00
dependabot[bot] 2c45307107
Bump github.com/urfave/cli/v2 from 2.1.1 to 2.2.0
Bumps [github.com/urfave/cli/v2](https://github.com/urfave/cli) from 2.1.1 to 2.2.0.
- [Release notes](https://github.com/urfave/cli/releases)
- [Changelog](https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/urfave/cli/compare/v2.1.1...v2.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:43:05 +00:00
dependabot[bot] ad2e46cb40
Bump github.com/manifoldco/promptui from 0.3.2 to 0.7.0
Bumps [github.com/manifoldco/promptui](https://github.com/manifoldco/promptui) from 0.3.2 to 0.7.0.
- [Release notes](https://github.com/manifoldco/promptui/releases)
- [Changelog](https://github.com/manifoldco/promptui/blob/master/CHANGELOG.md)
- [Commits](https://github.com/manifoldco/promptui/compare/v0.3.2...v0.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:40:57 +00:00
Matt Baer e796331de8
Merge pull request #346 from writeas/dependabot/go_modules/github.com/stretchr/testify-1.6.1
Bump github.com/stretchr/testify from 1.6.0 to 1.6.1
2020-07-28 12:39:26 -04:00
Matt Baer 9ff54f9944
Merge pull request #342 from writeas/dependabot/go_modules/github.com/mattn/go-sqlite3-1.14.0
Bump github.com/mattn/go-sqlite3 from 1.10.0 to 1.14.0
2020-07-28 12:38:13 -04:00
dependabot[bot] aa170d0c5a
Bump github.com/mattn/go-sqlite3 from 1.10.0 to 1.14.0
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.10.0 to 1.14.0.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.10.0...v1.14.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:25:28 +00:00
Matt Baer 42c6f3ca03
Merge pull request #345 from writeas/dependabot/go_modules/github.com/microcosm-cc/bluemonday-1.0.3
Bump github.com/microcosm-cc/bluemonday from 1.0.2 to 1.0.3
2020-07-28 12:22:47 -04:00
Matt Baer 191eac77ab
Merge pull request #352 from writeas/dependabot/go_modules/github.com/gorilla/schema-1.1.0
Bump github.com/gorilla/schema from 1.0.2 to 1.1.0
2020-07-28 12:15:52 -04:00
dependabot[bot] 00c47fa62f
Bump github.com/gorilla/schema from 1.0.2 to 1.1.0
Bumps [github.com/gorilla/schema](https://github.com/gorilla/schema) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/gorilla/schema/releases)
- [Commits](https://github.com/gorilla/schema/compare/v1.0.2...v1.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:10:53 +00:00
Matt Baer 29dc53aacd
Merge pull request #344 from writeas/dependabot/go_modules/github.com/hashicorp/go-multierror-1.1.0
Bump github.com/hashicorp/go-multierror from 1.0.0 to 1.1.0
2020-07-28 12:09:22 -04:00
Matt Baer 2f06b0b487
Merge pull request #341 from writeas/dependabot/go_modules/github.com/gorilla/feeds-1.1.1
Bump github.com/gorilla/feeds from 1.1.0 to 1.1.1
2020-07-28 12:06:19 -04:00
dependabot[bot] 5897ef7cab
Bump github.com/writeas/import from 0.2.0 to 0.2.1
Bumps [github.com/writeas/import](https://github.com/writeas/import) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/writeas/import/releases)
- [Commits](https://github.com/writeas/import/compare/v0.2.0...v0.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:26 +00:00
dependabot[bot] 99b2f41aa1
Bump github.com/go-sql-driver/mysql from 1.4.1 to 1.5.0
Bumps [github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) from 1.4.1 to 1.5.0.
- [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.4.1...v1.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:24 +00:00
dependabot[bot] 94094ed16d
Bump github.com/guregu/null
Bumps [github.com/guregu/null](https://github.com/guregu/null) from 3.4.0+incompatible to 3.5.0+incompatible.
- [Release notes](https://github.com/guregu/null/releases)
- [Commits](https://github.com/guregu/null/compare/v3.4.0...v3.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:19 +00:00
dependabot[bot] f278eccd14
Bump gopkg.in/ini.v1 from 1.55.0 to 1.57.0
Bumps [gopkg.in/ini.v1](https://github.com/go-ini/ini) from 1.55.0 to 1.57.0.
- [Release notes](https://github.com/go-ini/ini/releases)
- [Commits](https://github.com/go-ini/ini/compare/v1.55.0...v1.57.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:13 +00:00
dependabot[bot] 267c9df1c4
Bump github.com/stretchr/testify from 1.6.0 to 1.6.1
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.6.0...v1.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:06 +00:00
dependabot[bot] b569144624
Bump github.com/microcosm-cc/bluemonday from 1.0.2 to 1.0.3
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.2 to 1.0.3.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.2...v1.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:01:01 +00:00
dependabot[bot] 3d80b46bdc
Bump github.com/hashicorp/go-multierror from 1.0.0 to 1.1.0
Bumps [github.com/hashicorp/go-multierror](https://github.com/hashicorp/go-multierror) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/hashicorp/go-multierror/releases)
- [Commits](https://github.com/hashicorp/go-multierror/compare/v1.0.0...v1.1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:00:57 +00:00
dependabot[bot] cfaaffdc6c
Bump github.com/gorilla/feeds from 1.1.0 to 1.1.1
Bumps [github.com/gorilla/feeds](https://github.com/gorilla/feeds) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/gorilla/feeds/releases)
- [Commits](https://github.com/gorilla/feeds/compare/v1.1.0...v1.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 16:00:55 +00:00
Matt Baer 3294087abd
Merge pull request #333 from shleeable/patch-2
Create dependabot.yml
2020-07-28 11:59:43 -04:00
Shlee 48e85b7c63
Update dependabot.yml 2020-07-25 00:10:13 +08:00
Matt Baer 4c5f45f462
Merge pull request #273 from writeas/gopher
Add Gopher support

Closes T559
2020-07-23 11:48:25 -04:00
Matt Baer 6dbc753ecb Merge branch 'develop' into gopher 2020-07-23 11:47:49 -04:00
Matt Baer 1451fc1369 Use latest version of go-gopher library 2020-07-23 11:46:00 -04:00
Matt Baer dbe36861c3 Prevent Gopher server start on Private instance 2020-07-23 11:11:11 -04:00
Matt Baer 24fa3d6863
Merge pull request #301 from joicemjoseph/fix/tls-mysql
fix: #297 - tls for mysql connections
2020-07-23 10:03:49 -04:00
Matt Baer 94fee2e19e
Merge pull request #318 from writeas/ignore-collation-errs
Ignore "collation mix" errors in GetCollectionRedirect()
2020-07-23 10:02:26 -04:00
Matt Baer ede68d86a7
Merge pull request #324 from writeas/fix-unsynced-edits
Show warning in editor when local draft is out of date
2020-07-22 15:46:38 -04:00
Matt Baer 504a2a42aa
Merge pull request #329 from writeas/add-footer
Add footer and header element
2020-07-07 10:11:32 -04:00
Shlee b98903cff8
Update dependabot.yml 2020-07-02 20:28:40 +08:00
Shlee beef2b15a7
Create dependabot.yml 2020-07-02 20:24:26 +08:00
Neil Moore 94bcb91220 Add CORS header ('Access-Control-Allow-Origin') to 'AllReader'
Allows all API endpoints using 'AllReader' to be queryable by any
origin.
2020-06-23 20:33:30 -04:00
Matt Baer a25664bb97 Support post signatures
This enables users to add a signature to all blog posts, and update it from a single location.

Requires database migration with: writefreely db migrate

Closes T582
2020-06-23 16:24:45 -04:00
CJ Eller 591bb0866c Add footer and header element 2020-06-22 20:33:52 +00:00
prichier f6aa99e591 Add disable_password_auth option 2020-06-14 00:27:25 +02:00
Matt Baer 9624c4db00 Show warning in editor when local draft is out of date
Fixes #41
2020-06-11 11:45:12 -04:00
Matt Baer 507acc7e1c Support AP-style mentions of centralized social media accounts
This allows users to mention users on the following non-ActivityPub
social media sites:

- twitter.com
- medium.com

It also adds missing error handling in federatePost().
2020-06-08 13:50:43 -04:00
Matt Baer cceea03076 Ignore "collation mix" errors in GetCollectionRedirect() 2020-06-08 13:37:02 -04:00
prichier 724ab34006 Fix: option name from allow_logout to allow_disconnect 2020-06-06 23:52:26 +02:00
prichier fe7ff38bd8 Manage generic Oauth buttons on Account Settings
Add generic Oauth allow_logout option
2020-05-31 04:09:14 +02:00
Keturah Dola-Borg cd01a4459d Fix login page variable name
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:29:40 +02:00
Keturah Dola-Borg 405a2602ce Fix endpoint URI generation
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:39 +02:00
Keturah Dola-Borg 92d822b5c6 Remove redundant variable
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:35 +02:00
Keturah Dola-Borg 211d441090 Fix capitalisation of OAuth in display name
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:27 +02:00
Keturah Dola-Borg 7b71d455a8 Apply go fmt
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:21 +02:00
Keturah Dola-Borg 630ac1f7c0 Typo fix
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:16 +02:00
Keturah Dola-Borg badaffcd5c Add generic oauth to routes
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:20:08 +02:00
Keturah Dola-Borg cfd2165442 Add HTML for generic oauth login button
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-31 00:19:58 +02:00
Keturah Dola-Borg 75ca5cd417 Add generic oauth module
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:59:02 +02:00
Keturah Dola-Borg ee1ca48800 Add generic oauth client
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:58:57 +02:00
Keturah Dola-Borg 89f7946cb0 Add config/ini structures for generic oauth
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:58:51 +02:00
Keturah Dola-Borg 6174987c6a Adds generic oAuth bool & name string to login view.
Signed-off-by: prichier <pascoualito@gmail.com>
2020-05-30 23:58:42 +02:00
Matt Baer 5c94d23466
Merge pull request #312 from writeas/fix-no-to
Only log "No to!" when debugging
2020-05-29 05:53:24 -04:00
Matt Baer 2aa154d85c
Merge pull request #295 from writeas/optimize-drafts-fetch
Optimize Drafts retrieval
2020-05-29 05:52:58 -04:00
RJ722 53cb5c3837 core.less: Use a bigger font size for h2
The font-size for h2 was set to be 1.17em, which is also the font
size defined for h3 by user agent stylesheet. This lead to both
h2 and h3 being rendered in same size. Use 1.2em for h2.

Closes: #217
2020-05-25 07:01:34 +05:30
Matt Baer 9d854c17c1 Only log "No to!" when debugging
Fixes #311
2020-05-15 13:48:20 -04:00
Matt Baer 037fc40fb3
Merge pull request #307 from writeas/fix-numeric-alpha-hashtags
Support hashtags with numbers + letters
2020-05-07 11:33:30 -04:00
Matt Baer 5fe1dd1731 Add SQLite query for v9 migration 2020-05-06 14:08:48 -04:00
Matt Baer b9c467558c Return transaction Begin error in v9 migration 2020-05-06 14:08:25 -04:00
Matt Baer a0e517c224 Merge branch 'develop' into optimize-drafts-fetch 2020-05-01 15:00:51 -04:00
Matt Baer dc7b5df90e Update saturday library to support number+letter hashtags
Previously, a hashtag like #100DaysToOffload wouldn't automatically turn
into a clickable link. This fixes that by updating to the latest version
of the satuday library.
2020-04-28 10:50:51 -04:00
gytisrepecka 8675eb0f95
Merge develop branch into oauth-gitea and resolve pages/login.tmpl conflict. 2020-04-23 14:24:17 +03:00
Matt Baer 99d86a7489
Merge pull request #303 from writeas/fix-oauth-account-creation
Respect registration config on OAuth signup flow
2020-04-22 13:49:52 -04:00
Matt Baer 8e16bac12c Update README
Includes a better description, features, install and license sections, and a screenshot.
2020-04-22 13:43:29 -04:00
Matt Baer 7420039770 Merge branch 'develop' of github.com:writeas/writefreely into develop 2020-04-22 12:47:00 -04:00
Matt Baer f15acf3880 Reduce vertical margin around OAuth buttons 2020-04-22 09:27:33 -04:00
Matt Baer 308b1a7282 Remove "login" verbiage on OAuth signup page
Change it to reflect that this is the final step in the signup flow.
2020-04-22 09:27:19 -04:00
Matt Baer fd97539f85 Mention unset password on failed login
(when it applies)
2020-04-22 09:26:42 -04:00
Matt Baer cf3d5588c2 Move unique OAuth username creation to client-side
Now, on OAuth signup form, we create a unique username with random appended string only if there's a conflict.
Previously, this was always happening during the Slack OAuth flow. This has the benefit of preventing username collisions for all OAuth providers.
2020-04-22 09:17:25 -04:00
Matt Baer 6fc166174b
Merge pull request #299 from writeas/fix-friendica-federation
Fix federation with Friendica
2020-04-21 14:39:48 -04:00
Matt Baer 0c6d3e45e4 Update Go modules 2020-04-21 13:04:19 -04:00
Matt Baer b97038e696 Better describe usage stats in setup process 2020-04-21 13:01:39 -04:00
Matt Baer 37ccf69d81 Increase App Settings checkbox and select sizes 2020-04-21 13:01:08 -04:00
Matt Baer 0127e38ed0 Reorder App Settings page and improve descriptions 2020-04-21 13:00:36 -04:00
Matt Baer 7b7df5535e Run go fmt on oauth_test.go 2020-04-21 07:31:23 -04:00
Matt Baer 5400f416c0 Reduce db calls on normal invite-based signup
This removes an unnecessary database call after creating a user, and documents `db.CreateUser()` to make it clear that extra calls are unnecessary.
2020-04-20 18:21:01 -04:00
Matt Baer ca4a576c31 Support OAuth registration with invite code
This adds any OAuth login buttons to the invite signup page, stores the invite code for the flow duration, and associates the new user with it once successfully registered.

It enables invite-only instances with OAuth-based registration.
2020-04-20 18:18:23 -04:00
Matt Baer 93c2773412 Prevent account creation via OAuth when registration is closed 2020-04-20 15:26:53 -04:00
gytisrepecka 0e1459c6b2
Remove address variable - to make Locations use host value from config instead. 2020-04-17 17:12:06 +03:00
gytisrepecka 658310bc24
Remove constant giteaHost because there is no default URL for Gitea - must always use host from config.ini. 2020-04-17 17:07:45 +03:00
gytisrepecka ddd519f6b7 Merge branch 'develop' into oauth-gitea before making fixes to address pull request #293 feedback. 2020-04-17 15:33:18 +03:00
Joice M. Joseph 671c7e99a5 fix: #297 - tls for mysql connetions 2020-04-16 09:34:16 +05:30
Matt Baer 5e4ed5d9bc Remove extraneous @context fields on AP outbox 2020-04-15 12:30:50 -04:00
Matt Baer 1c5a0099b6 Fix empty date showing on collection 404 page 2020-04-14 07:27:44 -04:00
Matt Baer 5de4d2086b Optimize Drafts retrieval
This adds a database index to speed up retrieval of Drafts.

It is untested with SQLite.
2020-04-09 13:54:26 -04:00
Matt Baer e51e58386e Update versions in migrations.go 2020-04-09 13:49:44 -04:00
Matt Baer 9f1dd7a138 Use latest writeas/activityserve library
This fixes a 500 error / panic caused by ActivityPub actors without an
`inbox` or `outbox` attribute.
2020-04-09 13:00:29 -04:00
gytisrepecka c798a44f69
Added Gitea OAuth login and account management. 2020-04-03 13:26:59 +03:00
Matt Baer d6cb178eb6 Use writeas/impart v1.1.1
This doesn't change the actual underlying dependency; it simply uses the
current, most recent tag for the impart library.
2020-03-28 13:30:44 -04:00
Matt Baer c2417399a4 Bump version to 0.12.0 2020-03-27 12:53:48 -04:00
Matt Baer 8cc793142e
Merge pull request #288 from writeas/css-invalidation-release
Add CSS cache busting to templates in release
2020-03-27 12:28:52 -04:00
Matt Baer 599e7669d0 Add CSS cache busting to templates in release 2020-03-27 12:19:59 -04:00
Matt Baer dbd7eff7ea
Merge pull request #284 from writeas/high-load-error-page
Show 503 page on blogs under high load
2020-03-27 11:52:28 -04:00
Matt Baer 07debec8d5 Add new err func to wflib and sqlite builds 2020-03-27 11:48:20 -04:00
Matt Baer 8ad04c5187
Merge pull request #272 from writeas/novelfix
Fix Novel pagination
2020-03-27 11:38:20 -04:00
CJ Eller f11e6ed843
Apply review edits to chorus-collection 2020-03-27 11:22:35 -04:00
CJ Eller 540d716d29
Apply review edits 2020-03-27 11:22:16 -04:00
Matt Baer 1d25b38eb7
Merge pull request #282 from writeas/patch-ap-mentions
Clean up ActivityPub mentions
2020-03-27 09:07:57 -04:00
Matt Baer c3400242f0
Merge pull request #274 from writeas/private-instance-improvements
Private instance cleanup
2020-03-27 09:05:45 -04:00
Matt Baer 9c93e55e0a
Merge pull request #281 from writeas/accessibility-improvements
Accessibility improvements for readers
2020-03-26 10:09:28 -04:00
Matt Baer 0acc630af5
Merge pull request #243 from writeas/T713-oauth-account-management
OAuth account management
2020-03-24 15:09:49 -04:00
Matt Baer 491a1148ee Restyle OAuth account management section
- Break up linked / to-link sections
- Add logos for all services
- Lay out buttons horizontally
- Tweak the copy

Ref T713
2020-03-24 10:41:53 -04:00
Matt Baer 5d01f49ce9 Move /me/oauth/remove endpoint to /api/me/oauth/remove 2020-03-24 10:33:45 -04:00
Matt Baer d7d4cd907e Tweak "account already attached" verbiage 2020-03-24 09:09:14 -04:00
Matt Baer b25e80bb1b Show configured GitLab name on Account page
This includes the chosen GitLab display name in the button text.
2020-03-24 09:07:27 -04:00
Matt Baer 9dbba9d8c7 Make `handle` column in remoteusers NULL
This alters the V6 migration to make the column NULLable. Anyone who has already run this migration will need to manually update their database.
2020-03-24 07:59:00 -04:00
Nick Gerakines 048e8a5e13
Added error messaging when user attempts to attach a slack account to a user that already has the slack account attached. Added GitLab to settings page as oauth option. 2020-03-20 18:07:35 -04:00
Matt Baer f9cd87ae3a Log handle on GetProfilePageFromHandle err 2020-03-19 13:43:07 -04:00
Matt Baer cf4f08b264 Merge branch 'develop' into T713-oauth-account-management 2020-03-19 12:02:33 -04:00
Matt Baer 75a9df82ab
Merge pull request #283 from writeas/gitlab-oauth-cleanup
Clean up GitLab auth code
2020-03-19 12:00:56 -04:00
Matt Baer 9e25979e37 Run go fmt on modified GitLab files 2020-03-18 16:17:06 -04:00
Matt Baer 0285a9b0bd Show 503 page on collections under high load
This acknowledges "too many connections" and "max user connections"
errors in MySQL and propagates the error up the chain so we can notify
the user and return the correct HTTP code.
2020-03-18 16:14:05 -04:00
Matt Baer 79a968f425 Fix login.tmpl rendering
This passes in the correct field named GitlabDisplayName.
2020-03-18 16:05:26 -04:00
Matt Baer ac522ed600 Reuse mention regex
This makes the app less error-prone by avoiding a regexp.MustCompile()
call in the ActivityObject() method, saves CPU work, and reuses code.
2020-03-17 13:43:25 -04:00
Matt Baer 97aec9c158 Fix error / info logging around AP mentions
This fixes log formatting and makes verbiage consistent & concise.
2020-03-17 13:42:51 -04:00
Matt Baer 471a9e0602 Store AP handles consistently
This ensures handles are always stored without leading @ symbol.
2020-03-17 13:42:09 -04:00
Matt Baer a9bed9fea9 Prevent nil pointer panic from ActivityObject() method
Previously, we might potentially return a nil activitystreams.Object,
which would crash the app. This fixes that.
2020-03-17 13:14:29 -04:00
Matt Baer f4c106beaf Fix text contrast ratio in blockquote
This darkens the text slightly to get a contrast ratio over 4.5:1.
2020-03-17 10:31:44 -04:00
Matt Baer 3e1019f29d Fix text contrast ratio on pinned post links
This also darkens the text color of the blog description, to
differentiate it from the pinned links.
2020-03-17 10:24:30 -04:00
Matt Baer 06054a2cd7
Merge pull request #268 from writeas/noindex-invite-links
Add 'X-Robots-Tag: noindex' header to invite URLs
2020-03-16 13:38:18 -04:00
Matt Baer da0455198d
Merge pull request #194 from writeas/table-borders
lightly style tables in posts
2020-03-16 12:40:20 -04:00
Matt Baer 5b6e008118
Merge pull request #277 from paddatrapper/oauth-gitlab
Add Gitlab OAuth
2020-03-16 12:18:45 -04:00
Kyle Robbertze 26b6ed5f4f simplify gitlab oauth config 2020-03-16 15:11:03 +02:00
Matt Baer f126ac624a
Merge pull request #276 from writeas/dateless-pinned-posts
Don't show date on pinned post page
2020-03-13 12:15:57 -04:00
Kyle Robbertze c292512b9d add Gitlab OAuth 2020-03-12 10:50:55 +02:00
Matt Baer f76bfebfde Add dedicated Title field to WYSIWYG editor
This takes styling from the Classic Editor on Write.as.

It adds all application code for auto-saving the title, publishing it
with the post body, and including it in the word count.
2020-03-11 13:28:02 -04:00
Matt Baer 4b0833435f Restyle WYSIWYG editor
- Match light / dark theme
- Make editor fill the viewport
- Remove borders
- Add spacing in toolbar
2020-03-11 12:42:43 -04:00
Matt Baer 9780f0bbb9 Reformat prose.less 2020-03-11 12:13:24 -04:00
Matt Baer d277e283d5 Move ProseMirror styles to less dir
This keeps it consistent with all our other CSS.
2020-03-11 11:55:55 -04:00
Rob Loranger 7bccb3d7f1
fix not setting value to existing post on edit 2020-03-11 08:38:36 -07:00
Rob Loranger b3a541ab09
markdown preserved between edit/load/save 2020-03-11 07:59:22 -07:00
Rob Loranger ee712bbfaa
WIP: implement WYSIWYG editor w/ prosemirror 2020-03-11 07:18:03 -07:00
Rob Loranger cb1553d67e
add basic prosemirror instructions 2020-03-10 15:17:40 -07:00
Rob Loranger 58f27717be
update .gitignore to ignore node_modules 2020-03-10 15:12:21 -07:00
Rob Loranger f1f5dbb128
add prosemirror build environment 2020-03-10 15:11:32 -07:00
Matt Baer bad970c60a
Merge pull request #275 from writeas/fix-v4-migrations
Fix V4 + V5 SQLite migrations
2020-03-10 11:03:29 -05:00
Matt Baer 2aeb994b04 Don't show date on pinned post page
Ref T669
2020-03-09 15:01:01 -05:00
Matt Baer 172a6dba25
Merge pull request #263 from writeas/typography-alpha
Typography Improvements, Vol. I
2020-03-06 07:05:51 -06:00
Matt Baer eda267e30a Revert accidental h2 font-size change 2020-03-04 10:14:33 -06:00
Matt Baer 32f3fcb859 Skip IF [TABLE] NOT EXISTS on v4 migrations
We'd like these queries to fail correctly if the tables exist.
2020-03-03 11:48:04 -06:00
Matt Baer 61ddcff2c0 Add copyright notices to fixed files 2020-03-03 11:47:38 -06:00
Matt Baer 83b2c5a21b Fix unique index on v5 SQLite migration
This index needed a unique name in order for this query to succeed.
2020-03-03 11:46:51 -06:00
Matt Baer 471ef4d403 Fix "NOT NULL column with NULL" error in v5 SQLite migration
Previously, this migration would cause the error: "Cannot add a NOT NULL
column with default value NULL". This fixes that by setting the default
value for new columns to '' (empty string). It updates the query builder
to support this, too.
2020-03-03 11:43:46 -06:00
Matt Baer bb5da1d3f5 Break up v5 table ALTERs for SQLite
Combining all operations into a single query was causing problems in
SQLite. This fixes that by breaking them up into separate queries. It
also moves one column length change to only run on MySQL, since SQLite
doesn't need it.
2020-03-03 11:40:56 -06:00
Matt Baer f1ffcf96ec Remove user_id and remote_user_id constraints in v4&v5 migrations
It's not straightforward to remove these constraints in SQLite, so this
just skips it entirely. Since both of these migrations are part of the
same WF release, this should have minimal impact on admins.
2020-03-03 11:36:30 -06:00
Matt Baer 5b2612af54 Fix `created_at` default val in v4 migration for SQLite
This previously used a default timestamp value which caused the
migration to fail for SQLite databases.
2020-03-03 11:26:23 -06:00
Matt Baer 793380c1d9
Merge pull request #269 from writeas/lift-reader-limits
Adjust Reader limits and filtering
2020-03-02 17:05:57 -06:00
Matt Baer 2db6c33a41 Consolidate /signup page link logic
This moves logic for determining whether or not to show a "Sign up" page
on /signup (i.e. because the `/` route shows another, non-signup page)
into the AppCfg.SignupPath() method. It also changes various signup
links to use this value.
2020-03-02 16:34:44 -06:00
Matt Baer 151ec71163 Land on login form for private instances 2020-03-02 16:32:04 -06:00
Matt Baer 7aef706977 Fix Reader nav link on WFModesty + Private instance
(that is, hide the footer nav link when logged out)
2020-03-02 15:34:31 -06:00
Matt Baer c71d020e86
Merge pull request #261 from techknowlogick/update-cli
Refactor CLI
2020-03-02 14:43:05 -06:00
Matti R 2550804d93
return clear error 2020-03-02 14:07:37 -05:00
Matti R b6044120ef
go fmt & update per feedback 2020-03-02 13:59:32 -05:00
Matt Baer 6aa8de3a4b Add Gopher support
This adds gopher support to WriteFreely -- both single- and multi-user
instances. It is off by default, but can be enabled with the new
`gopher_port` config value in the `[server]` section.

When enabled, multi-user instances will show all public blogs at
gopher://[host]:[gopher_port]/ -- otherwise, blogs are accessible at
gopher://[host]:[gopher_port]/[blog]/

This is just a proof of concept for now. We still need to handle some
edge cases and different configurations, like private instances.

Ref T559
2020-03-01 20:12:47 -05:00
Matt Baer fca864c94a
Merge pull request #266 from writeas/fix-social-images
Fix image extraction for social metadata
2020-03-01 15:45:38 -05:00
Matt Baer 7283b17400
Merge pull request #267 from writeas/update-contributing-guide
Update Contributing guide
2020-03-01 14:27:02 -05:00
Matt Baer 4595d480ae
Merge pull request #264 from writeas/admin-dashboard-redesign
Admin dashboard redesign

Closes T694
2020-03-01 13:59:50 -05:00
Matt Baer cd2e725746
Merge pull request #270 from nkoehring/js-html-aliases
add js, jsx and html aliases for highlightjs
2020-03-01 13:06:02 -05:00
CJ Eller e140fe139f
Add {{end}} on line 97 2020-02-28 16:30:13 -05:00
CJ Eller 6027f7cfdc Fixed pagination for Novel 2020-02-28 21:24:52 +00:00
koehr b42760abab add js, jsx and html aliases for highlightjs 2020-02-26 22:12:06 +01:00
Matt Baer f903388a28 Fix admin nav rendering on user viewing 2020-02-25 15:03:29 -05:00
Matt Baer 9fe528bf47 Fix admin nav rendering on page editing 2020-02-25 15:01:24 -05:00
Matt Baer 303144fd24 Fix 500 when update checks disabled
Ref T572
2020-02-25 15:00:58 -05:00
Matt Baer 46dbb10433 Make Admin Settings page more user-friendly
- Add a description to each config item
- Change item names to make more sense

Ref T694
2020-02-25 14:28:34 -05:00
Matt Baer d17e82d34c Prevent update check from slowing app init
Previously, we'd wait for a response before finishing app
initialization, meaning an overall slower startup. This fixes that.

Ref T572
2020-02-25 14:20:32 -05:00
Matt Baer 05aad04b21 Limit Reader posts by count, not publish date
This changes the Reader to show the 250 most recent posts, with the
5-post-per-author limit still, instead of only posts from the last 3
months.
2020-02-25 13:13:36 -05:00
Matt Baer 8933076296 Add invite button to admin Users page header
Ref T694
2020-02-25 13:06:55 -05:00
Matt Baer 6f3b502e65 Add 'X-Robots-Tag: noindex' header to invite URLs
This instructs search engines to not index invite links.
2020-02-22 19:46:36 -05:00
Matt Baer e6e8cb5944 Add details and update CONTRIBUTING guide
This updates the guide to explain our current practices and processes.
It now focuses primarily on contributing code, instead of other types of
contributions.
2020-02-22 13:04:26 -05:00
Matt Baer 563ea5b25b Fix image extraction for URLs with query strings
Previously, image extraction wouldn't catch images with a query string
(or anything else) appended. This fixes that by parsing extracted URLs
and only checking the path for what looks like an image file.
2020-02-19 17:07:02 -05:00
Matt Baer 34d196376e Include extracted images in draft social metadata
Previously, we didn't extract images for draft posts. This fixes that.
2020-02-19 16:38:50 -05:00
Matt Baer 8e8eb3c563
Merge pull request #260 from writeas/fix-deletion-pleroma
Fix post deletion on Pleroma
2020-02-19 09:59:25 -05:00
Matt Baer 987c74c93a Add logging around automated update checks
Logs requests when --debug enabled, and always logs errors from the
check.

Ref T572
2020-02-15 12:58:45 -05:00
Matt Baer 37b7755c08 Tell admin that automated check failed when necessary
This keeps track when automated update check fails, and displays a
relevant message to the admin on /admin/updates

Ref T572
2020-02-15 12:56:33 -05:00
Matt Baer c2ece926e0 Show update notification in admin nav bar
When a WriteFreely update is available, indicate this on the "Updates"
navigation item

Ref T572
2020-02-15 12:53:47 -05:00
Matt Baer 389dc8b9db Show functional Updates page when checks are disabled
Ref T572
2020-02-15 12:19:06 -05:00
Matt Baer a06bb457de Change copy and design on WF Updates page
- Tweak the copy
- Include link to download latest release
- Change the version status design a bit
- Restyle some text
2020-02-15 11:59:47 -05:00
Matt Baer 48ca695c46 Show last update check time in local timezone 2020-02-15 11:57:55 -05:00
Matt Baer 68e992a55e Fix bad #status anchor in view-user.tmpl 2020-02-15 11:28:06 -05:00
Matt Baer 8e2eab5b73
Merge pull request #265 from shleeable/patch-1
Update Dockerfile - Go / Alpine base images
2020-02-15 10:48:13 -05:00
Shlee 7d15b799f0
Update Dockerfile 2020-02-15 23:27:13 +08:00
Matt Baer 04a76c4120
Merge pull request #262 from writeas/consistent-backend-pages
Consistent backend page design
2020-02-15 09:47:38 -05:00
Matt Baer 602cd80020 Fix mismatched span in user/admin/users.tmpl 2020-02-14 16:26:13 -05:00
Matt Baer 0d79057bae Rename ReleaseURL() to ReleaseNotesURL() 2020-02-14 16:13:54 -05:00
Matt Baer 84ab41697b Merge branch 'develop' into admin-dashboard-redesign 2020-02-14 14:50:23 -05:00
Matt Baer f79926031f
Merge pull request #175 from writeas/T572-check-updates
add update checks

Closes T572
2020-02-14 14:32:35 -05:00
Matt Baer 8364dce398 Merge branch 'develop' into T572-check-updates 2020-02-14 14:27:22 -05:00
Matt Baer b58464addb Optionally hide Monitor page in Admin nav
This adds a new config option that signifies the admin doesn't need to
see deeply technical things, like application resource usage. In the
[app] section, set forest = true to enable this.

Ref T694
2020-02-14 14:12:28 -05:00
Matt Baer 92da069ce4 Move admin dashboard sections into subpages
This moves app config to a "Settings" page and the application monitor
to a "Monitor" page. It also reworks the admin navigation bar a bit and
adds some instance stats on the dashboard.

Ref T694
2020-02-14 13:55:24 -05:00
Matt Baer 71224d68a2 Change line-height to 1.5 2020-02-14 08:40:06 -05:00
Matt Baer 8ce7d4c9fc Add isIgnorableError to database-lib.go 2020-02-12 11:39:39 -05:00
Rob Loranger 33474cb1f1
change to simpler style 2020-02-11 13:02:10 -08:00
Matt Baer 7fe281df69 Use NULL for new attach_user_id column
Ref T713
2020-02-10 15:24:39 -05:00
Matt Baer b1d006fcf2 Make Reader width consistent with other pages 2020-02-10 15:08:42 -05:00
Matt Baer 5d754176e0 Use h1 consistently on user pages
instead of h2s. This also removes odd one-time class usages on Account
Settings page.
2020-02-10 15:08:14 -05:00
Matti R b0f0de3dde
go mod tidy 2020-02-10 01:57:57 -05:00
Matti R 6173405794
switch to use urfave/cli for cli, which allows for auto-manpage generation and bash complete 2020-02-10 01:46:58 -05:00
Matt Baer f846cada4b Merge branch 'develop' into T713-oauth-account-management 2020-02-09 14:05:12 -05:00
Matt Baer 9fb12eea74
Merge pull request #240 from techknowlogick/acme-v2
update golang.org/x/crypto vendor to use acme v2
2020-02-09 13:36:13 -05:00
Matt Baer 42467fc9c1 Merge branch 'develop' into acme-v2 2020-02-09 13:32:45 -05:00
Matt Baer ab2b8dff7f
Merge pull request #249 from writeas/fix-activitypub-client
Improve resource use for federation
2020-02-09 12:03:00 -05:00
Matt Baer f406f894c5 Merge branch 'develop' into T319-user-delete-acct 2020-02-09 11:53:24 -05:00
Matt Baer d6c0026644 Merge branch 'develop' into T319-admin-delete-acct 2020-02-09 11:51:39 -05:00
Matt Baer 859702f3e7 Merge pull request #210 from writeas/rename-account-suspend
Rename Suspend status to Silence

Ref T661
2020-02-09 11:40:10 -05:00
Matt Baer 7023b74d12 Update calls and vars for Invites and elsewhere
Ref T661
2020-02-09 11:24:48 -05:00
Matt Baer 629d40b549 Fix collection rendering after merge 2020-02-09 11:24:16 -05:00
Matt Baer f70c1dfaa5 Merge branch 'develop' into rename-account-suspend 2020-02-09 11:14:51 -05:00
Matt Baer 468bbf2187 Merge branch 'develop' into rename-account-suspend 2020-02-09 11:14:14 -05:00
Matt Baer 252d59d3f7
Merge pull request #208 from writeas/silence-invites
add silenced warning on invites page

Ref T661
2020-02-09 11:02:42 -05:00
Matt Baer b78f64bad3 Don't fail Invite page rendering on IsUserSuspended check 2020-02-09 10:57:08 -05:00
Matt Baer 8cfffb5650 Disable form items on Invite page when silenced
Ref T661
2020-02-09 10:51:34 -05:00
Matt Baer 6d3803bfe8
Merge pull request #241 from writeas/post-time-fix
fixes imported post times
2020-02-09 10:23:15 -05:00
Matt Baer f902f65365 Merge pull request #259 from writeas/fix-edit-route-permissions
Require authenticated user on draft edit routes
2020-02-08 15:26:32 -05:00
Matt Baer 1a10bb3ed6 Merge pull request #252 from writeas/fix-mix-of-collations
Restrict /invite/{code} route to valid chars
2020-02-08 15:25:15 -05:00
Matt Baer fe82cbb96e Fix post deletion on Pleroma
See: https://git.pleroma.social/pleroma/pleroma/issues/1481

Fixes #223
2020-02-08 15:19:06 -05:00
Matt Baer f8a40fac4b
Merge pull request #202 from writeas/T319-delete-account
add account deletion

Ref T319
2020-02-08 15:00:49 -05:00
Matt Baer 666bd1b9d1 Show correct error when user not found in admin panel
Previously, it would show a 500. This also logs the real reason if it's
not a "not found" error
2020-02-08 14:46:05 -05:00
Matt Baer af14bcbb78 Clean up oauth_users table on account deletion
Ref T319
2020-02-08 13:51:38 -05:00
Matt Baer c9faff178d Don't float posts on account deletion
Ref T319
2020-02-08 13:51:14 -05:00
Matt Baer 9d360f0e41 Merge branch 'develop' into T319-delete-account 2020-02-08 13:42:46 -05:00
Matt Baer 9be05ef32e
Merge pull request #195 from writeas/activitypub-mentions
Send out ActivityPub mentions

Closes T627
2020-02-08 13:12:52 -05:00
Matt Baer 9589612d0e Add TODOs for improving GetProfilePageFromHandle() 2020-02-08 13:05:54 -05:00
Matt Baer ca4b0acf60 Fix error logging format in RemoteLookup 2020-02-08 13:05:09 -05:00
Matt Baer 457051106d Add u-url class and span in mention link
Ref T627
2020-02-08 13:04:23 -05:00
Matt Baer eac223158a Move remote user URL to /@/
from /mention:

Ref T627
2020-02-08 12:58:21 -05:00
Matt Baer 867eb53b35 Show 404 when remote user not found
This notifies the user that the remote user doesn't exist, instead of
showing a blank page.

Ref T627
2020-02-08 12:55:10 -05:00
Matt Baer 81edb739dd Fix mention links
by making them absolute, not relative.
2020-02-08 12:19:08 -05:00
Matt Baer bb63e64883 Clean up getProfilePageFromHandle
- Export the func
- Remove commented-out code
- Use log, not fmt for debug messages
- Remove named return parameters
- Use standard var naming schemes
- Fix spacing in queries and remove unnecessary chars
2020-02-08 12:10:47 -05:00
Matt Baer 68d63d3fef Merge branch 'develop' into activitypub-mentions 2020-02-08 11:51:18 -05:00
Matt Baer 1b8f62d143 Require authenticated user on draft edit routes
- /edit
- /meta
2020-02-06 17:44:02 -05:00
Matt Baer fec0eb2a0b
Merge pull request #251 from writeas/fix-memory-leak
Fix memory leak
2020-02-05 16:04:45 +01:00
Matt Baer 6e36868e92
Merge pull request #239 from techknowlogick/switch-xgo
Switch to a maintained fork of XGO
2020-02-05 14:56:22 +01:00
Matt Baer 1fd4230267
Merge pull request #248 from writeas/fix-drafts-html-entities
A minor Drafts page fix + improvement
2020-02-05 12:26:42 +01:00
Matti R 0ed3059bd7
add xgo to go mod 2020-01-31 16:34:36 -05:00
Matt Baer ff33c59f27
Merge pull request #180 from writeas/cache-control
Add Cache-Control headers on AP endpoints

Closes T693
2020-01-31 12:00:47 +01:00
Matt Baer 5452bf0c0d
Merge pull request #254 from writeas/fix-local-datetime
Fix date format in `datetime` attribute
2020-01-31 11:00:43 +01:00
Matt Baer 51700cc7da Ignore "mix of collations" error on invite SELECT
This adds the `isIgnorableError` method and calls it when error checking
in `GetUserInvite()`, returning "not found" if the rror comes up.
2020-01-30 10:36:29 +01:00
Matt Baer bc9455db4f Fix datetime attributes on read.tmpl 2020-01-30 10:20:50 +01:00
Matt Baer 5de2f633e1 Fix localdate.js not included on Tags page 2020-01-29 13:03:04 -05:00
Matt Baer 50901d2446 Fix date format in `datetime` attribute
Previously, the date format in this attribute for posts was invalid.
This caused local date rendering to fail in Firefox. This fixes that.

Closes #253
2020-01-29 13:01:21 -05:00
Matt Baer d6b7a5925f Restrict /invite/{code} route to valid chars
Previously, loading something like /invite/fFdblk😄 would return a 500,
due to a mix of collations in MySQL while SELECTing for an invite with
an ID of 'fFdblk😄'. This restricts the route to [a-zA-Z0-9] chars, to
prevent this.
2020-01-29 09:11:02 -05:00
Matt Baer 93dd2341c2
Merge pull request #191 from writeas/T670-local-time
show timestamps in local date/locale
2020-01-29 07:10:43 -05:00
Matt Baer 4d5f58a7e6 Fix date-based post header links
Posts without an explicit title render the date as the post header in
lists of posts (like on the blog index and tag pages). This updates
localdate.js to properly adjust those dates, too.
2020-01-29 06:42:32 -05:00
Matt Baer 3e902461f1 Merge branch 'develop' into T670-local-time 2020-01-29 06:24:46 -05:00
Matt Baer 5ddd73eff4
Merge pull request #247 from writeas/update-upgrade-script
update upgrade script for recent changes
2020-01-29 05:53:58 -05:00
Matt Baer b25cec8381 Update copyright in upgrade script 2020-01-29 05:49:12 -05:00
Matt Baer be0885698e Change "restarting" to "starting" in upgrade script 2020-01-29 05:47:19 -05:00
Matt Baer 8fce34b70b Tidy up Go mod files 2020-01-29 05:24:22 -05:00
Matt Baer ae1a892be0 Upgrade gorilla/sessions to v1.2.0
This gets rid of the gorilla/context dependency, which might have been
causing a memory leak.

We noticed some serious memory leakage on Write.as that seemed to point
to this library. One heap snapshot:

      flat  flat%   sum%        cum   cum%
  259.13MB 30.41% 30.41%   268.13MB 31.46%  net/textproto.(*Reader).ReadMIMEHeader
  105.71MB 12.40% 42.81%   105.71MB 12.40%  github.com/gorilla/context.Set
   78.53MB  9.21% 52.03%   125.53MB 14.73%  github.com/gorilla/sessions.(*Registry).Get
   55.51MB  6.51% 58.54%    82.52MB  9.68%  net/http.(*Request).WithContext
   38.01MB  4.46% 63.00%    38.01MB  4.46%  github.com/gorilla/mux.extractVars
      35MB  4.11% 67.11%       53MB  6.22%  context.WithCancel
   34.50MB  4.05% 71.16%    34.50MB  4.05%  context.WithValue
      27MB  3.17% 74.32%       27MB  3.17%  net/http.cloneURL
      26MB  3.05% 77.38%       26MB  3.05%  github.com/gorilla/sessions.NewSession
      18MB  2.11% 79.49%       18MB  2.11%  context.(*cancelCtx).Done
   16.50MB  1.94% 81.42%    16.50MB  1.94%  syscall.anyToSockaddr
      14MB  1.64% 83.07%       47MB  5.52%  github.com/gorilla/sessions.(*CookieStore).New
   13.50MB  1.58% 84.65%    51.51MB  6.04%  github.com/gorilla/mux.(*Route).Match
   11.67MB  1.37% 86.02%    13.21MB  1.55%  regexp.(*Regexp).replaceAll
    9.72MB  1.14% 87.16%    22.94MB  2.69%  regexp.(*Regexp).ReplaceAllString
    9.50MB  1.11% 88.28%   115.21MB 13.52%  github.com/gorilla/sessions.GetRegistry

With the help of these articles, we tracked it down to this dependency,
and upgraded the library, which seems to have completely fixed the issue
so far:

https://rover.rocks/golang-memory-leak/
https://medium.com/@walterwu_22843/golang-memory-leak-while-handling-huge-amount-of-http-request-35cc970cb75e

This should fix #133
2020-01-29 04:56:23 -05:00
Matt Baer bf8dcff01e Quit AP goroutine early when there's no "to"
Previously, we'd sleep for 2 seconds and then return for no reason. This
fixes that.
2020-01-27 09:23:50 -05:00
Matt Baer 8d3e755c8f Return pointer to http.Client in activityPubClient() 2020-01-23 12:03:23 -05:00
Matt Baer bc9843dfa3 Add timeout on ActivityPub requests 2020-01-23 11:47:35 -05:00
Matt Baer fe26594e8c
Merge pull request #245 from writeas/fix-editor-open-access
Require authenticated user for editor access
2020-01-20 15:42:24 -05:00
Matt Baer 30032e74a0 Add helpful text on Drafts page 2020-01-20 15:25:37 -05:00
Matt Baer b336e95e12 Render HTML entities in Drafts list
Previously, we'd show the raw HTML entities in the summaries of Draft
posts, instead of rendering them. This fixes that.
2020-01-20 15:20:45 -05:00
Rob Loranger 2c075c0347
update upgrade script for recent changes
changes accounted for
- the tar directory structure had changed to use a subdirectory
- there are now multiple linux targets released

bugs
- the service must be stopped before replacing the binary
- migrations were not being run during an upgrade
2020-01-19 15:57:58 -08:00
Matt Baer 8e09e72979 Require authenticated user for editor access
Previously, anyone could access the editor even if they weren't logged
in. They couldn't do much in that case (publishing would fail), but it
could potentially cause some confusion.

Now, users will be sent to the login page, and then redirected back to
the editor once successfully logged in.
2020-01-16 14:50:29 -05:00
Matt Baer b9914dd65a
Merge pull request #244 from writeas/oauth-signup-tweaks
OAuth signup form tweaks

Resolves T715
2020-01-16 14:46:48 -05:00
Matt Baer c1ec6b2605 Fix copyright years in oauth_slack.go 2020-01-16 14:43:32 -05:00
Matt Baer dcdd4dd1ef Add and update copyright notices 2020-01-16 14:39:18 -05:00
Matt Baer 803dd78df5 Remove Password field from OAuth signup page
This removes a bit of friction.

Ref T715 T712
2020-01-16 14:30:09 -05:00
Matt Baer f7dabd39c2 Skip password requirement on OAuth signup
This makes it possible to complete OAuth signup without creating a
password on the WriteFreely instance.

A user can then add a password to their account through their Account
Settings page without any admin action (all of this logic is already in
place).

Ref T715 T712
2020-01-16 14:25:33 -05:00
Matt Baer b5a38efd28 Fall back to username as coll title on OAuth signup
This uses the given username as the Display Name / Collection Title if a
user doesn't give one -- as might happen when authenticating with
Write.as.

Ref T712
2020-01-16 14:09:42 -05:00
Matt Baer 130c9eb747 Change Blog Title to Display Name in OAuth signup
Ref T712
2020-01-16 13:58:14 -05:00
Matt Baer 6842ab2e3b Rename collTitle from alias
"alias" is the name of a different collection field, so this renames the
variable internally to make things clearer.
2020-01-16 13:50:37 -05:00
Matt Baer 4d5c89e7ef Fix false login state on OAuth signup page
Having a `Username` field populated in the page data tells the base
template to display navigation that only a logged in user should see. So
this renames the field to `LoginUsername`, similar to our login.tmpl
page.

Ref T712
2020-01-16 13:37:44 -05:00
Matt Baer 33a6129d1e Add async username check on OAuth signup form
This checks the user's inputted username as they type it, and prevents
form submission if the name is taken.

Ref T712
2020-01-16 13:18:23 -05:00
Matt Baer f2f779e4a2 Generate non-colliding usernames in all lowercase
All usernames should be lowercase, so this generates any username suffix
(in cases of collision) with only lowercase letters. It also removes
vowels to prevent bad 5-letter words from forming.

Ref T712
2020-01-16 12:29:01 -05:00
Matt Baer d297859705 Reserve the username "oauth" 2020-01-16 12:18:21 -05:00
Nick Gerakines 5d834c1cd2 Minor code cleanup on settings page to improve oauth account management UI. T713 2020-01-15 13:37:57 -05:00
Nick Gerakines c0317b4e93 Implemented oauth attach functionality, oauth detach functionality, and required data migration. T713 2020-01-15 13:16:59 -05:00
Rob Loranger 571460f08d
move timezone correction to client side 2020-01-15 09:04:38 -08:00
Rob Loranger 0766e6cb36
fixes imported post times
changes the client side to round the unix time to avoid floats

alters the time to match the client time zone on the server side
2020-01-14 10:44:56 -08:00
Matti R 80cffbb3ec
update golang.org/x/crypto vendor to use acme v2
also run go mod tidy to clean up module files
2020-01-14 12:46:52 -05:00
Matt Baer 75e2b60328
Merge pull request #172 from writeas/import-text
add basic text file imports

Resolves T609
2020-01-14 12:33:57 -05:00
Matt Baer 3e97625cca Fix Unix timestamps on client during import
File API gives timestamp in milliseconds, not seconds, so this converts
it on the client-side and sends it the correct time to the server.

Ref T609
2020-01-14 12:26:02 -05:00
Matt Baer 65e2e5126b Revert "Fix unix timestamp in file upload"
This reverts commit 2b066997d1.
2020-01-14 12:24:57 -05:00
Matt Baer 2b066997d1 Fix unix timestamp in file upload
File API gives timestamp in milliseconds, not seconds, so this converts
it correctly.

Ref T609
2020-01-14 12:23:01 -05:00
Matti R 98ca449b66
add arm-6 2020-01-14 12:02:43 -05:00
Rob Loranger aae2f28bb6
pass original file modified date for imports 2020-01-14 08:59:30 -08:00
Matti R f4c6ce76dd
Switch to a maintained fork of XGO 2020-01-14 11:55:55 -05:00
Matt Baer c7b797929b
Merge pull request #238 from writeas/oauth-bugfix-alias-signature
OAuth alias field not set correctly
2020-01-14 10:59:48 -05:00
Nick Gerakines f7995bee48 Fixing bug where display name was not set correctly. 2020-01-14 10:28:40 -05:00
Matt Baer 659392ac4f
Merge pull request #235 from writeas/date-stamps
Add dates to blog posts

Resolves T669
2020-01-14 09:51:12 -05:00
Matt Baer c00daf64b0
Merge pull request #236 from writeas/oauth-provider-callback-hotfix
Fixing bug in oauth callback URL registration.
2020-01-14 09:12:28 -05:00
Nick Gerakines a77d403dfb
Fixing bug in oauth callback URL registration.
Fixing a bug in the oauth callback URL registration where the lack of provider context was overwriting the previous oauth callback route registration call.
2020-01-10 16:16:43 -05:00
Matt Baer 9958a1122b Show published date on post pages if Blog
Dates now display on blog post pages if the collection's chosen display
format is "Blog". It updates the chorus-collection-post template to now
respect this value (previously, it always showed the date).

Ref T669
2020-01-09 16:50:02 -05:00
Matt Baer 812136357e Move Format from DisplayCollection to CollectionObj 2020-01-09 16:48:22 -05:00
Matt Baer f5d21c8c1a Reorder federation check logic on upload
Ref T609
2020-01-09 13:29:30 -05:00
Matt Baer 18d3456a23 Tweak user-facing upload errors + internal logs
Ref T609
2020-01-09 13:29:07 -05:00
Matt Baer 03eeca179e Fix potential resource leaks from defer calls in for loop
This moves file operations inside the `for` loop into an anonymous func,
so the `defer` calls don't wait until the end of the handler call to
actually execute.

Ref T609
2020-01-09 12:36:58 -05:00
Matt Baer 6860c0a3ff Fix collection logic on import
- Only retrieve a collection from database if an alias is submitted
- Only call GetCollection() once (previously, it was inside the loop)
- Return error if user doesn't own the collection

Ref T609
2020-01-09 12:08:06 -05:00
Matt Baer 5b7f37aed8 Restyle Import page
- Changes Import link location in dropdown menu
- Makes design consistent with Invite People page (and extracts some
  common CSS into core.less)
- Selects the user's first blog by default in the dropdown
- Changes the copy a bit

Ref T609
2020-01-09 11:16:26 -05:00
Matt Baer a2a9f60976
Merge pull request #232 from writeas/T712-oauth-registration-improvements
OAuth registration improvements

Resolves T712
2020-01-08 14:09:32 -05:00
Nick Gerakines 8ddfce4f19 oauth signup page changes per PR feedback. T712 2020-01-07 22:13:29 -05:00
Nick Gerakines 6d79ed3cfd Updating oauth form validation per PR feedback. T712 2020-01-07 21:52:55 -05:00
Nick Gerakines 5e76565271 Code cleanup per PR feedback. T712 2020-01-07 21:52:55 -05:00
Matt Baer e5671cd1e6 Fix GetCollections() call 2020-01-07 16:51:40 -05:00
Matt Baer be76f865a4 Merge branch 'develop' into import-text 2020-01-07 16:35:23 -05:00
Matt Baer d66091a356 Bump Travis build to Go 1.13 2020-01-07 16:27:25 -05:00
Nick Gerakines 28cf4dd5f5 Added state location register hook. T712. 2020-01-07 15:22:25 -05:00
Matt Baer 9be534038b
Merge pull request #233 from writeas/markdown-api-rename
Rename base_url to collection_url in Markdown API
2020-01-05 12:50:07 -05:00
Matt Baer 9fb8de48d4 Rename base_url to collection_url in MD API
Ref T519
2020-01-05 11:22:22 -05:00
Matt Baer 77e0126808 Move and restyle OAuth login links
- Move them above local login form
- Restyle as side-by-side buttons

Ref T712
2020-01-05 11:00:58 -05:00
Matt Baer 5249456ec6 Add .btn.cta link styles 2020-01-05 11:00:11 -05:00
Nick Gerakines 6429d495a2 Implemented /oauth/signup. T712 2020-01-03 13:50:21 -05:00
Matt Baer a4579719cd
Merge pull request #197 from writeas/markdown-API
add basic API endpoint for rendering markdown

Ref T519
2020-01-03 13:47:50 -05:00
Matt Baer 97b25628fb
Merge pull request #230 from writeas/T710-oauth-slack
OAuth Provider: Slack

Resolves T710
2020-01-03 13:32:05 -05:00
Nick Gerakines a4e373065c Merge branch 'T710-oauth-slack' into T712-oauth-registration-improvements 2020-01-03 11:39:40 -05:00
Nick Gerakines 0b229a5ede Updating oauth user lookup call as per PR feedback. T710 2020-01-03 11:31:38 -05:00
Nick Gerakines 6d8da2bffd Encrypting email from oauth signup as per PR feedback. T710 2020-01-03 11:28:06 -05:00
Matt Baer 2486b3c100
Merge pull request #231 from writeas/oauth-wrapper
Pass OAuth requests through OAuth handler
2020-01-02 16:55:18 -05:00
Nick Gerakines 6823f10821 Updated unit tests to reflect handler wrapper. 2020-01-02 16:29:23 -05:00
Nick Gerakines 2aea9560bc Merged T710-oauth-slack into oauth-wrapper. 2020-01-02 16:19:26 -05:00
Nick Gerakines 31e2dac118 Adding slack display name to inspect response to use in user creation as per PR feedback. T710 2020-01-02 15:55:28 -05:00
Nick Gerakines cd5fea5ff1 write.as oauth client cleanup as per PR feedback. T710 2020-01-02 15:50:54 -05:00
Nick Gerakines ee1473aa56 Rolling back v1 migration change as per PR feedback. T710 2020-01-02 15:36:21 -05:00
Nick Gerakines 37f0c281ab Removing test skip as per PR feedback. T710 2020-01-02 15:35:15 -05:00
Nick Gerakines b985292b18 First take at template updates. T712 2020-01-02 15:33:39 -05:00
Nick Gerakines 9170c84617 Merged in final changes from PR 225 into T705-oauth-slack. T710 2019-12-31 11:48:08 -05:00
Matt Baer f343cebce7
Merge pull request #225 from writeas/T705-oauth
OAuth 2 support

Resolves T705
2019-12-31 11:34:13 -05:00
Nick Gerakines b5f716135b Changed oauth table names per PR feedback. T705 2019-12-31 11:28:05 -05:00
Matt Baer ad5f72d8a4 Merge branch 'T705-oauth' into oauth-wrapper 2019-12-30 18:47:40 -05:00
Matt Baer 6bcc4cfa46 Check for error response in code exchange
This checks to see if we get a response with a populated `error` field
in exchangeOauthCode(). If so, we return that error message as an error,
to ensure the callback logic doesn't continue with a bad response.

Ref T705
2019-12-30 18:25:24 -05:00
Matt Baer 39d0f1de98 Add logging in viewOauthCallback()
Ref T705
2019-12-30 18:23:45 -05:00
Matt Baer af23e28d05 Pass OAuth requests through new OAuth handler
This gives us our standard logging and passes around errors with
impart.HTTPError.

Ref T705
2019-12-30 18:14:01 -05:00
Nick Gerakines cf87ae9096 Code cleanup in prep for PR. T710 2019-12-30 13:32:06 -05:00
Nick Gerakines 462f87919a Feature complete on MVP slack auth integration. T710 2019-12-28 15:15:47 -05:00
Nick Gerakines 13121cb266 Merging T705-oauth into T710-oauth-slack. T705,T710 2019-12-27 13:40:11 -05:00
Nick Gerakines 4266154749 Code cleanup from PR 255 feedback. T705 2019-12-27 13:35:48 -05:00
Nick Gerakines bf3b6a5ba0 Unit tests, integration testing, and code cleanup for oauth support. Part of T705. 2019-12-23 14:30:32 -05:00
Nick Gerakines 7a0863f71b Added oauth handlers and tests with mocks. Part of T705. 2019-12-19 11:51:19 -05:00
Rob Loranger dae65b7d1f
retain output structure in response 2019-12-19 08:28:06 -08:00
Matt Baer dc1af91cf6
Merge pull request #213 from yalh76/arm64
Add ARM64 Build
2019-12-18 11:47:42 -05:00
Matt Baer e16ea3b419
Merge pull request #224 from writeas/hotfix-0.11.2
v0.11.2 hotfix
2019-12-17 21:39:19 -05:00
Matt Baer 8dc1ef0fdb
Merge pull request #215 from writeas/prevent-fail-on-suspend-check
Prevent failed requests on failed user silence check
2019-12-17 21:12:58 -05:00
Matt Baer ed40e9dea5
Merge pull request #222 from writeas/fix-template-typo
Fix password-protected page template
2019-12-17 21:11:33 -05:00
Matt Baer 6afafa4d67 Fix whitespace 2019-12-17 21:10:39 -05:00
Matt Baer cfea887b78 Suppress "user not found" log when post not found
This also saves a user suspension check when a post isn't found.
2019-12-17 20:58:32 -05:00
Rob Loranger 26d906ae92
clean up responses and logging, change endpoint
- return an error with invalid request types
- simplify json decoding
- return error and success consistent with app conventions
- endpoint change from /api/generate/markdownify to /api/markdown
- fix nil pointer dereference when passing a base_url
2019-12-17 12:27:34 -08:00
Rob Loranger 4c0e4d04c1
404 for protected posts when previously authorized
a user who had previously authenticated on a protected collection would
still see the post after the owner was silenced, with a banner meant for
the owner displayed.
2019-12-17 10:42:31 -08:00
Matt Baer aa405bc57c Remove "silenced" warning on password-collection.tmpl
Logged-in users never see this particular page, so it's not needed here.
2019-12-17 13:11:32 -05:00
Matt Baer 6f6204a849 Return 404 for suspended pass-protected colls
Previously, any password-protected collection on a suspended account
would prompt visitors for a password, and *then* take them to the "not
found" page. This fixes that.
2019-12-17 13:02:21 -05:00
Matt Baer 6a5d49eeb7
Merge pull request #218 from writeas/fix-empty-hostname-editing
Suppress log when editing a collection post or its metadata
2019-12-11 16:35:07 -05:00
Matt Baer 0b701c5f7f Update "account silenced" alert on edit-meta
Use "silenced" phrasing instead of "suspended"
2019-12-07 09:08:37 -05:00
Matt Baer acb8f5fe5d Fix broken password-collection template
Fixed "user-supsended" to "user-suspended"
2019-12-07 09:06:31 -05:00
Matt Baer 5259c4fcdf Federate posts without a double-line break as Notes
This proof-of-concept sends out posts that don't include a double-line
break as Notes -- otherwise they go out as Articles, as before.

Inspired by the discussion here:
https://socialhub.activitypub.rocks/t/resolving-the-note-vs-article-distinction/258
2019-12-04 19:40:52 -05:00
Matt Baer d8f77585f5 Suppress log when editing post or its metadata
This adds the instance's Hostname to the collection data loaded when
editing a collection post or its metadata. While not technically needed
in this situation, it suppresses an alarming error log.

Resolves #216
2019-12-01 06:16:12 -05:00
Matt Baer a266d8e032 Update IsJSON call in handleRenderMarkdown() 2019-11-29 08:12:54 -05:00
Matt Baer 5fa164d5cf Merge branch 'develop' into markdown-API 2019-11-29 08:12:17 -05:00
Matt Baer 8c1bf2ddd5 Merge branch 'markdown-API' into activitypub-mentions 2019-11-28 10:31:35 -05:00
Matt Baer a513c99a1e Merge branch 'hotfix-0.11.2' into activitypub-mentions 2019-11-27 18:20:04 -05:00
Matt Baer ae5bbd273d Fix mention URL on multi-user instances
Previously, links would go to /user/mention:@me@this.tld instead of
/mention:@me@this.tld
2019-11-27 17:54:17 -05:00
Matt Baer 88a3ed7878
Merge pull request #214 from writeas/fix-hostname-fediverse
Fix fediverse post IDs
2019-11-27 17:02:20 -05:00
Matt Baer 59d892e486
Merge pull request #212 from writeas/fix-suspension-check
Fix suspension check in fetchPost()
2019-11-27 16:59:53 -05:00
Matt Baer 181af8c5c8 Update httpsig and activityserve
This fixes activityserve crashes caused by mentioning WriteFreely
instances.
2019-11-27 16:37:52 -05:00
Matt Baer af6e5dea3a Merge branch 'develop' into import-text 2019-11-26 17:41:29 -05:00
Matt Baer bbb7b28110 Bump Travis build to Go 1.12
This fixes the `undefined: strings.ReplaceAll` build error.
2019-11-26 13:32:33 -05:00
Matt Baer d8df15855c Merge branch 'develop' into activitypub-mentions 2019-11-26 13:19:20 -05:00
Matt Baer 342c3cde89 Bump version to 0.11.2 2019-11-26 13:15:31 -05:00
Matt Baer 44a6703742 Prevent failed requests on failed user silence check 2019-11-26 13:14:52 -05:00
Matt Baer c81927a69f Fix empty hostname when fetching AS post via ID
Previously, fetching ActivityStreams data about a post via
/api/posts/ID, instead of /api/collections/ALIAS/posts/SLUG wouldn't
include the instance's base URL. This fixes that.
2019-11-26 12:59:15 -05:00
yalh76 36df095dac Add ARM64 Build 2019-11-21 21:45:06 +01:00
Matt Baer 8d8e671a07 Fix suspension check in fetchPost()
Previously, this check would return a "user not found" error when
retrieving a collection post by its post ID, e.g. /api/posts/abc123
instead of /api/collections/demo/posts/my-slug -- this happens
particularly when `Announce`ing a post in the fediverse. This change
fixes that.
2019-11-19 09:59:13 +09:00
Matt Baer bd99044e9c Fix 500 on tags page
This fixes a panic from a nil user when calling u.IsSuspended().
Instead, this checks and calls IsSuspended() on `owner`.
2019-11-12 20:01:14 +09:00
Matt Baer 2899d98cfd Fix collection post 500 when not logged in
This reverts some code from 5429ca4a, which broke collection post
loading on blog posts when not logged in.
2019-11-12 19:43:41 +09:00
Matt Baer 278e4f6242 Bump version to 0.11.1 2019-11-12 16:53:52 +09:00
Matt Baer 3d49baf39a Improve non-chorus site-wide header
This adds a Reader tab when necessary while logged in, and generally
keeps the navigation consistent for logged-in users, particularly in
regard to the Reader:

- Now includes user buttons and dropdown
- Makes header on user pages consistent with Reader page
2019-11-12 16:49:38 +09:00
Rob j Loranger 474a5d908d
Merge pull request #209 from writeas/fix-host-single-user-templates
Fix collection template issues introduced in #205
2019-11-11 15:26:38 -08:00
Rob Loranger 7e014ca659
Rename Suspend status to Silence
This changes all variables and functions from using Suspend{ed} to using
Silence{d} as well as documentation, errors and logging.
2019-11-11 15:25:19 -08:00
Matt Baer 80362000fe Skip logging default pad template fallback
This reduces unnecessary logging by not showing the "no template" line
when the `editor` config value is empty (default).
2019-11-12 08:07:51 +09:00
Matt Baer 79f35a0ccd Fix collection template issues introduced in #205
This fixes a template rendering issue caused by bad references to $.Host
in pinned posts links on single-user instances.

Closes #207
2019-11-12 08:03:00 +09:00
Rob Loranger 9b69de166f
add silenced warning on invites page 2019-11-11 14:25:34 -08:00
Matt Baer bca678aee5
Merge pull request #174 from writeas/T661-disable-accounts
Add account suspension features
2019-11-12 02:08:30 +09:00
Matt Baer 53586d9cb8 Merge branch 'develop' into T661-disable-accounts 2019-11-12 01:46:37 +09:00
Matt Baer 5839c2ac4d
Merge pull request #192 from writeas/T695-reset-user-pass
Resolves T695
2019-11-12 01:39:09 +09:00
Matt Baer 8f24da94a6 Bump version to 0.11.0 2019-11-12 00:47:49 +09:00
Matt Baer 5644e8d251 Fix "silenced" alert styles on more pages
- Tagged posts
- Collection index

Ref T661
2019-11-12 00:41:45 +09:00
Matt Baer 7f96e8c384 Rename UserSuspended to UserSilenced
Some of the work needed to have the backend match user-facing wording.

Ref T661
2019-11-12 00:41:25 +09:00
Matt Baer c3f76a3ab8 Change "suspend" to "silence" where user-facing
This puts the verbiage more in line with what the feature does, and
leaves room for other moderation controls in the future.

NOTE: this includes no backend refactoring, which may be confusing. We
should rename things to fit ASAP.

Ref T661
2019-11-12 00:22:33 +09:00
Matt Baer f7550a0da8 Change more suspension check logic
From u.Status == UserSuspended to u.IsSuspended()

Ref T661
2019-11-12 00:04:36 +09:00
Matt Baer d4206cd5f8 Move to web-core v1.2.0 2019-11-11 23:19:34 +09:00
Matt Baer a9b5bb2f6b Fix reset user's email address display
Previously, this had bad template logic and showed the wrong email address.

Ref T695
2019-11-11 21:40:49 +09:00
Matt Baer d5dd007ff7 Change Reset Password button style
Ref T695
2019-11-11 21:37:02 +09:00
Matt Baer 3e8d1014d9 Tweak admin reset confirmation copy
Also updates some whitespace in the JS.

Ref T695
2019-11-11 18:04:20 +09:00
Matt Baer 422c16f39a Tweak admin user pass reset success copy
This also adjusts the style and includes the user's password, so the
admin can easily notify them.

Ref T695
2019-11-11 18:03:19 +09:00
Matt Baer f673f9b562 Reset password to sorta-sensical string
This resets user password to something random that also reminds the user
they should change it immediately after logging in, instead of a
completely random jumble of characters.

Ref T695
2019-11-11 18:01:08 +09:00
Matt Baer 6d4ec0b17d Remove extra OwnUserPage field
Move logic into template, rather than add another field to the page.

Ref T695
2019-11-11 16:06:03 +09:00
Matt Baer 6e09fcb9e2 Change password reset endpoint to /admin/user/{Username}/passphrase
Ref T695
2019-11-11 16:02:22 +09:00
Matt Baer 38f3eec8e0 Merge branch 'develop' into T572-check-updates 2019-11-11 15:45:47 +09:00
Matt Baer a65917ae2e
Merge pull request #205 from writeas/fix-csv-export
Fix URLs in CSV exports
2019-11-11 15:42:45 +09:00
Matt Baer 2c2ee0c00c Tweak "suspended" notification copy 2019-11-11 15:16:04 +09:00
Rob Loranger f66d5bf1e8
use .Host instead of adding .Hostname 2019-11-09 11:41:39 -08:00
Rob Loranger c0b75f6b65
pass hostname to canonical url in post templates
the change to take a hostname in Post.CanonicalURL broke a few template
using that function. This adds a Hostname string to the Post being
passed to templates and passes it to calls to Post.CanonicalURL
2019-11-08 08:47:03 -08:00
Matt Baer e1149cd1e9 Fix URLs in CSV exports
This includes the instance's hostname in calls to export a CSV file and
PublicPost.CanonicalURL().

It also fixes a panic in that method during CSV export caused by draft
posts.
2019-11-07 17:25:42 +09:00
Matt Baer 619b10c3e5 Fix "suspended" message location on Drafts
Previously it was above the header.

Ref T661
2019-11-07 17:10:17 +09:00
Matt Baer 280c32afdc Confirm suspension before submitting the form
This also includes a bit of explanation about what suspending a user
actually does.

Ref T661
2019-11-07 16:59:02 +09:00
Matt Baer c9f7219831 Move user status in list out of <a>
The link here is a little redundant, and might make people think that it
actually changes the status by clicking on it.
2019-11-07 16:49:52 +09:00
Matt Baer da7dcfee6a Move admin template IsSuspended logic into method
This adds a User.IsSuspended() method and uses it when displaying the
user's status on admin pages, instead of doing a magic number check.
This should also help in the future, in case this logic ever changes.

Ref T661
2019-11-07 14:07:00 +09:00
Matt Baer 3167e19b77
Merge pull request #189 from writeas/T697-blog-default
Default to user's first blog instead of draft.

Resolves T697
2019-11-07 13:46:17 +09:00
Matt Baer fea62b14ce
Merge pull request #185 from writeas/accept-json
update IsJSON to check for Accept header
2019-11-07 13:30:34 +09:00
Matt Baer fcf074cf40
Merge pull request #166 from writeas/159-follow-panic
fix panic on duplicate remoteuser key
2019-11-07 12:26:57 +09:00
Rob Loranger fc553d277f
add admin user account deletion
this adds a section to the admin user view to delete the account and a
handler to process the request.
2019-11-05 12:22:58 -08:00
Rob Loranger 482e632ca9
add user account delete UI 2019-11-05 12:22:27 -08:00
Rob Loranger b83af955c3
remove wrapper over db.DeleteAccount 2019-11-05 12:20:07 -08:00
Rob Loranger 41166e5c35
CLI delete account by username and delete posts
this changed the CLI flag to use the username instead of the userID
leaving the underlying database function as is.

also now posts are all deleted with no option to skip as this is likely
never needed.
2019-11-05 09:14:20 -08:00
Matt Baer bf4f879383 Update hosting options in README
Now: Write.as Pro and Write.as for Teams
2019-11-04 14:06:24 -05:00
Rob Loranger c87ca11a52
add account deletion
CLI only but backend supports calls from app.db.DeleteAccount already

takes --delete-account user_id_number with optional --posts to also
delete posts. if --posts is omitted all user posts will be updated to
anonymous posts
2019-10-31 15:20:18 -07:00
Rob Loranger 5429ca4ab0
add check for suspended user on single posts
also fix logic bug in posts.go viewCollectionPost checking the page
owner
2019-10-25 13:40:32 -07:00
Rob Loranger f85f0751a3
address PR comments
- update error messages to be correct
- move suspended message into template and include for other pages
- check suspended status on all relevant pages and show message if
logged in user is suspended.
- fix possible nil pointer error
- remove changes to db schema files
- add version comment to migration
- add UserStatus type with UserActive and UserSuspended
- change database table to use status column instead of suspended
- update toggle suspended handler to be toggle status in prep for
possible future inclusion of further user statuses
2019-10-25 12:04:24 -07:00
Matt Baer 9873fc443f Merge branch 'develop' into T661-disable-accounts 2019-10-24 13:22:26 -04:00
Rob Loranger d2480cb3aa
add basic API endpoint for rendering markdown 2019-10-15 15:03:45 -07:00
Michael Demetriou 638059a26b Remove css changes, keep only pad switching code
https://github.com/writeas/writefreely/pull/188#issuecomment-540045935
2019-10-15 11:53:53 +03:00
Michael Demetriou 8404f0896c Handle default pad color mode according to `prefers-color-scheme`
This just queries the browser whether `prefers-color-scheme` is set
and chooses dark mode if needed, and only if the user hasn't manually
set a scheme by pushing the button.
2019-10-15 11:53:53 +03:00
Michael Demetriou dfa98bcfc8 WIP: this is a branch that attempts to honor the new dark mode
in OS preferences. I will be using it myself and fix things until
I find out that this is adequate to merge to develop.
2019-10-15 11:53:53 +03:00
Michael Demetriou 1bda0434de Unmarshal to `webfinger.Resource` instead of interface{}
(https://github.com/writeas/writefreely/pull/195#discussion_r334567408)
2019-10-15 10:01:22 +03:00
Michael Demetriou 972ec00c58 Update dependencies and add a comment 2019-10-11 10:33:51 +03:00
Michael Demetriou b9d2689828 Fix comments on T627 pull request
(https://github.com/writeas/writefreely/pull/195)
2019-10-11 10:05:18 +03:00
Michael Demetriou bc2016f00f Fix missing commit statement in migrations/v3.go 2019-10-10 16:49:44 +03:00
Michael Demetriou db14f04b59 Redirects from the intermediate page work and if there's an old mention
there it updates the table to include the handle.

migrations WIP
2019-10-10 16:04:43 +03:00
Michael Demetriou 99bb77153e Handles are saved in `remoteusers` while the links take you to an
intermediate page (WIP) that shows the user profile page url
2019-10-10 15:11:46 +03:00
Michael Demetriou e5bbd45b49 Change the result that webfinger returns from the first alias
to the last because mastodon doesn't like

https://my.instance/@me but https://my.instance/users/me
2019-10-10 10:59:14 +03:00
Michael Demetriou 3eb638b14a Fix @thebaer's comments in dccfae7a61 (commitcomment-35410380) 2019-10-09 14:34:31 +03:00
Rob Loranger 25fe5285da
lightly style tables in posts 2019-10-08 09:39:39 -07:00
Michael Demetriou dccfae7a61 Mentioning pleroma accounts works! Mastodon still needs the type to b
be Note to work but I will open an issue for them and see what their
reaction will be.
2019-10-08 15:58:19 +03:00
Rob Loranger 513765c09f
include localdate in all collections +reader 2019-10-03 14:09:53 -07:00
Rob Loranger aa9efc7b37
allow admin to reset user passwords
this adds a new button when viewing a user as an admin, that will
generate and store a new password for the user
2019-10-03 13:53:04 -07:00
Rob Loranger caca8f0ae2
show timestamps in local date/locale
this adds a helper script to rewrite all time elements with a proper
datetime attribute into the users locale via the browser
navigator.language.

collection, collection-post and chorus-collection-post templates now
include this script
2019-10-03 09:47:08 -07:00
Rob Loranger 02dd190945
T697 default to user's first blog instead of draft 2019-10-02 10:20:51 -07:00
Matt Baer 3759f16ed3
Merge pull request #183 from writeas/T690-invite-instructions
add user invite instructions

Resolves T690
2019-09-23 10:52:26 -04:00
Matt Baer 5a9182f688 Tweak "already invited" message
Ref T690
2019-09-23 10:44:23 -04:00
Matt Baer c6564b3d16 Shorten invite-instructions.tmpl filename 2019-09-23 10:31:38 -04:00
Matt Baer ddce177784 Fix invite input box size in non-Firefox browsers
font-size-adjust is still a flag-enabled feature in Chrome 77, and
doesn't have widespread support across browsers. So instead this uses
font-size to make the text large enough.

Ref T690
2019-09-23 10:21:03 -04:00
Matt Baer 26a4f48e8b Add expiration information to invite help
This uses the Invite fetched from the database to explain a bit more
about how the invite URL expires. It also reduces some space around the
input box.

Ref T690
2019-09-23 10:06:12 -04:00
Matt Baer f01b439ff5 Tweak invite page title and intro
Ref T690
2019-09-23 10:02:36 -04:00
Matt Baer 7e9e3cb7eb Show status on logged-in expired invite links
Ref T690
2019-09-23 09:45:36 -04:00
Matt Baer 891b15b8a8 Always return invite errors
This ensures we see a 404 page when looking up an invalid invite URL,
even if the user is logged in.

Ref T690
2019-09-23 09:19:21 -04:00
Matt Baer afa3792e8e
Merge pull request #165 from writeas/lessc-check
check for lessc executable in any location
2019-09-20 18:24:24 -04:00
Matt Baer a01e280890 Tweak "LESS not installed" message 2019-09-20 18:22:54 -04:00
Rob Loranger cb78fd227e
use inline bash instead 2019-09-20 10:17:58 -07:00
Rob Loranger 43849d95d3
add back all generation steps
accidentally removed two lines from make all

fix check when trying to install lessc
2019-09-20 10:06:49 -07:00
Rob Loranger 9d0027ec53
don't need less to install less 2019-09-20 09:17:47 -07:00
Rob Loranger d129894ba7
fix check for missing less 2019-09-18 15:56:22 -07:00
Matt Baer 0066fecc20 Fix LESSC assignment in less/Makefile 2019-09-18 17:06:40 -04:00
Rob Loranger f87371b594
update IsJSON to check for Accept header
this changes the helper IsJSON to take a request instead of a string,
allowing to check multiple headers. In this case both Content-Type and
Accept.
2019-09-18 12:39:53 -07:00
Matt Baer 66974dcbff
Merge pull request #184 from writeas/title-attrs
allow titles for abbreviation elements
2019-09-18 12:27:04 -04:00
Rob Loranger a6c1f4ae41
allow titles for abbreviation elements
this allows abbreviation elements to keep their title attributes when
containing special characters.
2019-09-18 08:21:33 -07:00
Rob Loranger d954b7c8e3
add user invite instructions
this adds a new page with instructions for sharing user invites

if a user clicks the link for one of their own invite codes they are
directed to a page with clear instructions for it's use.

if a user clicks another users link they are redirectec to their account
settings witha flash telling them they do not need to register.
2019-09-13 10:58:17 -07:00
Matt Baer 5310e6d509
Merge pull request #182 from writeas/rm-gogs-pkg-tool
remove gogs/gogs/pkg/tool dependency
2019-09-13 19:31:40 +02:00
Rob Loranger 0286dcf214
move tool from gogs into appstats pkg 2019-09-13 08:22:38 -07:00
Matt Baer 66b0945b70 Add copyright header to copied Gogs code 2019-09-13 07:17:22 -04:00
Rob Loranger 4d150fe831
Revert "add all mathjax components"
This reverts commit 25145296b3.
2019-09-12 10:19:08 -07:00
Rob Loranger 25145296b3
add all mathjax components
with only the single script, there were still many requests to a CDN.
this fixes that and speed up the page load a bit as well.
2019-09-12 10:12:57 -07:00
Rob Loranger 84d7ac35d3
fix issue with fonts and not rendering 2019-09-11 14:04:13 -07:00
Rob Loranger feba200916
remove gogs/gogs/pkg/tool dependency
this borrows some code from github.com/gogs/gogs/pkg/tool to avoid
pulling it in as a dependency, along with many other indirect deps.
2019-09-11 12:50:04 -07:00
Rob Loranger aad4768aed
include mathjax script
this includes a copy of mathjax@3.0.0/es5/tex-mml-chtml.js
2019-09-11 11:18:29 -07:00
Rob Loranger 38c1bf9cab
remove mathjax submodule 2019-09-11 10:57:46 -07:00
Matt Baer 6b99d75aa9
Merge pull request #157 from writeas/chorus
Reader-first multi-user instances

Resolves T680 T681 T684
2019-09-11 16:00:11 +02:00
Matt Baer c7a90d2ace Fix blog post links when `chorus` enabled
This ensures the "new post" link under each blog on the user Blogs page
goes to /new instead of /.

Ref T681
2019-09-10 22:07:14 +02:00
Matt Baer 40ffb3a5f9 Merge branch 'develop' into chorus 2019-09-10 21:41:28 +02:00
Matt Baer 9256293123
Merge pull request #169 from writeas/future-pins
prevent future posts from showing in pins
2019-09-10 21:37:28 +02:00
Matt Baer 151e996387 Use new isOwner var in tests
With the var there now, this makes the code a bit more readable.
2019-09-10 21:21:45 +02:00
Matt Baer b7acd39051 Add Cache-Control headers on AP endpoints
Includes:

* AP Collection fetching via canonical URL
* AP Collection fetching via API
* AP Post fetching via canonical URL
* AP Post fetching via API

Ref T693
2019-09-09 22:07:03 +02:00
Rob Loranger 908f009248
clean up and add tests for updates cache
- removes the parameter for newVersionCheck as was not being used
- changes newUpdatesCache to take expiry parameter for possible future
configuration option
- adds basic test quite to verify all cache fucntions work as expected
2019-09-09 10:24:29 -07:00
Matt Baer ca388d6536 Merge branch 'develop' into chorus 2019-09-09 17:26:40 +02:00
Matt Baer 94b8fa7756
Merge pull request #171 from writeas/empty-coll-host
fix missing collection hostname
2019-09-09 14:04:05 +02:00
Matt Baer 811a0a3cfb
Merge pull request #179 from writeas/fix-pad-edit
set a default pad tempate on all pad renders
2019-09-09 13:34:58 +02:00
Rob Loranger 6396749f31
default pad tempate on all pad renders
this fixes a bug where if the `editor` config is set to an unsupported
value there is a nil pointer error and the pad fails to render when
editing only, not on a new post.
2019-09-06 19:49:15 -07:00
Matt Baer 4419632f83 Fix false login state on failed login
Previously, a failed login would change the site-wide navigation so that
it looked like the user was logged in, even though they weren't. This
fixes that.
2019-09-03 17:56:27 -04:00
Matt Baer 8ec25f1fb4 Fix pinning on chorus collection page
Previously, the new pinned post link would appear in the site header,
instead of the blog header.
2019-09-03 17:42:23 -04:00
Matt Baer 954e57897b Fix unpinning on chorus post page 2019-09-03 17:40:02 -04:00
Rob Loranger 2a7a8298e1
strings.ReplaceAll is not in go 1.11 2019-08-29 16:20:41 -07:00
Rob Loranger eae4097677
add update checks
includes cache of latest version and page to view if updates are
available with a link to the latest update's release notes and a link to
check for the latest update now, refreshing the cache manually.
2019-08-29 15:30:27 -07:00
Rob Loranger 77f7b4a522
Add account suspension features
This renders all requests for that user's posts, collections and related
ActivityPub endpoints with 404 responses.

While suspended, users may not create or edit posts or collections.

User status is listed in the admin user page

Admin view of user details shows status and now has a button to activate
or suspend a user.
2019-08-29 09:09:11 -07:00
Rob Loranger 2fa2086654
newline in import.tmpl 2019-08-26 15:04:00 -07:00
Rob Loranger d9bf8ab6cc
update to wfimport v0.2.0
now checking for and returning invalid content type errors
2019-08-26 14:53:05 -07:00
Matt Baer 4d97856ec5
Merge pull request #164 from writeas/tar-bombs
fix tar bombs
2019-08-22 15:32:37 -04:00
Rob Loranger 6e9000659c
fix typo in Makefile GITREV release target 2019-08-22 12:16:37 -07:00
Rob Loranger 42a2219335
add ui back to target release linux 2019-08-22 10:48:58 -07:00
Matt Baer de7acb5abe
Merge pull request #168 from OddBloke/log-version
Emit the server software and version to the log on startup
2019-08-22 13:05:48 -04:00
Rob Loranger 7fb3c4cafe
allow markdown extensions in import form 2019-08-21 15:42:48 -07:00
Rob Loranger cbc9c6725a
include imported created time
this updates to parse the time from the imported file, using v0.1.1 of
the wfimport library
2019-08-21 14:43:05 -07:00
Rob Loranger 4acd35f8cd
revert include time in imported posts
in favor of library side generation to support zip files
2019-08-21 13:12:56 -07:00
Rob Loranger 9dbf14c05e
include time in imported posts 2019-08-21 10:07:30 -07:00
Rob Loranger 92f75a8871
avoid generating excess access tokens
this changes the import handler to use CreatePost instead of
CreateOwnedPost which required generation of non expiring access tokens
2019-08-19 09:06:56 -07:00
Rob Loranger 6c5d89ac86
move import post handler under /api
handler for post request to import is now under /api/me/import
form target updated

also allow all plaintext files in form
2019-08-19 09:05:52 -07:00
Rob Loranger 0ca198c715
include nice alert message on success
different template action for partial or complete import success
2019-08-17 16:18:40 -07:00
Rob Loranger ee4fe2f4ad
add basic text file imports
this adds basic support for importing files as blog posts.

.txt and .md are supported at this time and the
collection is selectable, defaulting to draft.

if a collection is specified the post is federated.
2019-08-16 14:27:24 -07:00
Matt Baer 55808233fd Fix logic for showing sign up link
This prevents the link from showing when an instance lands on the sign
up page anyway.

Ref T681
2019-08-14 23:25:02 -04:00
Matt Baer 8a29a4dfc9 Link to home page in bare editor in chorus mode
Ref T681
2019-08-14 23:14:34 -04:00
Rob Loranger 55dc1917fe
use established future posts pattern 2019-08-12 14:13:02 -07:00
Rob Loranger f241d69425
reduce GetPinnedPosts calls to single line 2019-08-12 14:12:35 -07:00
Rob Loranger 1d80e47e07
change subdirectory to writefreely
instead of writefreely_versionstring
2019-08-12 13:51:29 -07:00
Rob Loranger ca957c4b6d
fix missing collection hostname
GetCollections and GetPublishableCollections now take a hostname
parameter to allow setting the collecion hostname.

All collections used in memory now have their hostname set.
2019-08-12 12:35:17 -07:00
Rob Loranger b373aad298
prevent future posts from showing in pins
this changes GetPinnedPosts to accept an includeFutre bool, which
returns future dated pinned posts when true.
2019-08-12 09:58:30 -07:00
Daniel Watkins 7a53af355e
Emit the server software and version to the log on startup 2019-08-11 09:53:33 -04:00
Rob Loranger 95a98234eb
fix panic on duplicate remoteuser key
this changes handleFetchCollectionInbox to log _all_ errors after
attempting to insert an actor in the remoteusers table. previously
checking for all errors _except_ duplicate keys would cause a panic if
an actor made a request to follow while already having followed.
2019-08-09 14:04:15 -07:00
Matt Baer 047ad0323b Don't show user pages in nav when unauth'd
Ref T681 T680
2019-08-09 14:58:43 -04:00
Matt Baer d8405680b4 Respect `private` setting with home page Reader
Ref T681
2019-08-09 14:57:09 -04:00
Rob Loranger 3c104cb3aa
check for lessc executable in any location
previously the checks were explicit locations which does not work when
using something like nvm to manage node packages and versions.

this checks for the executable and sets the script variable LESSC to the
full path of the one found.
if none was found the make command will error.
2019-08-09 11:31:42 -07:00
Rob Loranger 1301160921
fix tar bombs
this changes the release targets in the Makefile to use a subdirectory
of the format BINARYNAME_GITREV so extracting the archive results in a
single directory.
2019-08-09 11:26:52 -07:00
Matt Baer fda2929aed Show New Post button when Chorus, not SimpleNav
Ref T681
2019-08-09 13:59:55 -04:00
Matt Baer df56060f99 Add DisableDrafts option and adjust nav
This shows Drafts in the SimpleNav menu, when both enabled. It also
hides Drafts in the non-SimpleNav menu when disabled.

Ref T679
2019-08-09 13:53:41 -04:00
Matt Baer 9dc15f569c Move About nav link next to Home
Ref T681
2019-08-09 13:52:07 -04:00
Matt Baer da423fa1bc Move Reader to Home link in nav when chorus
Also, refactor navigation bar template logic to be simpler and easier to
understand.

Ref T681
2019-08-09 13:45:19 -04:00
Matt Baer 603839fda7 Add link to Posts in user backend pages when chorus
This provides easy navigation to the logged in user's posts, since
there's no direct link to their blog otherwise.

Ref T681
2019-08-09 12:04:55 -04:00
Matt Baer f821dbaac4 Support dedicated signup page in chorus mode
This adds a Sign Up link to site navigation and shows the
otherwise-landing page on /signup when in chorus mode.

Ref T681
2019-08-09 12:00:46 -04:00
Matt Baer 006b7a86ea Show Reader on home route in chorus mode
Ref T681
2019-08-09 11:32:53 -04:00
Matt Baer 7b42efb9d9 Enable customizing Reader page
This makes it possible to edit the title and introductory text at the
top of the Reader view.

Ref T684
2019-08-09 11:16:38 -04:00
Matt Baer cb28c95689 Send new user to pad with SimpleNav
Previously, they would've been dropped onto the Blogs page.

Ref T680
2019-08-09 08:54:10 -04:00
Matt Baer deec914ccb Merge branch 'develop' into chorus 2019-08-08 07:55:49 -04:00
Matt Baer 8557119451
Merge pull request #153 from writeas/configurable-editor
Add editor config option
2019-08-08 07:33:04 -04:00
Matt Baer 10ca7ca00a
Merge pull request #149 from writeas/modestly-writefreely
Support toning down WriteFreely promotion

Resolves T676
2019-08-07 18:03:36 -04:00
Matt Baer 1c9438e305
Merge branch 'develop' into modestly-writefreely 2019-08-07 18:02:54 -04:00
Matt Baer adfcc82241
Merge pull request #148 from writeas/default-visibility
Support setting default blog visibility on instance

Resolves T675
2019-08-07 17:44:37 -04:00
Matt Baer f8d57d9e75 Return correct `public` state on collection creation
(in returned JSON data)

Ref T675
2019-08-07 16:22:35 -04:00
Matt Baer afadf6fdf6 Set default visibility in datastore.CreateCollection
instead of updating it later. This is more correct behavior, prevents an
additional query, and ensures that potential collection-creation when
claiming posts also respects the default (which it didn't before).

Ref T675
2019-08-07 16:20:32 -04:00
Matt Baer df078c569d Accept config.Config in datastore.CreateUser()
instead of App

Ref T675
2019-08-07 16:19:35 -04:00
Matt Baer de1a51d70d
Merge pull request #145 from writeas/fix-single-user
Fix single-user instance issues
2019-08-07 15:54:31 -04:00
Matt Baer f6dc07850b Fix pinned post URL on single-user instances
Previously, the URL of the dynamically-added pinned post on a
single-user instance would include the username, causing the link to go
to a non-existent page. This fixes that.
2019-08-07 15:47:49 -04:00
Matt Baer 3cc397ad76 Merge branch 'develop' into chorus 2019-08-07 10:58:34 -04:00
Matt Baer ef4a5b20d1
Merge pull request #144 from writeas/fix-api-missing-hostname
Fix missing hostname when publishing via API
2019-08-07 10:40:45 -04:00
Matt Baer b06d1c2762
Merge pull request #143 from writeas/collection-404
Style collection 404 page like rest of blog

Resolves T493
2019-08-07 10:39:38 -04:00
Matt Baer 582f041748 Return plainer message on coll .txt post 404
Ref T493
2019-08-07 10:26:36 -04:00
Matt Baer 35906118d0 Return only 404 on ActivityPub coll post request
Ref T493
2019-08-07 10:18:40 -04:00
Matt Baer ff7828c558 Link hashtags to Reader when Chorus mode enabled
instead of linking to posts only on a user's blog.

Ref T681
2019-08-07 09:40:07 -04:00
Matt Baer 1a80cd3c02 Add site-wide navigation on colls when chorus = true
This adds a new config value: `chorus` that signifies an instance is
more about the Reader view than individual blogs / writers. When
enabled, user navigation will show on all pages, including About,
Reader, and Privacy (ref T680).

It also uses different collection templates that keep the instance-wide
navigation at the top of the page, instead of the author's name --
again, branded more for the collective than the individual.

Ref T681
2019-08-07 09:00:16 -04:00
Matt Baer 5f28eb55a5 Update golang.org/x/crypto in go.mod 2019-08-06 15:59:14 -04:00
Matt Baer cd27a37027 Display current tag on Reader
i.e. current tag a user is browsing, when they are.
2019-08-06 10:42:43 -04:00
Matt Baer 17f7bc1bec Move user navigation to its own template section
Ref T681
2019-08-06 09:15:05 -04:00
Matt Baer d752d29b4b
Merge pull request #151 from writeas/custom-logging
Let Apper set request log format
2019-08-05 10:58:43 -04:00
Matt Baer 603a52dc46 Fall back to default template on bad editor config
Ref T677
2019-08-05 10:25:58 -04:00
Matt Baer 1d25784d20 Add `bare` editor option
This adds a new editor template that strips away most of the
customization features in the default editor and includes only:

- publishing
- editing
- viewing word count

It also restricts publishing to a user's first collection, so it's
optimized for instances that only allow users to have a single
collection and don't use Drafts.

Ref T680 T677
2019-08-05 09:54:05 -04:00
Matt Baer 90ad50c7f5 Use normal nav on user pages when SimpleNav
This shows About, Reader, Log out links on backend user pages when
logged in. It also adds "New post" buttons on the backend pages and
blogs.
2019-08-05 09:34:47 -04:00
Matt Baer 81847fbbcc Land on Blogs page when SimpleNav is enabled
This shows the Blogs page instead of the Editor to logged in users on
the `/` path when the new `simple_nav` config option is enabled.

Ref T680
2019-08-05 09:27:51 -04:00
Matt Baer f6a7dfacb9 Add editor config option
Ref T677
2019-08-04 22:20:30 -04:00
Matt Baer 740282b7b7
Merge pull request #147 from writeas/support-audio
Support <audio> element
2019-08-02 22:38:56 -04:00
Matt Baer 3321c750ac
Merge pull request #142 from writeas/autocert
Automatic certificates from Let's Encrypt

Resolves T542
2019-08-01 23:12:28 -04:00
Matt Baer 0bd61da3f6 Link to writefreely.org in default About text 2019-08-01 22:04:09 -04:00
Matt Baer 6bfc441680
Merge pull request #141 from writeas/fix-sitemap-lib
Fix go-sitemap-generator overriding GOMAXPROCs
2019-08-01 21:18:48 -04:00
Rob Loranger dd2a5840ec
fix mistake with images in sitemap 2019-08-01 18:13:04 -07:00
Matt Baer 5953a50f4a Let Apper set request log format
This adds a new ReqLog() func to the Apper interface that'll return the
log message for incoming requests.

Ref T649
2019-08-01 16:12:22 -04:00
Rob Loranger f02a241213
get image sitemap working
changed to use stm.URL for imgs for readability
fixed error in append statement
reordered import statements
2019-08-01 08:25:22 -07:00
Matt Baer 73ec3e3016 Support toning down WriteFreely promotion
This adds a new `wf_modesty` config option that removes the copious
mentions of WriteFreely in places like the About page and site
footers.

WriteFreely remains boastful and bumptious by default; but enabling
the modesty option will tone it down and likely lead to less confusion
among average users.

Ref T676
2019-07-31 22:53:10 -04:00
Matt Baer 569bc792d0 Enable changing default_visibility from Admin dash
Ref T675
2019-07-31 22:20:00 -04:00
Matt Baer a75b45f060 Support configuring default collection visibility
This adds a new `default_visibility` config value that lets an instance
admin set the visibility of newly created collections.

Ref T675
2019-07-31 22:18:40 -04:00
Matt Baer b0d70d9bdb Support <audio> element
This whitelists the HTML5 <audio> element and adds some basic style to
make it look nice.
2019-07-25 22:54:11 -04:00
Matt Baer a48b746706 Fix empty Drafts page for single-user instances
- This removes copy mentioning the Blogs page, which isn't used on
  single-user instances
- This fixes the "Start writing" link, which on a single-user instance
  would've gone to the blog index, rather than the editor
2019-07-22 14:47:39 -04:00
Matt Baer 3129b837f1 Hide footer links to About and Privacy pages when single-user
Previously, these links showed up on user backend pages on a single-user
instance, despite them not working / only being applicable on multi-user
instances.
2019-07-22 14:40:10 -04:00
Matt Baer bd4bb52b9c Hide Public blog option on single-user instances 2019-07-22 14:37:32 -04:00
Matt Baer 4faf41ae7f Log missing hostName in Collection.RedirectingCanonicalURL
This is the crucial part where the hostName is needed for federation and
API clients. This change at least lets us know when we mess up like this
so the issue is easier to catch in the future.
2019-07-22 14:22:41 -04:00
Matt Baer f6f116d672 Fix missing hostname when publishing via API
This fixes a bug that occurred only when publishing via API and
authenticating via token (rather than cookie).

Previously, the instance's hostname wouldn't be added to the Collection
that got passed around after retrieving the owned post, meaning an
incomplete URL was returned in the API response, and federation failed due
to the missing host.
2019-07-22 14:02:53 -04:00
Matt Baer f541f72224 Style collection 404 page like rest of blog
This displays the "page is missing" text within the same page as any
other blog post, keeping customizations, pinned pages, and general blog
navigation.

Ref T493
2019-07-21 15:15:52 -04:00
Matt Baer ba3cb4b4ff
Merge pull request #138 from writeas/fix-unpublished-state
Don't consider post unpublished when title exists
2019-07-21 11:35:16 -04:00
Matt Baer 1f7a0f0122 Add option for automated cert in config process
This adds a new "Secure (port 443), auto certificate" option to the "Web
server mode" prompt when running `writefreely --config`. When chosen,
it'll set `autocert` to `true` and set the path for certs and keys to
`certs`.

Ref T542
2019-07-20 21:46:10 -04:00
Matt Baer 3346e735d3 Fix autocert insecure server redirect
This fixes certificate validation, while keeping HTTP -> HTTPS
redirection.

Ref T542
2019-07-20 21:38:02 -04:00
Matt Baer 42386beabc Fix autocert HostPolicy
Previously, this would pass in the instance's full (and invalid) URL.
Now it passes only the host name.

Ref T542
2019-07-20 21:37:27 -04:00
Matt Baer 36fb7ecb2b Support automatically generated certificates
This adds a new config option in the `[server]` section: `autocert`.
When true, WF will automatically generate certificates instead of using
ones from the provided cert path. However, all generated certificates
will be stored in the configured `tls_cert_path`.

Ref T542
2019-07-20 20:49:20 -04:00
Matt Baer 41062728f5 Fix go-sitemap-generator overriding GOMAXPROCs
This upgrades the library to v2, which lets you specify that GOMAXPROCs
should always be the max number of CPUs -- and then sets it to that.

One side effect of this is that images are no longer listed in sitemaps.
I'm somehow at a loss on how to build and append the images array we
need, with the library's latest changes.

Fixes #86
2019-07-20 19:47:13 -04:00
Matt Baer 22c1fabbcb
Merge pull request #137 from writeas/fix-long-slugs-chinese
Prevent transliterated slugs exceeding length limit
2019-07-18 14:32:06 -04:00
Matt Baer 909976dd90 Don't consider post unpublished when title exists
Previously, you could create a post with a title but no body, e.g. by
publishing via email. This would still show the post on a blog, but
would give a 410 Gone page when trying to access the page.

This issue originally reported on the forum:
https://discuss.write.as/t/removing-post-unpublished-by-author-post/725
2019-07-14 12:59:33 -04:00
Matt Baer 31b521c11c Prevent transliterated slugs exceeding limit
Transliteration during slug generation can cause slugs to exceed their
80-character limit. This fixes that by making a second truncation pass
on the slug during generation.

Originally reported on the forum:
https://discuss.write.as/t/title-convert-to-url-function-bug-under-chinese/723
2019-07-11 09:18:39 -04:00
Matt Baer 71fb63580a Merge branch 'master' into develop 2019-07-08 08:57:05 -04:00
Matt Baer e0666baa5d Include ARMv7 build in `make release`
This closes #135
2019-07-08 08:56:25 -04:00
Matt Baer 0b25109a6b Add `make build-arm7`
This makes it easy to build WF for ARMv7, e.g. the Raspberry Pi.

part of #135
2019-07-08 08:55:22 -04:00
Matt Baer 3b079810bb Accept Apper in writefreely.ResetPassword()
instead of *App
2019-07-03 14:39:43 -04:00
Matt Baer 79cf6ce0eb Accept Apper in writefreely.Migrate()
instead of *App
2019-07-03 14:39:05 -04:00
Matt Baer 3faa2def08 Add Documentation section and fix dev setup link 2019-07-02 11:41:43 -04:00
Matt Baer 5923b6401c Replace old "quick start" section with Getting Started link 2019-07-02 11:02:54 -04:00
Matt Baer ad6fd5e809 Use "Draft" in post meta page
instead of "Anonymous".
2019-07-01 19:59:52 -04:00
Matt Baer 554995916e Replace top-left "w" button on post meta page
This was still a relic of Write.as. Now it has the same icon as the
WriteFreely editor.
2019-07-01 19:56:50 -04:00
Matt Baer 7aaff778da
Merge pull request #123 from writeas/private-instance
Private instances

Resolves T576
2019-07-01 19:14:20 -04:00
Matt Baer 7240bf0cdc
Merge pull request #131 from writeas/customize-landing
Customize landing page

Resolves T565
2019-07-01 19:12:58 -04:00
Matt Baer bd180f56a8 Add comments about isRaw logic 2019-07-01 19:10:29 -04:00
Matt Baer fdcdfe4d25 Open landing page preview in new window 2019-07-01 19:05:47 -04:00
Matt Baer 60a6848361 Fix userlevel error logging
Previously, we just included the value of `ul`, which is a func. This
now calls `ul()` and logs that value.
2019-07-01 16:45:35 -04:00
Matt Baer 5757407994 Bump version to 0.10.0 2019-07-01 15:20:29 -04:00
Matt Baer 18bafadc43
Merge pull request #127 from writeas/shorter-config-process
Shorter config process
2019-07-01 14:15:41 -04:00
Matt Baer b8b15c8550 Move Environment prompt back under Server section 2019-07-01 14:00:56 -04:00
Matt Baer a740c67495 Fix whitespace
This runs `go fmt` on changed files and moves around some blank lines.
2019-07-01 13:33:26 -04:00
Matt Baer ebeb7b03e6 Explicitly set background-color
Closes #132
2019-06-28 08:26:15 -04:00
Matt Baer c3f3eb0a65 Rename getLandingPage -> getLandingBody
This makes the naming scheme more consistent with other funcs.

Ref T565
2019-06-27 22:22:21 -04:00
Matt Baer a72ce2ef29 Make landing page dynamic
This enables admins to customize their landing / home page via the Admin
dashboard -- including the text at the top of the page and the section
below it. It keeps the current default text, falling back to it if the
user hasn't overwritten it.

Ref T565
2019-06-27 17:06:37 -04:00
Matt Baer aedb05080c Support ?landing=1 to always show landing page
This supports admins previewing changes to the landing page.

Ref T565
2019-06-27 16:38:24 -04:00
Matt Baer 6fdc343986
Merge pull request #130 from mrvdb/issue125
Construct version from annotated tags only
2019-06-27 15:32:14 -04:00
Marcel van der Boom f6c129ed20 Construct version from annotated tags only
Fixes issue 125
2019-06-27 21:25:22 +02:00
Matt Baer f26e0ca86e
Merge pull request #128 from writeas/fix-c-syntax-highlighting
Fix #124 according to the snippet by @mrvdb
2019-06-27 15:07:04 -04:00
Michael Demetriou 4feac6dcd2 Remove `langs` list from `post-render` as it does not actually
do anything useful (see https://github.com/writeas/writefreely/pull/128#issuecomment-506207107)
2019-06-27 18:15:58 +03:00
Matt Baer 8d9f60aaa9 Always initialize database after --config
Previously, this would only run when configuring an instance for
single-user usage. Now it'll also run when configuring for multi-user
usage.

It also adds a log when the database has already been initialized.
2019-06-27 18:15:58 +03:00
Michael Demetriou a102f97c3e Fix #96
This solves the error 500 on the /api/me endpoint.

Replace token search query `=` with `LIKE` to fix sqlite complaining about
no valid tokens. Also checked with MySQL and it still works after the change.
2019-06-27 18:15:58 +03:00
Matt Baer bb0be02b4f
Merge pull request #126 from writeas/config-improvements
Always initialize database after --config
2019-06-27 09:31:58 -04:00
Matt Baer 00a8f8c951
Merge pull request #119 from qwazix/develop
Half-fix of #96
2019-06-27 09:29:25 -04:00
Michael Demetriou 0842119694 Change `sh` alias back to `bash`
because this is the alias in highlight itself.

(see https://github.com/writeas/writefreely/pull/128#issuecomment-505766645)
2019-06-27 00:12:18 +03:00
Michael Demetriou c2d7c2c8b7 Fix #124 according to the snippet by @mrvdb
I changed the sh alias to shell instead of bash.

The additions to the `highlight(nodes)` function look redundant.
It works for me without them but maybe they cover an edge case I
cannot think about?
2019-06-25 21:17:30 +03:00
Michael Demetriou 6506709fbc Merge branch 'develop' into shorter-config-process
Move flag parsing to main.go as per the issue description
2019-06-21 12:07:01 +03:00
Michael Demetriou aeab30db8a Fix #96
This solves the error 500 on the /api/me endpoint.

Replace token search query `=` with `LIKE` to fix sqlite complaining about
no valid tokens. Also checked with MySQL and it still works after the change.
2019-06-21 10:48:40 +03:00
Michael Demetriou efbef83362 make sure app exits after error in --sections argument 2019-06-21 10:40:40 +03:00
Matt Baer 77bf403443 Merge branch 'develop' into private-instance 2019-06-20 21:10:36 -04:00
Matt Baer 86a128483b Fix more missing hostNames
This fixes places, especially around federation, where the Collection's
`hostName` wasn't set.
2019-06-20 21:08:30 -04:00
Michael Demetriou 07fe366c15 Fix T657: add --sections argument to allow partial configuration.
Use the split argument list (slice) just for validation purposes
as it's substantially easier to do `.contains` in a string instead
of a slice. As such, pass the `configSections` arguments to
`Configure()` and check the existence of each one before showing
the options to the user.

An empty argument list is replaced by "server db app" so everything
is there negating the need to check anything else in `Configure()`.
In the same vein the default is "server db app".

The parsing is done in `app.go` alongside the other flags instead
of `main.go` as described in T657.
2019-06-20 23:41:03 +03:00
Michael Demetriou 1d5c396327 Add --sections flag to app.go and pass it to setup.go
Add --sections flag to app.go according to T657, parse them
into a string array (check for invalid arguments and abort)
and pass them to Configure(). For now Configure() doesn't do
anything with them yet.
2019-06-20 23:14:36 +03:00
Matt Baer bbd775bcc6 Always initialize database after --config
Previously, this would only run when configuring an instance for
single-user usage. Now it'll also run when configuring for multi-user
usage.

It also adds a log when the database has already been initialized.
2019-06-20 09:04:52 -04:00
Matt Baer 2b39b714de Use UserLevelReader func for read routes
Previously, that func was duplicated here.
2019-06-19 19:26:10 -04:00
Matt Baer 44a4fd7a79 Correctly log and return after serving static file 2019-06-19 19:17:45 -04:00
Matt Baer 7dc620aff1 Check reader permissions on .well-known endpoints
(for private instances)

Ref T576
2019-06-16 21:22:56 -04:00
Matt Baer d6a77d6668 Check reader permissions on RSS feed & sitemap
(on private instances)

Ref T576
2019-06-16 21:16:23 -04:00
Matt Baer 63b536ec87 Don't federate anything when instance is private
Ref T576
2019-06-16 20:34:32 -04:00
Matt Baer 35718cd239 Change blog visibility explanations on Private instance
Ref T576
2019-06-16 20:30:56 -04:00
Matt Baer bf989eb696 Hide Reader link on private instance when unauth'd
Ref T576
2019-06-16 20:29:31 -04:00
Matt Baer a2088c1646 Restrict API read access based on Private setting
This verifies that a user is authenticated before getting to the actual
handler on API endpoints where a user is reading content.

Ref T576
2019-06-16 20:24:47 -04:00
Matt Baer b3a36a3be7 Allow completely private instances, part 1
This is the start of all changes needed to support entirely private
instances, where all blogs are only visible to other authenticated users
on an instance (ref T576). It begins by changing how Handler methods check an
endpoint's permissions.

- Renames UserLevelLEVEL consts to UserLevelLEVELType
- Adds UserLevelLEVEL funcs with same names as previous consts. Each
  returns a UserLevel
- Adds a new UserLevelReader that restricts access based on app
  configuration. This is now used on collections and posts.
- Changes routing a bit so static files are always accessible
2019-06-16 18:55:50 -04:00
Matt Baer 161f7a8de2 Support changing landing conf val from Admin UI
Closes T651
2019-06-16 17:38:34 -04:00
Matt Baer 2b8b52285d
Merge pull request #122 from writeas/update-script
Update script

Closes T555
2019-06-14 20:36:07 -04:00
Rob Loranger 075f25b829
fix: update script: non-standard version numbers
store version output in new variable
slice output indexed from beginning instead of end. allowing for custom
build version numbers.

also fix weird spacing from tabs being two spaces wide, sorry :)
2019-06-14 17:26:56 -07:00
Matt Baer 872ec4809b Tweak status wording
And fix a typo

Ref T555
2019-06-14 19:50:41 -04:00
Matt Baer ac7d727435
Merge pull request #102 from writeas/librarization
Decouple writefreely library from executable

Ref T613
2019-06-14 19:25:24 -04:00
Matt Baer 36b160b706 Add TODO for multierror 2019-06-14 19:12:14 -04:00
Matt Baer f38a135bfa Remove global hostName var
This moves `hostName` to the `Collection` struct, where it's needed. The
field is populated after successful `GetCollection...()` calls.

This isn't the cleanest way to do things, but it accomplishes the goal.
Eventually, we should accept the AppCfg to `GetCollection...()` calls,
or make them `App` methods, instead of `datastore` methods.

Ref T613
2019-06-14 18:54:04 -04:00
Matt Baer 4c34b34736 Exit when no writefreely executable found
(or `writefreely -v` otherwise doesn't output anything)

Ref T555
2019-06-14 16:43:18 -04:00
Rob Loranger f6ba1fc9c8
update script: error check restarting service
basic error check with descriptive output in update_server.sh
2019-06-14 10:05:08 -07:00
Rob Loranger 0c44fe1c2e
add update script
this bash script will download a new version, if any, and exrtact it to
a temporary location. Then copy the new files to the current directory
and restart the systemd service.
2019-06-14 09:55:31 -07:00
Matt Baer 26a0990014 Save config via Apper interface from Admin dash
Ref T613
2019-06-13 21:56:13 -04:00
Matt Baer d5c2fe47da
Merge branch 'develop' into librarization 2019-06-13 20:44:55 -04:00
Matt Baer 830b859421 Fix activitypub.go imports 2019-06-13 20:44:13 -04:00
Matt Baer a10a4e9a28 Merge branch 'develop' into librarization 2019-06-13 20:39:52 -04:00
Matt Baer be0547a62c Include schema.sql when built with wflib tag
This ensures outside application builds will succeed when including the
writefreely pkg.

Ref T613
2019-06-13 20:25:30 -04:00
Matt Baer 034db22f8c Break functionality out of Serve() func
- Adds a new interface, Apper, that enables loading and persisting
  instance-level data in new ways
- Converts some initialization funcs to methods
- Exports funcs and methods needed for intialization
- In general, moves a ton of stuff around

Overall, this should maintain all existing functionality, but with the
ability to now better manage a WF instance.

Ref T613
2019-06-13 18:50:23 -04:00
Matt Baer ed4aacd1ac Move static file ServeMux to App struct 2019-06-13 18:45:03 -04:00
Matt Baer f8de8f7f21 Merge branch 'librarization' of github.com:writeas/writefreely into librarization 2019-06-13 13:48:05 -04:00
Matt Baer eb6349f93a Allow compiling without go-sql-driver/mysql pkg
This ensures the writefreely pkg can be used in other applications that
need to load mysql themselves -- this can be done by building with the
tag: wflib

Ref T613
2019-06-13 13:47:27 -04:00
Matt Baer 23acabaeb3 Use db.isDuplicateKeyErr() in activitypub.go
(instead of writing out the logic of that helper function)

Ref T613
2019-06-13 13:47:37 -04:00
Matt Baer 758269e3d8 Move key generation and Keychain to key pkg
Ref T613
2019-06-13 13:47:28 -04:00
Matt Baer 584fe4fb93 Support changing default landing path
This adds a new `landing` value in the [app] section of config.ini.
If non-empty, unauthenticated users on multi-user instances will be
redirected to the path given there.

This closes T574
2019-06-13 13:47:27 -04:00
Michael Demetriou 9570388d1d Fix #96
This solves the error 500 on the /api/me endpoint.

Replace token search query `=` with `LIKE` to fix sqlite complaining about
no valid tokens. Also checked with MySQL and it still works after the change.
2019-06-11 20:18:58 +03:00
Matt Baer b2a9429db0
Merge pull request #117 from writeas/T612
change delete post authentication logic
2019-06-05 16:18:08 -04:00
Rob Loranger d58c142467
change delete post authentication logic
this reorders the logic for checking authentication on post deletes to
first check for a provided edit token and after check for an access
token or auth'd user.
2019-06-05 09:39:22 -07:00
Matt Baer c87b7ab39e
Merge pull request #111 from writeas/gh100
support pubgate
2019-06-03 16:37:36 -04:00
Rob Loranger 08799b220b
revert accidental .vscode folder inclusion 2019-06-03 11:55:42 -07:00
Rob Loranger d8fa85432d
fix for Pubgate user not having SharedInbox 2019-06-03 11:53:17 -07:00
Rob Loranger 702db2bf75
Merge branch 'gh100' of github.com:writeas/writefreely into gh100 2019-06-03 11:52:44 -07:00
Matt Baer 73f627a6c2
Merge pull request #110 from writeas/fix-rtl
Enable un-setting RTL setting via web
2019-06-02 13:19:32 -04:00
Matt Baer f82e11b3b3
Merge pull request #115 from writeas/blog-index-titles
Make post body h2's smaller on index pages
2019-06-01 12:16:36 -04:00
Matt Baer 6bf4e1a52e Add spacing around time element on index pages
This adds a small margin around a time element, which makes things look
better when the body of a post starts with a header.
2019-06-01 09:26:58 -04:00
Matt Baer 4b1ca3e296 Make post body h2's smaller on index pages
Previously, <h2>s in a post were the exact same size as post titles on
index pages (blog index, tag listing). This fixes that by reducing the
font-size of body h2's. Closes #82.
2019-06-01 09:17:28 -04:00
Matt Baer 68bd5ef01a
Merge pull request #114 from writeas/export-title
Include post title in zip export txt files
2019-05-31 09:26:20 -04:00
Matt Baer cba7f1223e Include post title in zip export txt files 2019-05-30 10:38:55 -04:00
Matt Baer cf51fc4886
Merge pull request #113 from writeas/fix-json-export-2
Modify GetPost() to allow inclusion of pinned posts
2019-05-29 12:18:04 -04:00
Noëlle Anthony f271e53925 Update GetPosts() docstring 2019-05-29 12:03:01 -04:00
Noëlle Anthony 95e84a1d0e Change GetPosts() to have includePinned parameter, change all calls to match 2019-05-28 14:54:56 -04:00
Rob Loranger 95215aa39d
fixes issue #100 - can't follow from pubgate
this moves the unmarshaling of a remote actor out into a new helper which
accounts for the possibility of a context being a list or a single entity.
i.e. a string or an object.

basics tests are provided for both situations

also go fmt'd the file activitypub.go
2019-05-24 19:38:35 -07:00
Matt Baer cf1d2d30e9 Merge branch 'agd' into librarization 2019-05-23 08:26:52 -04:00
Rob Loranger ff2d3fc3d5
fixes issue #100 - can't follow from pubgate
this moves the unmarshaling of a remote actor out into a new helper which
accounts for the possibility of a context being a list or a single entity.
i.e. a string or an object.

basics tests are provided for both situations

also go fmt'd the file activitypub.go
2019-05-21 07:02:35 -07:00
Rob Loranger 3986c8eec1
add missing string variable in log statement in export.go + go fmt 2019-05-20 14:51:54 -07:00
Matt Baer b6da5d9711
Merge pull request #107 from writeas/go-bindata-as-dep
Add go-bindata as a module dependency
2019-05-20 14:18:36 -04:00
Matt Baer b9b41b1ef7 Enable un-setting RTL setting via web
Previously, once RTL was enabled on a post, you couldn't unset it via
the web application. This fixes that. (Fixes #103)
2019-05-18 23:16:42 -04:00
Rob Loranger 6ff136455c
Add go-bindata as a module dependency
This adds https://github.com/jteeuwen/go-bindata as a module dependency
as the make target 'build' will result in it's addition.
2019-05-17 16:22:44 -07:00
Matt Baer 2fdd58387a
Merge pull request #105 from writeas/go-bindata-repo-fix
go-bindata url updated
2019-05-17 17:27:43 -04:00
Sandrockcstm cf139ecd72 go-bindata url updated 2019-05-17 15:57:33 -05:00
Matt Baer 98f5b14899 Use HTTP for MathJax submodule 2019-05-14 07:58:36 -04:00
Matt Baer effab9b6a1 Move MathJax to git submodule 2019-05-14 07:50:37 -04:00
Matt Baer c95ee0abe1 Support changing default landing path
This adds a new `landing` value in the [app] section of config.ini.
If non-empty, unauthenticated users on multi-user instances will be
redirected to the path given there.

This closes T574
2019-05-12 20:11:53 -04:00
Matt Baer 77c8152786 Break up CreateUser credentials into two args
Now the func takes a username and password instead of a single string.
2019-05-12 18:42:57 -04:00
Matt Baer 9e43b04f04 Make Keychain struct public 2019-05-12 17:20:24 -04:00
Matt Baer d8937e89a8 Make App struct public 2019-05-12 17:19:38 -04:00
Matt Baer 9023d4eb3f Move flag parsing to main package
This exposes setup and admin functions in the writefreely package, and
uses them in the main application, initialized by the flag parsing that
now happens there.

This is the first step towards making `writefreely` usable as a
standalone package.
2019-05-10 11:40:35 -04:00
Matt Baer 788713116f
Merge pull request #99 from gytisrepecka/master
Added /bin/lessc path to be compatible with CentOS 7
2019-05-02 10:01:28 -04:00
Gytis Repečka 6da10f28ac
Modified less/Makefile: added /bin/lessc path which is returned by which lessc in CentOS 7 when lessc is installed via npm. 2019-05-02 12:07:01 +03:00
Matt Baer 601fc5d93e
Merge pull request #97 from dariusk/master
Tweak Makefile for MacOS
2019-04-30 15:33:57 -04:00
Darius Kazemi 91a181f628 Adding another possible path for lessc to Makefile 2019-04-30 10:40:00 -07:00
Matt Baer fdbefa806f
Merge pull request #95 from TeDomum/master
Fix the build in Docker after enabling go modules
2019-04-19 09:01:44 -04:00
kaiyou 70f754e8af Install the writefreely cmd properly 2019-04-19 13:05:01 +02:00
kaiyou 402e9e822c Fix the build in Docker after enabling go modules
Enabling go modules requires that GO111MODULE is set, so
we set it as an environment variable in the Dockerfile.

Also, go-bindata is meant to be installed globally, so we
force the install before enabling Go modules. Also, we update
Go to 1.12 to fix a couple module builds.
2019-04-19 12:55:54 +02:00
Matt Baer 7a07e1009b Include -config and -init-db steps in make install
This also moves development instructions to documentation repo.
2019-04-16 11:27:27 -04:00
Matt Baer 771c7acf5f Run migrations on db initialization
This makes it so we can keep all schema changes in the `migrations`
module, instead of adding them there *and* in the schema files. It
fixes #92 and should prevent these kinds of issues in the future.
2019-04-15 13:48:19 -04:00
Matt Baer c08484aa9c Fix excessive p spacing in blockquotes 2019-04-13 12:04:17 -04:00
Matt Baer e80f99a72e Fix draft post metadata
Previously it referenced an image on write.as and had the incorrect
og:url property. This also fixes the canonical URL on single-user
instances. Closes #91
2019-04-12 14:56:46 -04:00
Matt Baer 1dc93c076d Revert "Support new commentsEnabled property"
This reverts commit 5e7da6678d.

There are better standards in progress like a `capabilities` object:
https://litepub.social/litepub/lice.html
2019-04-11 22:04:51 -04:00
Matt Baer 831209f4b6 Merge branch 'develop' 2019-04-11 22:03:16 -04:00
Matt Baer 1f973464bc Merge branch 'master' of github.com:writeas/writefreely 2019-04-11 22:03:03 -04:00
Matt Baer 238a913ce3 Make WriteFreely spacing consistent 2019-04-11 21:33:33 -04:00
Matt Baer 63a2225b7f Add admin dashboard link to dropdown navs 2019-04-11 21:11:30 -04:00
Matt Baer af0f6302a2 Replace --reset-pass instructions with admin guide link 2019-04-11 21:10:45 -04:00
Matt Baer 7ab5350a94 Bump version to 0.9.0 2019-04-11 21:04:48 -04:00
Matt Baer 51ac06a51b Move Docker instructions to writefreely/documentation 2019-04-11 20:44:44 -04:00
Matt Baer 07ab0cdb9c Add `invites` flag in NodeInfo
This indicates whether or not invites are enabled on this instance. It
requires an upgrade to writefreely/go-nodeinfo v1.2.0
2019-04-11 20:37:01 -04:00
Matt Baer 9cb0f80921 Support changing instance page titles
Now admins can choose a title for their About and Privacy pages; now
editable through the instance page editor.

This adds `title` and `content_type` fields to the `appcontent` table,
requiring a migration by running `writefreely --migrate`

The content_type field specifies that items we're currently storing in
this table are all "page"s; queries for fetching these have been updated
to filter for this type. In the future, this field will be used to
indicate when an item is a stylesheet (ref T563) or other supported
type.

Ref T566
2019-04-11 13:56:07 -04:00
Matt Baer 4cad074b44 Link to version-specific writer's guide 2019-04-11 13:52:10 -04:00
Matt Baer 24c56af6ee
Merge pull request #89 from writeas/comments-enabled
Support new commentsEnabled property
2019-04-07 12:34:14 -04:00
Matt Baer a850fa14cd Move instance page editing to dedicated section
This adds a "Pages" section to the admin part of the site, and enables
admins to edit the pre-defined About and Privacy pages there, instead of
on the dashboard itself.

It also restructures how these pages get sent around in the backend and
lays the groundwork for dynamically adding static pages. The backend
changes were made with more customization in mind, such as an
instance-wide custom stylesheet (T563).

Ref T566
2019-04-06 13:23:22 -04:00
Matt Baer 00ed2990eb Do Travis builds without sqlite
Using xgo comes with its own gomod-related issues. So let's see if this
fixes the build issue mentioned in the previous commit.
2019-04-06 11:46:27 -04:00
Matt Baer c7a4955840 Do CI compile with xgo
This will hopefully fix Travis errors:

/home/travis/.gimme/versions/go1.11.7.linux.amd64/pkg/tool/linux_amd64/link: running gcc failed: exit status 1
/usr/bin/ld: /tmp/go-link-810454258/000020.o: unrecognized relocation (0x2a) in section `.text'
/usr/bin/ld: final link failed: Bad value
2019-04-06 11:28:43 -04:00
Matt Baer 09fb73bdd5 Standardize admin navigation 2019-04-06 11:15:14 -04:00
Matt Baer 45b01c041b Don't install packages in make deps
This fixes an error: undefined: Asset
2019-04-06 10:50:22 -04:00
Matt Baer 8a9ef513fa Fix go-bindata error in Travis build
Based on @sheenobu's work on #58
2019-04-06 10:45:19 -04:00
Matt Baer 24b193df96 Bump Travis Go version to 1.11 2019-04-06 10:22:42 -04:00
Matt Baer 4af9fa66aa Don't federate scheduled posts upon claiming
Previously, moving an anonymous post to a blog would instantly federate
the post, regardless of its `created` date. This now respects that value
and doesn't federate the post if its `created` date is in the future.

This is the first part of supporting scheduled, federated posts (ref T567)
but technically fixes #76.
2019-04-05 18:50:18 -04:00
Matt Baer 5e7da6678d Support new commentsEnabled property
This is a field previously supported by PeerTube, and just recently
added on PixelFed, that should inform other ActivityPub services whether
or not comments are enabled on any given post. WriteFreely doesn't
support comments today, so this will always be false.
2019-04-04 17:03:54 -04:00
Matt Baer 5a0a3ce451
Merge pull request #78 from mrvdb/apple-icons
Add touch icons
2019-03-24 17:42:56 -05:00
Matt Baer be0df11653 Use WF "w" for touch icons
(instead of the Write.as "w")
2019-03-24 17:38:46 -05:00
Matt Baer b7d07e2037 Automatically assign "bug?" label to bug reports 2019-03-14 09:43:48 -04:00
Matt Baer 54edb2562d Strip HTML from post summary
This removes HTML, in addition to the Markdown stripping that was
already happening.

This fixes #83
2019-03-14 08:59:59 -04:00
Matt Baer 2f683e783e
Merge pull request #81 from mrvdb/codehighlight
Resolve an edge case where last language has error
2019-03-06 14:22:53 -05:00
Matt Baer 3d30a09695 Fix IsValidUsername check when PagesParentDir isn't current dir
Previously, this check would only work if there was a pages/ dir in the
current working directory. Now it respects the pages_parent_dir
configuration setting.
2019-03-06 10:44:32 -05:00
Marcel van der Boom f40ce14fb2 Resolve an edge case where last language has error
If there are multiple language blocks on a page, we set the
onload on the last one to load all highlighting at once.

If the last language block has an error, the onload would
never fire and thus all blocks would not be highlighted.

The simplest resolution is to fire the callback regardless. We've
already loaded everything so running the callback is not causing any
performance hit which is relevant I think.
2019-03-05 14:36:40 +01:00
Matt Baer 372b4e5dcd Fix nil pointer when navigating to bad invite URL
Previously when looking up an invite ID that doesn't exist, the database
call wouldn't communicate its non-existence in a standard way --
returning a nil object and nil error. Now the database call returns a
404 error, so handlers can show the correct page.
2019-02-27 06:15:42 -05:00
Matt Baer 99489aa920 Change zip link to txt on Export page
"zip" doesn't really describe what the posts themselves are exported as.
"txt" does.
2019-02-25 15:49:11 -05:00
Marcel van der Boom c8ea346d16 Add touch icons
These were missing.
2019-02-19 11:49:33 +01:00
Matt Baer 16c856ec27 Merge branch 'readme-downloads' 2019-02-11 10:54:14 -05:00
Matt Baer e20827ac3b Rearrange README badges
and remove IRC link
2019-02-11 10:53:35 -05:00
Matt Baer 2cee0dee2a Support Go modules 2019-02-11 10:30:31 -05:00
Matt Baer 32e99d0041 Fix hashes in code blocks rendered as hashtags
Previously, our hashtag parser would indiscriminately replace
hashtag-like text with hashtag HTML -- including in places it shouldn't
have, like inside code blocks. Along with the v1.7.0 changes to
writeas/saturday, this fixes that and closes #6.

As a bonus, strings of #spaceless#hashtags#in#a#row are now rendered
correctly.
2019-02-04 17:50:37 +01:00
Matt Baer c2436a43c5 Merge branch 'master' into develop 2019-02-04 10:14:59 +01:00
Matt Baer a896d475e4 Clean up resource URLs on collection-tags template
This included files.writeas.org paths and other Write.as-specific logic.
2019-02-04 10:12:38 +01:00
Matt Baer e5a00e00f5 Bump version to 0.8.1 2019-02-01 15:10:20 +01:00
Matt Baer ee6046bdbf
Merge pull request #43 from mrvdb/hashlink
Hashtag linking improvements
2019-02-01 14:39:01 +01:00
Matt Baer 88e7cea28b Set PublicReader value in nodeinfo 2019-01-29 19:07:12 -05:00
Matt Baer 261a6fefd6 Merge branch 'master' into develop 2019-01-27 14:54:50 -05:00
Matt Baer 02054aae18 Link to documentation repo 2019-01-26 21:25:38 -05:00
Matt Baer ef40c50920 Link to tutorials on some features
Also, add static pages
2019-01-26 11:59:02 -05:00
Matt Baer e1cd11df20 Update `go get` instructions
Including the `-d` flag ensures the package doesn't build. We need this
because it will immediately fail, since the static assets haven't been
compiled yet.
2019-01-26 11:13:36 -05:00
Matt Baer cd752858b7 Fix --create-user usage error message 2019-01-26 11:11:17 -05:00
Matt Baer c11ede53d9 Return error from adminCreateUser()
...instead of using os.Exit(). This makes it more apparent what's going
on, less error-prone, and more consistent with other command logic paths.
2019-01-26 11:06:58 -05:00
Matt Baer 08667d8978 Return error from adminInitDatabase()
...instead of doing os.Exit(). This allows the func to be used in many
places (as it is) and handle success results in different ways.

Previously, this caused the single-user configuration process to exit
prematurely. This fixes that and closes #71.
2019-01-26 10:52:11 -05:00
Matt Baer 47d18b2cc4 Merge branch 'master' into develop 2019-01-24 21:11:47 -05:00
Matt Baer 9c6e7eda65 Fix Write.as pricing link 2019-01-24 21:11:04 -05:00
Matt Baer e682824be5 Add @nkoehring to AUTHORS 2019-01-24 17:24:07 -05:00
Matt Baer ed5c4ec8b1
Merge pull request #65 from nkoehring/optimize-webfont-loading
use font-display:optional to optimize web font loading
2019-01-24 17:22:56 -05:00
Matt Baer 8b94418d3f Bump version to 0.8.0 2019-01-24 17:10:09 -05:00
Matt Baer 73ca34bb21 Ignore all .ini files 2019-01-24 17:09:48 -05:00
Matt Baer 3a6118c207 Set up migrations table on initial setup
This includes the appmigrations table in the schema files, and inserts
the current database version when running writefreely --init-db
2019-01-24 17:08:08 -05:00
Matt Baer 5e686a5b0f Update import schema step
Note the step is only required for multi-user instances.
2019-01-24 16:38:28 -05:00
Matt Baer b91c931ebd Tweak links at top of README 2019-01-24 16:36:58 -05:00
Matt Baer 53dfe4d215 Add Hosting section to README 2019-01-24 16:35:04 -05:00
Matt Baer e853a15303
Merge pull request #68 from writeas/user-invites
Support user invites
2019-01-23 13:02:16 -05:00
Matt Baer eb8f56a6e2 Merge branch 'master' into develop 2019-01-20 17:16:17 -05:00
Matt Baer 5de193a64d Generate encryption keys in configured directory
This makes --gen-keys respect the keys_parent_dir config value
2019-01-20 14:18:09 -05:00
Matt Baer 1c40103fbf Fix generateKey error logging
This logs the actual error when it occurs, instead of always saying that
the key already exists.
2019-01-20 13:43:06 -05:00
Matt Baer cb1bd37f64
Merge pull request #69 from writeas/resource-dirs-config
Support configuring resource directories
2019-01-19 00:26:29 -05:00
Matt Baer 14f0d446e6 Merge branch 'resource-dirs-config' into develop 2019-01-18 19:18:23 -05:00
Matt Baer 5c19d249b6 Log and exit when templates init fails 2019-01-18 19:17:10 -05:00
Matt Baer 53a51be578 Support configuring resource directories
This adds new configuration values that specify the parent directory of
application resources:

- templates_parent_dir
- static_parent_dir
- pages_parent_dir
- keys_parent_dir

For any values not specified, the application will default to the
current directory.

This closes T560
2019-01-18 19:09:27 -05:00
Matt Baer 6c7ee76768 Support configuring resource directories
This adds new configuration values that specify the parent directory of
application resources:

- templates_parent_dir
- static_parent_dir
- pages_parent_dir
- keys_parent_dir

For any values not specified, the application will default to the
current directory.

This closes T560
2019-01-18 18:57:04 -05:00
Matt Baer 41b2f7628f Describe package parse 2019-01-18 11:36:56 -05:00
Matt Baer 820b0ba6b9 Organize flags and add comments 2019-01-18 11:34:25 -05:00
Matt Baer d8876058a6 Add WriteFreely version to admin dash 2019-01-18 11:33:57 -05:00
Matt Baer 01f9dc86dc Fix migrations MySQL table check 2019-01-18 11:26:55 -05:00
Matt Baer 70e823d6ab Support user invites
This includes:

- A new `user_invites` config value that determines who can generate
  invite links
- A new page for generating invite links, with new user navigation link
- A new /invite/ path that allows anyone to sign up via unique invite
  link, even if registrations are closed
- Tracking who (of registered users) has been invited by whom

It requires an updated database with `writefreely --migrate` in order to
work.

This closes T556
2019-01-18 00:05:50 -05:00
Matt Baer 47b2155f92 Add --migrate command
This runs database migrations and obviates the need for manually running
changes.

Ref T509
2019-01-17 13:53:03 -05:00
Norman 7da8b3aef6
Fixes indentation 2019-01-16 23:07:44 +01:00
Matt Baer 6c2bd8031a
Merge pull request #66 from TeDomum/master
Deploy the app in the proper dir for dependency management
2019-01-16 15:26:34 -05:00
kaiyou 8ead0a9d09 Deploy the app in the proper dir for dependency management 2019-01-16 21:05:52 +01:00
koehr 059f0d4c54 use font-display:optional to optimize web font loading 2019-01-16 16:07:20 +01:00
Matt Baer a76144c182 Fix rendered sublist spacing
Along with a recent change to how spaces are output by the
writeas/saturday library, this alters the CSS to make sublists display
correctly, and fixes #27.
2019-01-14 14:01:43 -05:00
Matt Baer a9dff35f70 Bump version to 0.7.1 2019-01-13 09:41:48 -05:00
Matt Baer 062ae0e16a Initialize db on single-user instance config
This fixes the --config step so that when setting up a single-user
instance for the first time (and creating the admin user as part of the
process), the database is automatically initialized before creating that
user.

This removes the need for the --init-db command after --config when
setting up single-user instances.

It fixes #59: "no such table: users" error during the --config step on
single-user instances that haven't previously run --init-db.
2019-01-13 09:08:47 -05:00
Matt Baer d0edbd1936 Use showUserPage func for blog customize page
This fixes the page not rendering on Windows.

Closes #61
2019-01-10 23:11:52 -05:00
Matt Baer 21e6c64708 Add make release-linux command 2019-01-10 11:44:30 -05:00
Matt Baer 6da342b0d1 Add make build-no-sqlite
Supports creating a build without SQLite support compiled in.
2019-01-10 11:44:08 -05:00
Matt Baer 7c9bd10f73 Add Pull Request template 2019-01-09 18:11:19 -05:00
Matt Baer a686b61902 make assets before everything in Docker build 2019-01-07 20:39:42 -05:00
Matt Baer 64a8e2d0a5 Exclude unneeded schema.sql in docker build 2019-01-07 19:22:12 -05:00
Matt Baer fe98bd58fc make assets during docker build 2019-01-07 19:21:55 -05:00
Matt Baer caf976a054 Bump version to 0.7.0 2019-01-07 18:49:16 -05:00
Matt Baer c2a7f19ef0 Add numeric avatars 2019-01-07 15:40:37 -05:00
Matt Baer 2942a6818e Fix AP follower INSERT with SQLite
Previously the query would fail with "no such function: NOW"

Closes #56
2019-01-07 14:35:47 -05:00
Matt Baer 8a555567a6 Fix tagged posts SQLite query
SQLite doesn't have an `RLIKE` function, so the query for hashtagged
posts was failing before. This adds a `regexp` function to SQLite and
correctly retrieves all posts on a blog with the requested hashtag.

This closes #55
2019-01-07 11:55:23 -05:00
Matt Baer 1c58c64c7c Fix SQLite deadlock when creating user
This avoids reading from the database after a transaction has been
started in CreateUser(), fixing the deadlock that occurred before.

Closes #53
2019-01-06 21:30:34 -05:00
Matt Baer f53ced382f Prompt for database type in bug report template 2019-01-06 18:18:11 -05:00
Matt Baer 0a3f5b7d16 Add @geekgonecrazy to authors 2019-01-05 21:27:48 -05:00
Matt Baer 0cf1d13880
Merge pull request #54 from geekgonecrazy/sqlite3-user-password
Only check for username and password if driver type is mysql
2019-01-05 21:13:32 -05:00
Matt Baer a58a756746 Use driver constants in app.go 2019-01-05 17:51:17 -05:00
Aaron Ogle 3722c6ba79
Only check for username and password if driver type is mysql 2019-01-05 14:59:05 -06:00
Matt Baer bf7d422039 Add pager to admin user list
This enables paging through the entire list of users.

Ref T553
2019-01-05 09:37:53 -05:00
Matt Baer 3d301c97e9 Fix admin user paging query 2019-01-05 08:47:42 -05:00
Matt Baer 0e722de82c Add admin user list
This enables admins on multi-user instances to see all users registered,
and view the details of each, including:

- Username
- Join date
- Total posts
- Last post date
- All blogs
  - Public info
  - Views
  - Total posts
  - Last post date
  - Fediverse followers count

This is the foundation for future user moderation features.

Ref T553
2019-01-04 22:28:29 -05:00
Matt Baer 2f4c93cccb Document package and funcs in config.go 2019-01-04 19:58:44 -05:00
Matt Baer a419bd63fc Fix make deps not fetching sqlite3 lib 2019-01-04 19:40:00 -05:00
Matt Baer fca3019e4b Support building without SQLite support
This adds a new `sqlite` build tag that you should include only if you
want SQLite3 support built in. Both `make run` and `make release` create
builds with SQLite included.
2019-01-03 17:57:06 -05:00
Matt Baer 8513def899 Send correct status on 410/500 in handleHTTPError 2019-01-03 15:43:44 -05:00
Matt Baer 19215b0355 Send correct status on 404 in handleHTTPError 2019-01-03 12:27:52 -05:00
Matt Baer 8a07c0f0a0 Extract images with .image extension 2018-12-31 16:19:26 -05:00
Matt Baer 3ae45bc156 Fix spacing around copyright notices 2018-12-31 01:05:26 -05:00
Matt Baer 256d9e3c02 Add download / docker pull counts to README 2018-12-31 00:54:45 -05:00
Matt Baer 739afd2310 Embed schema files in binary
This includes schema.sql and sqlite.sql in the release binary, so they
no longer need to be included in the release archives. This reduces the
number of files extracted, but otherwise leaves all functionality as it
was -- especially the --init-db flag.

Ref T536
2018-12-30 20:10:42 -05:00
Matt Baer d4a08723aa Add @kaiyou to AUTHORS 2018-12-24 17:33:20 -05:00
Matt Baer e525bc32a7 Use language-specific dash in post titles
This closes #1
2018-12-24 13:58:32 -05:00
Matt Baer 69eab50f42 Create darwin archives with macos in the name
(not "darwin")
2018-12-24 12:56:57 -05:00
Matt Baer 1274914207 Add copyright / license notices to .go files 2018-12-24 12:45:15 -05:00
Matt Baer 7f5551105a Fix metadata update redirect on single-user instances
Fixes #50
2018-12-24 10:33:40 -05:00
Matt Baer 1439c8c359 Add --create-user option for regular user
This supports creating a regular user via the command-line.
2018-12-22 10:54:08 -05:00
Matt Baer 5e5b283daf Validate username in admin user creation process
This runs usernames through the same checks as the web interface,
ensuring no invalid user is created, such as user_name or userName.

This closes #49
2018-12-19 21:26:13 -05:00
Matt Baer 852ca5eea4 Merge branch 'master' of github.com:writeas/writefreely 2018-12-18 08:29:24 -05:00
Matt Baer 88e1c65939
Merge pull request #48 from TeDomum/master
Fix the Docker build when installing go-sqlite
2018-12-18 08:29:08 -05:00
Matt Baer f99244b93f Factor out admin user creation logic 2018-12-17 18:13:26 -05:00
Matt Baer 13bf5b6638 Include max number of blogs in nodeinfo 2018-12-17 01:27:04 -05:00
kaiyou 38184f4d13 Fix the Docker build when installing go-sqlite
Go-SQLite requires sqlite library headers and gcc.
2018-12-16 20:49:27 +01:00
Matt Baer 11de25237d Fix db.Ping() call 2018-12-15 14:14:09 -05:00
Matt Baer dad79adfd2 Update features list
And Quick Start intro
2018-12-14 22:43:32 -05:00
Matt Baer 2178b4abf2 Check database connection on startup 2018-12-13 19:15:09 -05:00
Matt Baer 3fab9f6439 Add missing CSS for highlightjs 2018-12-10 16:22:11 -05:00
Matt Baer 8beccaf6c2 Reserve "community" username 2018-12-10 16:18:09 -05:00
Matt Baer 4b8d5e3e37 Bump version to 0.6.0 2018-12-10 16:17:57 -05:00
Matt Baer 25a68d0c0e Add Reader section
This adds a "Reader" section of the site for admins who want to enable
it for their instance. That means visitors can go to /read and see who
has publicly shared their writing. They can also follow all public posts
via RSS by going to /read/feed/. Writers on an instance with this
`local_timeline` setting enabled can publish to the timeline by going
into their blog settings and choosing the "Public" visibility setting.

The `local_timeline` feature is disabled by default, as is the Public
setting on writer blogs. Enabling it adds a "Reader" navigation item and
enables the reader endpoints. This feature will also consume more
memory, as public posts are cached in memory for 10 minutes.

These changes include code ported over from Read.Write.as, and thus
include some experimental features like filtering public posts by tags
and authors. These features aren't well-tested or complete.

Closes T554
2018-12-10 16:08:07 -05:00
Matt Baer 7828bf6ba2 Merge branch 'develop' 2018-12-10 14:22:53 -05:00
Matt Baer 2422601e89
Merge pull request #45 from writeas/sqlite-support
SQLite support
2018-12-10 14:22:27 -05:00
Matt Baer d3b120be75 Merge branch 'sqlite-support' into develop 2018-12-10 00:27:50 -05:00
Matt Baer a3e287a77a Make accesstokens.user_agent NULL by default
The field is currently unused in the app, and it was causing problems in
SQLite.

Ref T529
2018-12-10 00:26:54 -05:00
Matt Baer 9fb7777c33 Switch to xgo for cross-compilation
Ref T529
2018-12-08 18:27:50 -05:00
Matt Baer 111945bc5d Add new -c [filename] option for supplying config file
This allows users to load a different configuration file instead of the
default config.ini. It works in combination with other configuration
actions, like --config and --create-config.
2018-12-08 17:49:19 -05:00
Matt Baer 20c77989ba Update setup steps 2018-12-08 15:26:33 -05:00
Matt Baer 17c816477b Add SQLite option to config process
Ref T529
2018-12-08 13:36:51 -05:00
Matt Baer ba3d6ae64c Support custom SQLite database file name
Ref T529
2018-12-08 13:34:29 -05:00
Matt Baer c6851fee50 Fix duplicate key checks in SQLite
Ref T529
2018-12-08 13:25:20 -05:00
Matt Baer 4b780361bf Fix upsert queries on SQLite
Ref T529
2018-12-08 12:58:45 -05:00
Matt Baer 026604b3dd Fix pinned post content truncation with SQLite
This extracts the LEFT/SUBSTR logic into its own datastore.clip() method
that also works correctly with SQLite.

Ref T529
2018-12-08 12:54:49 -05:00
Matt Baer daaa4564bb Fix post `created` date in SQLite
We store times in UTC in all other places, but the post.Created logic
when creating a post meant that dates were being stored in a user's
local timezone. This fixes that.

Ref T529
2018-12-08 12:51:27 -05:00
Matt Baer 6f4c004e8c Fix SQLite date format string
The formatted string was invalid before, causing date parsing to fail.
This fixes that.

Ref T529
2018-12-08 12:28:52 -05:00
Matt Baer bc1b3fdfb7 Move NOW() calls to datastore.now() method
Ref T529
2018-12-08 12:15:16 -05:00
Matt Baer 7fab11b3c8 Move initial config loading to its own func 2018-12-08 10:39:25 -05:00
Matt Baer afede80e87 Merge branch 'master' into sqlite-support 2018-12-08 10:34:43 -05:00
Matt Baer 9e466a6d23 Locally host MathJax
This closes T551
2018-12-07 16:50:29 -05:00
Matt Baer 860e8c0120 Locally host highlightjs
Part of T551
2018-12-07 16:37:14 -05:00
Matt Baer e06b328785 Merge branch 'master' of github.com:writeas/writefreely 2018-12-05 21:45:26 -05:00
Matt Baer df4cd9ed00 Add --create-admin option
This enables automated user creation by running:

  writefreely --create-admin username:password

It will fail if an admin (first user) already exists, which makes this
suitable for use on both for single- and multi-user instances.

Closes T544
2018-12-05 21:41:51 -05:00
Brad Koehn c2a3b4935c
Merge pull request #44 from TeDomum/master
Add openssl and root anchors for federation queries
2018-12-05 07:07:45 -06:00
kaiyou d01d2c80b6 Add openssl and root anchors for federation queries 2018-12-05 13:33:53 +01:00
Marcel van der Boom 5856e91f84 Allow beginning of line as begin of tag as well 2018-12-04 18:26:18 +01:00
Matt Baer 8ceb165020 Bump version to 0.5 2018-12-03 18:57:31 -05:00
Matt Baer cb2b30b379 Show stats on About page if public_stats enabled
Previously, it would only show the stats if federation was enabled.
2018-12-03 18:49:12 -05:00
Matt Baer 8cbc02d7cf Hide unneeded config values in single-user mode
(in the admin UI.)
2018-12-03 18:45:55 -05:00
Matt Baer cbf6ff54df Add site_description config value
This changes what displays in the NodeInfo, and in the future might be
used for other things.
2018-12-03 18:36:33 -05:00
Matt Baer 573ce02739 Remove params from URL after loading admin panel
This ensures that refreshing the page doesn't include any past success /
failure messages.
2018-12-03 18:34:36 -05:00
Matt Baer d2f89c6360 Open pages linked from admin UI in new window 2018-12-03 17:34:57 -05:00
Matt Baer 9fe4b09de5 Support editing some config values in admin UI
This is almost all of T541
2018-12-03 17:30:31 -05:00
Matt Baer 09a3fe09fe
Merge pull request #39 from mrvdb/codehighlight
Codehighlight, add support for all languages
2018-12-03 12:24:36 -05:00
Matt Baer 5fc41687be Fix whitespace 2018-12-03 12:18:04 -05:00
Matt Baer f04469beee Fix SQLite concurrency issues
A quick test with ApacheBench revealed that SQLite really can't handle
multiple concurrent requests with the default settings, due to a locked
database. This fixes that by following the suggestions here:
https://github.com/mattn/go-sqlite3#faq

Testing with ab -n 100 -c 5 http://localhost:8080/blog/post shows that
this fixes the issue. But we could improve performance by reducing
writes, like what's mentioned in T545.

Part of T529
2018-12-03 12:05:52 -05:00
Marcel van der Boom 79a7ca750e Require hashtags to have a space before them
This improves rendering in a number of situations:

- it keeps anchor tags working
- it gives the user some control for not linking, for example in code
  blocks.

Con:
hashTags at the beginning of a line without a space won't get linked.

Workaround related to issues #42 and #6 and #33
2018-12-03 12:08:02 +01:00
Matt Baer 61de04338e Extract out common DB connection logic
T529
2018-12-02 17:21:43 -05:00
Matt Baer c6d3ef7596 Include SQLite schema in releases
Part of T529
2018-12-02 17:21:18 -05:00
Matt Baer 31b802e440 Add backticks on table names in sqlite.sql
This keeps queries similar to schema.sql, and ensures log statements are
correct when running --init-db
2018-12-02 16:24:44 -05:00
Matt Baer fc856e36eb Fix editing metadata on single-user instances
This closes #40
2018-12-01 16:27:14 -05:00
Ben Overmyer 6cb86214d7 SQLite support added. 2018-12-01 12:07:25 -06:00
Matt Baer c60c61c5b8 Add link to AUR package 2018-12-01 10:13:45 -05:00
Marcel van der Boom 3b4d14f194 Only push language uri once on load stack 2018-11-29 17:36:50 +01:00
Marcel van der Boom b034a08350 Merge remote-tracking branch 'upstream/master' into codehighlight
* upstream/master:
  Re-add https in log message
  Also use bind address on standalone redirect
  Allow 'bind' in config to specify bind address
  Only log ActivityPub info when debugging
  Bump version to 0.4
  Move softwareVer to var() block
  Add @koehn to AUTHORS
  Add `make release-docker`
  Move docker build to `make build-docker`
  extracted docker command a la `go` and `make`
  added docker support to `make release`
  removed an unnecessary debugging statement
  added .git to make builds cache more effectively and run faster
  switched to much smaller alpine image since golang not required at runtime
  Updated Dockerfile to produce smaller image with minimum content and a few extra features; added .dockerignore
2018-11-29 16:30:31 +01:00
Marcel van der Boom c6e4967728 Add support for all languages
- dynamically construct a list of languages to load
- fire hightlighting once last file has loaded
2018-11-29 16:25:53 +01:00
Matt Baer fb18b8f6e3
Merge pull request #38 from mrvdb/bindaddress
Allow 'bind' in config to specify bind address
2018-11-27 13:58:17 -05:00
Matt Baer fe78d6d47f Re-add https in log message 2018-11-27 10:55:43 -05:00
Ben Overmyer dd5f6870d8 Add SQLite option for database. 2018-11-26 14:11:10 -06:00
Marcel van der Boom 875c758ba2 Also use bind address on standalone redirect 2018-11-26 18:34:09 +01:00
Marcel van der Boom 543f6c9ae3 Allow 'bind' in config to specify bind address
Minimal changes, definitely WIP, to resolve:

- how to support dualstack when not using localhost?
- net/http package uses string, mentions IP address instead of bind,
  need info.
2018-11-26 16:50:36 +01:00
Matt Baer 94badbc838 Only log ActivityPub info when debugging 2018-11-26 08:39:15 -05:00
Matt Baer 42a47258da Bump version to 0.4 2018-11-26 08:37:36 -05:00
Matt Baer f0250d501f Move softwareVer to var() block 2018-11-26 08:37:06 -05:00
Matt Baer 6ed9fef88f Add @koehn to AUTHORS 2018-11-24 18:43:33 -05:00
Matt Baer 223e385048
Merge pull request #36 from writeas/docker-release
added docker support to `make release`
2018-11-24 14:22:52 -05:00
Matt Baer 7bbc1b4b47 Add `make release-docker` 2018-11-24 14:18:11 -05:00
Matt Baer 402f66770e Move docker build to `make build-docker` 2018-11-24 14:11:39 -05:00
koehn 695bc79696 extracted docker command a la `go` and `make` 2018-11-24 17:57:01 +00:00
koehn 50a6a1ee40 added docker support to `make release` 2018-11-24 17:49:52 +00:00
Matt Baer bdcacbc632
Merge pull request #34 from koehn/master
Updated Dockerfile
2018-11-24 11:01:02 -05:00
Matt Baer cf784388f0
Merge pull request #31 from mrvdb/codehighlight
Add code highlighting
2018-11-23 13:23:50 -05:00
Matt Baer 3dd6e34914 Fix whitespace in templates 2018-11-23 12:42:30 -05:00
Matt Baer 2b5e869916 Rename render.tmpl to post-render.tmpl 2018-11-23 12:37:59 -05:00
Matt Baer 03c36af2f9 Optimize template loading on non-post pages
Avoids loading render.tmpl where it isn't needed.
2018-11-23 12:37:59 -05:00
Matt Baer 246a6dc554 Fix whitespace in highlight JS 2018-11-23 12:37:59 -05:00
Marcel van der Boom a54f5fc9d8 Fix typos 2018-11-22 16:03:44 +01:00
Marcel van der Boom d63db27917 Merge remote-tracking branch 'upstream/master' into codehighlight
* upstream/master:
  Work as a standalone server, including TLS
  Include About/Privacy page content in page description
  Show instance stats on About page
  Change default database name to writefreely
  Use and validate database type before connecting
  Mention Contributing Guide in README
  Add AUTHORS.md
  Fix About page link in Admin dash
  Include version in archives made by `make release`
  Remove keys.sh from make release
  Add make release
2018-11-22 15:09:58 +01:00
Marcel van der Boom e400fe1649 Only load highlght css when needed 2018-11-22 15:01:11 +01:00
Marcel van der Boom add06ee56c Only load js when there are blocks to highlight
Crux was the load event for the script tag inserted.
2018-11-22 14:31:25 +01:00
Brad Koehn 6f0c7aed66 removed an unnecessary debugging statement 2018-11-22 06:57:05 -06:00
Brad Koehn aebad1ce54 added .git to make builds cache more effectively and run faster 2018-11-22 06:56:29 -06:00
Matt Baer 09f5953431 Work as a standalone server, including TLS
This supports running the server on port 443, serving secure pages, with
automatic redirects from the insecure site. It also modifies the
configuration process to better guide users through configuring for
running behind a reverse proxy or as a standalone server.

This closes T537
2018-11-21 18:26:19 -05:00
Matt Baer 77e79acd06 Include About/Privacy page content in page description 2018-11-21 15:04:47 -05:00
Matt Baer be2c7ef86b Show instance stats on About page
This also moves the stats database logic out of nodeinfo.go and into
database.go.
2018-11-21 14:08:47 -05:00
Brad Koehn f2a07db23a switched to much smaller alpine image since golang not required at runtime 2018-11-21 12:51:37 -06:00
Matt Baer b9d7d4ce24 Change default database name to writefreely
(not writeas)
2018-11-21 13:36:00 -05:00
Matt Baer f3df2b4159 Use and validate database type before connecting
Just the start of changes needed for T529.
2018-11-21 13:10:10 -05:00
Matt Baer 82e45ef5ae Mention Contributing Guide in README 2018-11-21 11:54:01 -05:00
Matt Baer 4c7969d808 Add AUTHORS.md 2018-11-21 11:46:23 -05:00
Brad Koehn 5039853edc Updated Dockerfile to produce smaller image with minimum content and a few extra features; added .dockerignore 2018-11-21 10:38:56 -06:00
Matt Baer 8a5811e3e9 Fix About page link in Admin dash 2018-11-21 11:12:44 -05:00
Matt Baer bf5ed00484 Include version in archives made by `make release` 2018-11-20 18:35:52 -05:00
Matt Baer 1b57ea6f37 Merge branch 'master' of github.com:writeas/writefreely 2018-11-20 16:36:52 -05:00
Marcel van der Boom 8144c9581d Use the samen render common template for user pages 2018-11-20 22:11:53 +01:00
Marcel van der Boom 7181fd9d9e Remove code for CodeHighlighting UI
We load it always, no need for a user config UI
2018-11-20 22:11:09 +01:00
Marcel van der Boom a659acdc14 Bring all MathJax logic into one common template
Might as well do this right away, we're here anyway.
2018-11-20 22:01:25 +01:00
Marcel van der Boom a82882fddc Put the highlight logic into one common template
It now loads unconditionally and highlights all code blocks.

TODO: optimize to not load when there are no blocks
2018-11-20 21:51:39 +01:00
Marcel van der Boom 03ac402c37 Use the same highlightjs library everywhere 2018-11-20 18:37:56 +01:00
Marcel van der Boom ed3ea37537 Merge branch 'master' into codehighlight
* master:
  Don't automatically include "v"  from git
  Get versioninfo from the git repository
2018-11-20 18:24:36 +01:00
Matt Baer a4751c7abf
Merge pull request #32 from mrvdb/versioninfo
Get versioninfo from the git repository
2018-11-20 12:18:18 -05:00
Marcel van der Boom b030921691 Don't automatically include "v" from git
- "v" should not be part of the version (softwareVer variable is used
  in other places)
- formatting
2018-11-20 18:14:02 +01:00
Marcel van der Boom c25d0bef67 Get versioninfo from the git repository
Sets value of softwareVer during build
2018-11-20 14:21:13 +01:00
Marcel van der Boom 348d548793 Add code highlighting option
This is a first stab at having a configurable code highlighting option,
similar to the MathJax rendering option. This change makes a checkbox
in the settings for code highlighting using the highlightjs.org
library.
What works: code highlighting in multi-user env is like I would
expect. single and anon(?) needs work

Things to resolve/consider:
- does the .IsCode test for code highlighting need to stay? At least
this and that should use the same version of the highlight.js lib.
- can the common templating part be 'included' somehow?
- the anon vs single-user vs multi-user code is not completely
clear (to me)
- bring js to local instead of cloudfare cdn (perhaps combine with
MathJax)
2018-11-20 11:29:12 +01:00
Matt Baer 87ffafeb54 Remove keys.sh from make release 2018-11-18 22:31:32 -05:00
Matt Baer 64e52ba00d Add make release 2018-11-18 22:29:35 -05:00
Matt Baer bdc4f270f8 Support editing About and Privacy pages from Admin panel
This allows admin to edit these pages from the web, using Markdown. It
also dynamically loads information on those pages now, and makes loading
`pages` templates a little easier to find in the code / more explicit.

It requires this new schema change:

CREATE TABLE IF NOT EXISTS `appcontent` (
  `id` varchar(36) NOT NULL,
  `content` mediumtext CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
  `updated` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

This closes T533
2018-11-18 21:58:50 -05:00
Matt Baer 7d87aad55a Add basic admin dashboard with app stats
Start of T538
2018-11-18 20:18:22 -05:00
Matt Baer 99a10a2563 Accept different `created` time on post publish
This helps with post importing and clients that want to support post
scheduling. It also changes how Collection.ForPublic() works, no longer
resetting the ID.

Closes T532
2018-11-18 14:39:50 -05:00
Matt Baer 7321f6d5a0 Upgrade Lora to v2.202 (cyrillic)
This fixes things like em-dashes and adds new glyphs.
2018-11-17 22:28:58 -05:00
Matt Baer b58cb1e541 Fix Novel blog post order in feeds and outbox 2018-11-17 21:59:04 -05:00
Matt Baer 2ad2270973 Merge branch 'master' of github.com:writeas/writefreely 2018-11-17 21:57:59 -05:00
Matt Baer 093837b3a9 Fix wrong collection data in feed
Previously the logic was mixed up -- multi-user blogs would always load
collection number 1.
2018-11-17 21:51:14 -05:00
Matt Baer f7cc3764d0
Merge pull request #29 from BenOvermyer/improve-docker-workflow
Improved the Docker dev workflow slightly.
2018-11-17 18:36:06 -05:00
Ben Overmyer 2de37a4ddb Improved the Docker dev workflow slightly. 2018-11-16 14:53:42 -06:00
Matt Baer ad1180f202 Bump version to 0.3 2018-11-16 13:20:40 -05:00
Matt Baer 8e6d0daa06 Federate draft when published to a blog
This now sends out a `Create` activity when a post is moved from a draft
to a blog.

This closes #9. Closes T526.
2018-11-16 12:42:21 -05:00
Matt Baer 778098d925 Remove JS editor remnants
This was another issue mentioned in #28
2018-11-16 12:20:36 -05:00
Matt Baer a0f8a2df3b Fix MathJax value not sticking
This closes #28
2018-11-16 12:19:56 -05:00
Matt Baer 3595c8163c Use Accept ID on blog's origin
instead of the requesting user's. This closes #16
2018-11-15 17:05:33 -05:00
Matt Baer ed1b77ea3b Support outputting version with -v flag 2018-11-15 14:51:03 -05:00
Matt Baer 58d163d2e0 Properly handle failed AP requests 2018-11-14 18:30:24 -05:00
Matt Baer 0eac9251c0 Add location to MySQL connection string
This should address #23
2018-11-14 17:47:58 -05:00
Matt Baer 9d42d89a27 Fix "view blog" links on customize page in single mode
This closes #21
2018-11-14 15:37:10 -05:00
Matt Baer 9016f87041 Add --reset-pass option for admin pass reset
Usage: writefreely --reset-pass <username>

This closes #25, closes T534
2018-11-14 15:03:22 -05:00
Matt Baer 8c851545f6 Merge branch 'master' of github.com:writeas/writefreely 2018-11-14 13:16:49 -05:00
Matt Baer 6cede306b9
Merge pull request #24 from TheJF/add_dockerfile_and_docker-compose
Add Dockerfile and Docker Compose files
2018-11-14 10:24:27 -05:00
Jean-Francois Arseneau 5b393309a5 Add Dockerfile and Docker Compose files
This adds the first version of a Dockerfile, built on an Alpine Linux variation of the golang image, as well as a Docker Compose file that spins up both a MariaDB and the instance. It also updates the README with instructions on how to get Write Freely running with this setup.
2018-11-13 20:24:06 -08:00
Matt Baer b1686b1d46 Add --init-db flag to create schema in app
Part of T530
2018-11-13 13:04:52 -05:00
Matt Baer 6220e55559 Provide default `updated` value
This addresses #18
2018-11-13 08:52:42 -05:00
Matt Baer bf44236748 Bump version to 0.2.1 2018-11-12 21:02:45 -05:00
Matt Baer ac3d454f22 Include default `posts`.`privacy` value on post creation
This closes #7
2018-11-12 21:00:54 -05:00
Matt Baer efbba9e1ba Reuse statement on post insert 2018-11-12 20:51:04 -05:00
Matt Baer 8a3974c27d Merge branch 'master' of github.com:writeas/writefreely 2018-11-12 20:13:15 -05:00
Matt Baer 3576ab15d1 Create issue templates 2018-11-12 20:12:31 -05:00
Matt Baer 1836735499 Add login link on single-user blog
This closes #15
2018-11-12 19:47:14 -05:00
Matt Baer ecac59bf62 Fix Accept IDs
Prepend with hash, not hyphen

This closes #16
2018-11-12 19:31:21 -05:00
Matt Baer 78953f27f0 Fix badge 2018-11-12 13:40:38 -05:00
Matt Baer 002d0e6309 Bump version to 0.2 2018-11-11 18:37:30 -05:00
Matt Baer b8ce944b5c Add IRC badge in README 2018-11-11 18:37:13 -05:00
Matt Baer 7bc873580c Move key generation to app from keys.sh
This eliminates an external dependency needed for install, and ensures
the app can run on Windows.
2018-11-11 17:52:24 -05:00
Matt Baer 96c197453d Fix key loading on Windows + move paths into vars
This uses filepath.Join() to make sure they always load correctly
2018-11-11 17:25:34 -05:00
Matt Baer 561568343a Use avatar as blog link social media image 2018-11-11 15:34:26 -05:00
Matt Baer c996ae1cad Add To and CC on Create activities
Part of #8
2018-11-11 13:11:01 -05:00
Matt Baer 393f6d6834 Add ID on Accept activities
Part of #8
2018-11-11 13:10:39 -05:00
Matt Baer bbed72ff6b Remove unneeded followers column from remoteusers
To migrate:

  ALTER TABLE `remoteusers` DROP `followers`;
2018-11-11 12:43:24 -05:00
Matt Baer a0ac7bbbcd Insert missing default values on post creation
- view_count

This closes #7
2018-11-11 10:31:37 -05:00
Matt Baer 426fa01fb4 Explain where to post questions, feedback, bugs 2018-11-11 10:30:50 -05:00
Matt Baer 704558114d Remove r.Host from server logs 2018-11-11 09:40:18 -05:00
Matt Baer 7538fa68bc Insert default values on collection creation
This fixes MySQL errors about missing default values.
2018-11-11 09:35:01 -05:00
Matt Baer 331d76f75e Fix overflowing green bg in setup titles 2018-11-10 21:44:41 -05:00
Matt Baer 8db0afabf6 Show number of followers on stats page 2018-11-10 20:56:36 -05:00
Matt Baer 1cb593fd8c Fix unnecessarily connecting to DB after multi-user config 2018-11-10 20:43:00 -05:00
Matt Baer ae019e4dc3 Show error when ListenAndServe fails
and exit with 1
2018-11-10 20:41:35 -05:00
Matt Baer 59f436052e Add info about running in production 2018-11-10 16:33:56 -05:00
Matt Baer 5920578c70 Add links about instances in README 2018-11-10 16:19:05 -05:00
Matt Baer 8a8db3be53 Support retrieving posts as AS2 object
Previously this was only supported on
/api/collections/{alias}/posts/{id} -- this also allows it on
/api/posts/{id}, so things like `Announce`s work.

This closes #4
2018-11-10 01:29:48 -05:00
Matt Baer 3bf10d8074 Remove default value for `posts`.`updated`
This closes #3
2018-11-10 00:28:19 -05:00
454 changed files with 37431 additions and 21525 deletions

1
.dockerignore Normal file
View File

@ -0,0 +1 @@
Dockerfile

10
.editorconfig Normal file
View File

@ -0,0 +1,10 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
[*.go]
indent_style = tab

2
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,2 @@
github: writefreely
open_collective: writefreely

25
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,25 @@
---
name: Bug report
about: Let us know what went wrong.
labels: ❓ bug
---
### Describe the bug
Explain what the bug is, in as much detail as possible...
### Steps to reproduce (if necessary)
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. ...
### Expected behavior
What should've happened?
### Application configuration
- **Single mode or Multi-user mode?**
- **Database?** [mysql/sqlite]
- **Open registration?** [yes/no]
- **Federation enabled?** [yes/no]
**Version or last commit**:

View File

@ -0,0 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
---
# PLEASE DON'T SUBMIT FEATURE REQUESTS HERE #
Instead, post them to our forums: https://discuss.write.as/c/feedback/feature-requests

15
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: "gomod" # See documentation for possible values
directory: "/" # Location of package manifests
open-pull-requests-limit: 50
schedule:
interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/Dockerfile"
schedule:
interval: "daily"

5
.github/pull_request_template.md vendored Normal file
View File

@ -0,0 +1,5 @@
[Describe the pull request here...]
---
- [ ] I have signed the [CLA](https://phabricator.write.as/L1)

61
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: Build container image, publish as GitHub-package
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
branches: [ main, develop ]
# Publish semver tags as releases.
tags:
- 'v*.*.*'
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4.6.0
with:
images: |
ghcr.io/${{ github.repository }}
flavor: latest=true
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker images
uses: docker/build-push-action@v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

9
.gitignore vendored
View File

@ -1,5 +1,12 @@
node_modules
*~
*.swp
*.swo
config.ini
static/local/custom.css
build
tmp
*.ini
*.db
bindata.go

View File

@ -1,4 +1,9 @@
language: go
go:
- "1.10.x"
- "1.13.x"
env:
- GO111MODULE=on
script: make ci

11
AUTHORS.md Normal file
View File

@ -0,0 +1,11 @@
# WriteFreely Contributors
WriteFreely is built by [Matt Baer](https://github.com/thebaer), with contributions from:
* [Jean-Francois Arseneau](https://github.com/TheJF)
* [Ben Overmyer](https://github.com/BenOvermyer)
* [Marcel van der Boom](https://github.com/mrvdb)
* [Brad Koehn](https://github.com/koehn)
* [kaiyou](https://github.com/kaiyou)
* [Aaron Ogle](https://github.com/geekgonecrazy)
* [Norman](https://github.com/nkoehring)

View File

@ -1,13 +1,99 @@
# Contributing to Write Freely
# Contributing to WriteFreely
Welcome! We're glad you're interested in contributing to the Write Freely project.
Welcome! We're glad you're interested in contributing to WriteFreely.
To start, we'd suggest checking out [our Phabricator board](https://phabricator.write.as/tag/write_freely/) to see where the project is at and where it's going. You can also [join the Write Freely forums](https://discuss.write.as/c/writefreely) to start talking about what you'd like to do or see.
For **questions**, **help**, **feature requests**, and **general discussion**, please use [our forum](https://discuss.write.as).
## Contributing code
For **bug reports**, please [open a GitHub issue](https://github.com/writefreely/writefreely/issues/new). See our guide on [submitting bug reports](https://writefreely.org/contribute#bugs).
We gladly welcome development help, regardless of coding experience. We can also use help [translating the app](https://poeditor.com/join/project/TIZ6HFRFdE) and documenting it!
## Getting Started
**Before writing or 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/).
There are many ways to contribute to WriteFreely, from code to documentation, to translations, to help in the community!
Once you've done that, please feel free to [submit a pull request](https://github.com/writeas/writefreely/pulls) for any small improvements. For larger projects, please [join our development discussions](https://discuss.write.as/c/writefreely) or [get in touch](https://write.as/contact) so we can talk about what you'd like to work on.
See our [Contributing Guide](https://writefreely.org/contribute) on WriteFreely.org for ways to contribute without writing code. Otherwise, please read on.
## Working on WriteFreely
First, you'll want to clone the WriteFreely repo, install development dependencies, and build the application from source. Learn how to do this in our [Development Setup](https://writefreely.org/docs/latest/developer/setup) guide.
### 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.
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/).
### Branching
All stable work lives on the `master` branch. We merge into it only when creating a release. Releases are tagged using semantic versioning.
While developing, we primarily work from the `develop` branch, creating _feature branches_ off of it for new features and fixes. When starting a new feature or fix, you should also create a new branch off of `develop`.
#### Branch naming
For fixes and modifications to existing behavior, branch names should follow a similar pattern to commit messages (see below), such as `fix-post-rendering` or `update-documentation`. You can optionally append a task number, e.g. `fix-post-rendering-T000`.
For new features, branches can be named after the new feature, e.g. `activitypub-mentions` or `import-zip`.
#### Pull request scope
The scope of work on each branch should be as small as possible -- one complete feature, one complete change, or one complete fix. This makes it easier for us to review and accept.
### Writing code
We value reliable, readable, and maintainable code over all else in our work. To help you write that kind of code, we offer a few guiding principles, as well as a few concrete guidelines.
#### Guiding principles
* Write code for other humans, not computers.
* The less complexity, the better. The more someone can understand code just by looking at it, the better.
* Functionality, readability, and maintainability over senseless elegance.
* Only abstract when necessary.
* Keep an eye to the future, but don't pre-optimize at the expense of today's simplicity.
#### Code guidelines
* Format all Go code with `go fmt` before committing (**important!**)
* Follow whitespace conventions established within the project (tabs vs. spaces)
* Add comments to exported Go functions and variables
* Follow Go naming conventions, like using [`mixedCaps`](https://golang.org/doc/effective_go.html#mixed-caps)
* Avoid new dependencies unless absolutely necessary
### Commit messages
We highly value commit messages that follow established form within the project. Generally speaking, we follow the practices [outlined](https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines) in the Pro Git Book. A good commit message will look like the following:
* **Line 1**: A short summary written in the present imperative tense. For example:
* ✔️ **Good**: "Fix post rendering bug"
* ❌ No: ~~"Fixes post rendering bug"~~
* ❌ No: ~~"Fixing post rendering bug"~~
* ❌ No: ~~"Fixed post rendering bug"~~
* ❌ No: ~~"Post rendering bug is fixed now"~~
* **Line 2**: _[left blank]_
* **Line 3**: An added description of what changed, any rationale, etc. -- if necessary
* **Last line**: A mention of any applicable task or issue
* For Phabricator tasks: `Ref T000` or `Closes T000`
* For GitHub issues: `Ref #000` or `Fixes #000`
#### Good examples
When in doubt, look to our existing git history for examples of good commit messages. Here are a few:
* [Rename Suspend status to Silence](https://github.com/writefreely/writefreely/commit/7e014ca65958750ab703e317b1ce8cfc4aad2d6e)
* [Show 404 when remote user not found](https://github.com/writefreely/writefreely/commit/867eb53b3596bd7b3f2be3c53a3faf857f4cd36d)
* [Fix post deletion on Pleroma](https://github.com/writefreely/writefreely/commit/fe82cbb96e3d5c57cfde0db76c28c4ea6dabfe50)
### Submitting pull requests
Like our GitHub issues, we aim to keep our number of open pull requests to a minimum. You can follow a few guidelines to ensure changes are merged quickly.
First, make sure your changes follow the established practices and good form outlined in this guide. This is crucial to our project, and ignoring our practices can delay otherwise important fixes.
Beyond that, we prioritize pull requests in this order:
1. Fixes to open GitHub issues
2. Superficial changes and improvements that don't adversely impact users
3. New features and changes that have been discussed before with the team
Any pull requests that haven't previously been discussed with the team may be extensively delayed or closed, especially if they require a wider consideration before integrating into the project. When in doubt, please reach out [on the forum](https://discuss.write.as) before submitting a pull request.

50
Dockerfile Normal file
View File

@ -0,0 +1,50 @@
# Build image
# SHA256 of golang:1.21-alpine3.18 linux/amd64
FROM golang@sha256:f475434ea2047a83e9ba02a1da8efc250fa6b2ed0e9e8e4eb8c5322ea6997795 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 -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
WORKDIR /go/src/github.com/writefreely/writefreely
COPY . .
RUN cat ossl_legacy.cnf > /etc/ssl/openssl.cnf
ENV GO111MODULE=on
ENV NODE_OPTIONS=--openssl-legacy-provider
RUN make build \
&& 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 \
/go/src/github.com/writefreely/writefreely/keys \
/go/src/github.com/writefreely/writefreely/cmd \
/stage
# Final image
# SHA256 of alpine:3.18.4 linux/amd64
FROM alpine@sha256:48d9183eb12a05c99bcc0bf44a003607b8e941e1d4f41f9ad12bdcc4b5672f86
RUN apk -U upgrade \
&& apk add --no-cache openssl ca-certificates
COPY --from=build --chown=daemon:daemon /stage /go
WORKDIR /go
VOLUME /go/keys
EXPOSE 8080
USER daemon
ENTRYPOINT ["cmd/writefreely/writefreely"]
HEALTHCHECK --start-period=5s --interval=15s --timeout=5s \
CMD curl -fSs http://localhost:8080/ || exit 1

133
Makefile
View File

@ -1,34 +1,149 @@
GITREV=`git describe | cut -c 2-`
LDFLAGS=-ldflags="-s -w -X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)'"
GOCMD=go
GOINSTALL=$(GOCMD) install
GOBUILD=$(GOCMD) build
GOTEST=$(GOCMD) test
GOINSTALL=$(GOCMD) install $(LDFLAGS)
GOBUILD=$(GOCMD) build $(LDFLAGS)
GOTEST=$(GOCMD) test $(LDFLAGS)
GOGET=$(GOCMD) get
BINARY_NAME=writefreely
BUILDPATH=build/$(BINARY_NAME)
DOCKERCMD=docker
IMAGE_NAME=writeas/writefreely
TMPBIN=./tmp
all : build
build: deps
ci: deps
cd cmd/writefreely; $(GOBUILD) -v
build: deps
cd cmd/writefreely; $(GOBUILD) -v -tags='netgo sqlite'
build-no-sqlite: deps-no-sqlite
cd cmd/writefreely; $(GOBUILD) -v -tags='netgo' -o $(BINARY_NAME)
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 .
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 .
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 .
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 .
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 .
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 .
build-docker :
$(DOCKERCMD) build -t $(IMAGE_NAME):latest -t $(IMAGE_NAME):$(GITREV) .
test:
$(GOTEST) -v ./...
run:
$(GOINSTALL) ./...
$(GOINSTALL) -tags='netgo sqlite' ./...
$(BINARY_NAME) --debug
deps :
$(GOGET) -v ./...
$(GOGET) -tags='sqlite' -d -v ./...
install :
./keys.sh
deps-no-sqlite:
$(GOGET) -d -v ./...
install : build
cmd/writefreely/$(BINARY_NAME) --config
cmd/writefreely/$(BINARY_NAME) --gen-keys
cmd/writefreely/$(BINARY_NAME) --init-db
cd less/; $(MAKE) install $(MFLAGS)
release : clean ui
mkdir -p $(BUILDPATH)
cp -r templates $(BUILDPATH)
cp -r pages $(BUILDPATH)
cp -r static $(BUILDPATH)
rm -r $(BUILDPATH)/static/local
scripts/invalidate-css.sh $(BUILDPATH)
mkdir $(BUILDPATH)/keys
$(MAKE) build-linux
mv build/$(BINARY_NAME)-linux-amd64 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_amd64.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-arm6
mv build/$(BINARY_NAME)-linux-arm-6 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_arm6.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-arm7
mv build/$(BINARY_NAME)-linux-arm-7 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_arm7.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-arm64
mv build/$(BINARY_NAME)-linux-arm64 $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_arm64.tar.gz -C build $(BINARY_NAME)
rm $(BUILDPATH)/$(BINARY_NAME)
$(MAKE) build-darwin
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-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)
rm $(BUILDPATH)/$(BINARY_NAME).exe
$(MAKE) build-docker
$(MAKE) release-docker
# This assumes you're on linux/amd64
release-linux : clean ui
mkdir -p $(BUILDPATH)
cp -r templates $(BUILDPATH)
cp -r pages $(BUILDPATH)
cp -r static $(BUILDPATH)
mkdir $(BUILDPATH)/keys
$(MAKE) build-no-sqlite
mv cmd/writefreely/$(BINARY_NAME) $(BUILDPATH)/$(BINARY_NAME)
tar -cvzf $(BINARY_NAME)_$(GITREV)_linux_amd64.tar.gz -C build $(BINARY_NAME)
release-docker :
$(DOCKERCMD) push $(IMAGE_NAME)
ui : force_look
cd less/; $(MAKE) $(MFLAGS)
cd prose/; $(MAKE) $(MFLAGS)
$(TMPBIN):
mkdir -p $(TMPBIN)
$(TMPBIN)/xgo: deps $(TMPBIN)
$(GOBUILD) -o $(TMPBIN)/xgo src.techknowlogick.com/xgo
clean :
-rm -rf build
-rm -rf tmp
cd less/; $(MAKE) clean $(MFLAGS)
force_look :
true

107
README.md
View File

@ -1,86 +1,89 @@
&nbsp;
<p align="center">
<a href="https://writefreely.org"><img src="https://writefreely.org/writefreely.svg" width="350px" alt="Write Freely" /></a>
<a href="https://writefreely.org"><img src="https://writefreely.org/img/writefreely.svg" width="350px" alt="WriteFreely" /></a>
</p>
<hr />
<p align="center">
<a href="https://github.com/writeas/writefreely/releases/">
<img src="https://img.shields.io/github/release/writeas/writefreely.svg" alt="Latest release" />
</a>
<a href="https://goreportcard.com/report/github.com/writeas/writefreely">
<img src="https://goreportcard.com/badge/github.com/writeas/writefreely" alt="Go Report Card" />
<a href="https://github.com/writefreely/writefreely/releases/">
<img src="https://img.shields.io/github/release/writefreely/writefreely.svg" alt="Latest release" />
</a>
<a href="https://travis-ci.org/writeas/writefreely">
<img src="https://travis-ci.org/writeas/writefreely.svg" alt="Build status" />
<img src="https://travis-ci.org/writefreely/writefreely.svg" alt="Build status" />
</a>
<a href="https://github.com/writefreely/writefreely/releases/latest">
<img src="https://img.shields.io/github/downloads/writefreely/writefreely/total.svg" />
</a>
<a href="https://goreportcard.com/report/github.com/writefreely/writefreely">
<img src="https://goreportcard.com/badge/github.com/writefreely/writefreely" alt="Go Report Card" />
</a>
<a href="https://hub.docker.com/r/writeas/writefreely/">
<img src="https://img.shields.io/docker/pulls/writeas/writefreely.svg" />
</a>
</p>
&nbsp;
WriteFreely is a beautifully pared-down blogging platform that's simple on the surface, yet powerful underneath.
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.
It's designed to be flexible and share your writing widely, so it's built around plain text and can publish to the _fediverse_ via ActivityPub. It's easy to install and lightweight.
![](https://writefreely.org/img/screens/pencil-reader.png)
**Note** this is currently alpha software. We're quickly moving out of this v0.x stage, but while we're in it, there are no guarantees that this is ready for production use.
[Try the writing experience](https://write.as/new)
[Find an instance](https://writefreely.org/instances)
## Features
* Start a blog for yourself, or host a community of writers
* Form larger federated networks, and interact over modern protocols like ActivityPub
* Write on a dead-simple, distraction-free and super fast editor
* Publish drafts and let others proofread them by sharing a private link
* Build more advanced apps and extensions with the [well-documented API](https://developers.write.as/docs/api/)
### Made for writing
Built on a plain, auto-saving editor, WriteFreely gives you a distraction-free writing environment. Once published, your words are front and center, and easy to read.
### A connected community
Start writing together, publicly or privately. Connect with other communities, whether running WriteFreely, [Plume](https://joinplu.me/), or other ActivityPub-powered software. And bring members on board from your existing platforms, thanks to our OAuth 2.0 support.
### Intuitive organization
Categorize articles [with hashtags](https://writefreely.org/docs/latest/writer/hashtags), and create static pages from normal posts by [_pinning_ them](https://writefreely.org/docs/latest/writer/static) to your blog. Create draft posts and publish to multiple blogs from one account.
### International
Blog elements are localized in 20+ languages, and WriteFreely includes first-class support for non-Latin and right-to-left (RTL) script languages.
### Private by default
WriteFreely collects minimal data, and never publicizes more than a writer consents to. Writers can seamlessly create multiple blogs from a single account for different pen names or purposes without publicly revealing their association.
<h2><a href="https://write.as/writefreely"><img src="https://writefreely.org/img/writeas-readme.png" height="32px" alt="Write.as" /></a></h2>
The quickest way to deploy WriteFreely is with [Write.as](https://write.as/writefreely), a hosted service from the team behind WriteFreely. You'll get fully-managed installation, backup, upgrades, and maintenance — and directly fund our free software work ❤️
[**Learn more on Write.as**](https://write.as/writefreely).
## Quick start
First, download the [latest release](https://github.com/writeas/writefreely/releases/latest) for your OS. It includes everything you need to start your blog.
WriteFreely deploys as a static binary on any platform and architecture that Go supports. Just use our built-in SQLite support, or add a MySQL database, and you'll be up and running!
Now extract the files from the archive, change into the directory, and do the following steps:
For common platforms, start with our [pre-built binaries](https://github.com/writefreely/writefreely/releases/) and head over to our [installation guide](https://writefreely.org/start) to get started.
```bash
# 1) Log into MySQL and run:
# CREATE DATABASE writefreely;
#
# 2) Import the schema with:
mysql -u YOURUSERNAME -p writefreely < schema.sql
### Packages
# 3) Configure your blog
./writefreely --config
You can also find WriteFreely in these package repositories, thanks to our wonderful community!
# 4) Generate data encryption keys (especially for production)
./keys.sh
* [Arch User Repository](https://aur.archlinux.org/packages/writefreely/)
# 5) Run
./writefreely
## Documentation
# 6) Check out your site at the URL you specified in the setup process
# 7) There is no Step 7, you're done!
```
Read our full [documentation on WriteFreely.org](https://writefreely.org/docs) &mdash; and help us improve by contributing to the [writefreely/documentation](https://github.com/writefreely/documentation) repo.
## Development
Ready to hack on your site? Here's a quick overview.
Start hacking on WriteFreely with our [developer setup guide](https://writefreely.org/docs/latest/developer/setup). For Docker support, see our [Docker guide](https://writefreely.org/docs/latest/admin/docker).
### Prerequisites
## Contributing
* [Go 1.10+](https://golang.org/dl/)
* [Node.js](https://nodejs.org/en/download/)
We gladly welcome contributions to WriteFreely, whether in the form of [code](https://github.com/writefreely/writefreely/blob/master/CONTRIBUTING.md#contributing-to-writefreely), [bug reports](https://github.com/writefreely/writefreely/issues/new?template=bug_report.md), [feature requests](https://discuss.write.as/c/feedback/feature-requests), [translations](https://poeditor.com/join/project/TIZ6HFRFdE), or [documentation](https://github.com/writefreely/documentation) improvements.
### Setting up
```bash
go get github.com/writeas/writefreely/cmd/writefreely
```
Create your database, import the schema, and configure your site [as shown above](#quick-start).
Now generate the CSS:
```bash
make install # Generates encryption keys; installs LESS compiler
make ui # Generates CSS (run this whenever you update your styles)
make run # Runs the application
```
Before contributing anything, please read our [Contributing Guide](https://github.com/writefreely/writefreely/blob/master/CONTRIBUTING.md#contributing-to-writefreely). It describes the correct channels for submitting contributions and any potential requirements.
## License
Licensed under the AGPL.
Copyright © 2018-2022 [Musing Studio LLC](https://musing.studio) and contributing authors. Licensed under the [AGPL](https://github.com/writefreely/writefreely/blob/develop/LICENSE).

5
SECURITY.md Normal file
View File

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
To report a vulnerability, send an email to security@writefreely.org.

File diff suppressed because it is too large Load Diff

194
account_import.go Normal file
View File

@ -0,0 +1,194 @@
package writefreely
import (
"encoding/json"
"fmt"
"html/template"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/hashicorp/go-multierror"
"github.com/writeas/impart"
wfimport "github.com/writeas/import"
"github.com/writeas/web-core/log"
)
func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
// Fetch extra user data
p := NewUserPage(app, r, u, "Import Posts", nil)
c, err := app.db.GetCollections(u, app.Config().App.Host)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
}
d := struct {
*UserPage
Collections *[]Collection
Flashes []template.HTML
Message string
InfoMsg bool
}{
UserPage: p,
Collections: c,
Flashes: []template.HTML{},
}
flashes, _ := getSessionFlashes(app, w, r, nil)
for _, flash := range flashes {
if strings.HasPrefix(flash, "SUCCESS: ") {
d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
} else if strings.HasPrefix(flash, "INFO: ") {
d.Message = strings.TrimPrefix(flash, "INFO: ")
d.InfoMsg = true
} else {
d.Flashes = append(d.Flashes, template.HTML(flash))
}
}
showUserPage(w, "import", d)
return nil
}
func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
// limit 10MB per submission
r.ParseMultipartForm(10 << 20)
collAlias := r.PostFormValue("collection")
coll := &Collection{
ID: 0,
}
var err error
if collAlias != "" {
coll, err = app.db.GetCollection(collAlias)
if err != nil {
log.Error("Unable to get collection for import: %s", err)
return err
}
// Only allow uploading to collection if current user is owner
if coll.OwnerID != u.ID {
err := ErrUnauthorizedGeneral
_ = addSessionFlash(app, w, r, err.Message, nil)
return err
}
coll.hostName = app.cfg.App.Host
}
fileDates := make(map[string]int64)
err = json.Unmarshal([]byte(r.FormValue("fileDates")), &fileDates)
if err != nil {
log.Error("invalid form data for file dates: %v", err)
return impart.HTTPError{http.StatusBadRequest, "form data for file dates was invalid"}
}
files := r.MultipartForm.File["files"]
var fileErrs []error
filesSubmitted := len(files)
var filesImported int
for _, formFile := range files {
fname := ""
ok := func() bool {
file, err := formFile.Open()
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Unable to read file %s", formFile.Filename))
log.Error("import file: open from form: %v", err)
return false
}
defer file.Close()
tempFile, err := os.CreateTemp("", "post-upload-*.txt")
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
log.Error("import file: create temp file %s: %v", formFile.Filename, err)
return false
}
defer tempFile.Close()
_, err = io.Copy(tempFile, file)
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
log.Error("import file: copy to temp location %s: %v", formFile.Filename, err)
return false
}
info, err := tempFile.Stat()
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
log.Error("import file: stat temp file %s: %v", formFile.Filename, err)
return false
}
fname = info.Name()
return true
}()
if !ok {
continue
}
post, err := wfimport.FromFile(filepath.Join(os.TempDir(), fname))
if err == wfimport.ErrEmptyFile {
// not a real error so don't log
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
continue
} else if err == wfimport.ErrInvalidContentType {
// same as above
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
continue
} else if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
log.Error("import textfile: file to post: %v", err)
continue
}
if collAlias != "" {
post.Collection = collAlias
}
dateTime := time.Unix(fileDates[formFile.Filename], 0)
post.Created = &dateTime
created := post.Created.Format("2006-01-02T15:04:05Z")
submittedPost := SubmittedPost{
Title: &post.Title,
Content: &post.Content,
Font: "norm",
Created: &created,
}
rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
if err != nil {
fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
log.Error("import textfile: create db post: %v", err)
continue
}
// Federate post, if necessary
if app.cfg.App.Federation && coll.ID > 0 {
go federatePost(
app,
&PublicPost{
Post: rp,
Collection: &CollectionObj{
Collection: *coll,
},
},
coll.ID,
false,
)
}
filesImported++
}
if len(fileErrs) != 0 {
_ = addSessionFlash(app, w, r, multierror.ListFormatFunc(fileErrs), nil)
}
if filesImported == filesSubmitted {
verb := "posts"
if filesSubmitted == 1 {
verb = "post"
}
_ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, verb), nil)
} else if filesImported > 0 {
_ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
}
return impart.HTTPError{http.StatusFound, "/me/import"}
}

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018-2021 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 (
@ -7,32 +17,68 @@ import (
"encoding/base64"
"encoding/json"
"fmt"
"github.com/go-sql-driver/mysql"
"io"
"net/http"
"net/http/httputil"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/writeas/activity/streams"
"github.com/writeas/activityserve"
"github.com/writeas/httpsig"
"github.com/writeas/impart"
"github.com/writeas/web-core/activitypub"
"github.com/writeas/web-core/activitystreams"
"github.com/writeas/web-core/id"
"github.com/writeas/web-core/log"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"time"
"github.com/writeas/web-core/silobridge"
)
const (
// TODO: delete. don't use this!
apCustomHandleDefault = "blog"
apCacheTime = time.Minute
)
var instanceColl *Collection
func initActivityPub(app *App) {
ur, _ := url.Parse(app.cfg.App.Host)
instanceColl = &Collection{
ID: 0,
Alias: ur.Host,
Title: ur.Host,
db: app.db,
hostName: app.cfg.App.Host,
}
}
type RemoteUser struct {
ID int64
ActorID string
Inbox string
SharedInbox string
URL string
Handle string
Created time.Time
}
func (ru *RemoteUser) CreatedFriendly() string {
return ru.Created.Format("January 2, 2006")
}
func (ru *RemoteUser) EstimatedHandle() string {
if ru.Handle != "" {
return ru.Handle
}
username := filepath.Base(ru.ActorID)
host, _ := url.Parse(ru.ActorID)
return username + "@" + host.Host
}
func (ru *RemoteUser) AsPerson() *activitystreams.Person {
@ -51,17 +97,28 @@ func (ru *RemoteUser) AsPerson() *activitystreams.Person {
}
}
func handleFetchCollectionActivities(app *app, w http.ResponseWriter, r *http.Request) error {
func activityPubClient() *http.Client {
return &http.Client{
Timeout: 15 * time.Second,
}
}
func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Server", serverSoftware)
vars := mux.Vars(r)
alias := vars["alias"]
if alias == "" {
alias = filepath.Base(r.RequestURI)
}
// TODO: enforce visibility
// Get base Collection data
var c *Collection
var err error
if app.cfg.App.SingleUser {
if alias == r.Host {
c = instanceColl
} else if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
@ -69,13 +126,26 @@ func handleFetchCollectionActivities(app *app, w http.ResponseWriter, r *http.Re
if err != nil {
return err
}
c.hostName = app.cfg.App.Host
if !c.IsInstanceColl() {
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("fetch collection activities: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
}
p := c.PersonObject()
setCacheControl(w, apCacheTime)
return impart.RenderActivityJSON(w, p, http.StatusOK)
}
func handleFetchCollectionOutbox(app *app, w http.ResponseWriter, r *http.Request) error {
func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Server", serverSoftware)
vars := mux.Vars(r)
@ -93,6 +163,15 @@ func handleFetchCollectionOutbox(app *app, w http.ResponseWriter, r *http.Reques
if err != nil {
return err
}
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("fetch collection outbox: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
c.hostName = app.cfg.App.Host
if app.cfg.App.SingleUser {
if alias != c.Alias {
@ -116,18 +195,20 @@ 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(c, p, false)
posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false)
for _, pp := range *posts {
pp.Collection = res
o := pp.ActivityObject()
o := pp.ActivityObject(app)
a := activitystreams.NewCreateActivity(o)
a.Context = nil
ocp.OrderedItems = append(ocp.OrderedItems, *a)
}
setCacheControl(w, apCacheTime)
return impart.RenderActivityJSON(w, ocp, http.StatusOK)
}
func handleFetchCollectionFollowers(app *app, w http.ResponseWriter, r *http.Request) error {
func handleFetchCollectionFollowers(app *App, w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Server", serverSoftware)
vars := mux.Vars(r)
@ -145,6 +226,15 @@ func handleFetchCollectionFollowers(app *app, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("fetch collection followers: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
c.hostName = app.cfg.App.Host
accountRoot := c.FederatedAccount()
@ -169,10 +259,11 @@ func handleFetchCollectionFollowers(app *app, w http.ResponseWriter, r *http.Req
ocp.OrderedItems = append(ocp.OrderedItems, f.ActorID)
}
*/
setCacheControl(w, apCacheTime)
return impart.RenderActivityJSON(w, ocp, http.StatusOK)
}
func handleFetchCollectionFollowing(app *app, w http.ResponseWriter, r *http.Request) error {
func handleFetchCollectionFollowing(app *App, w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Server", serverSoftware)
vars := mux.Vars(r)
@ -190,6 +281,15 @@ func handleFetchCollectionFollowing(app *app, w http.ResponseWriter, r *http.Req
if err != nil {
return err
}
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("fetch collection following: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
c.hostName = app.cfg.App.Host
accountRoot := c.FederatedAccount()
@ -204,10 +304,11 @@ func handleFetchCollectionFollowing(app *app, w http.ResponseWriter, r *http.Req
// Return outbox page
ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "following", 0, p)
ocp.OrderedItems = []interface{}{}
setCacheControl(w, apCacheTime)
return impart.RenderActivityJSON(w, ocp, http.StatusOK)
}
func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request) error {
func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Server", serverSoftware)
vars := mux.Vars(r)
@ -223,6 +324,15 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
// TODO: return Reject?
return err
}
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("fetch collection inbox: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
c.hostName = app.cfg.App.Host
if debugging {
dump, err := httputil.DumpRequest(r, true)
@ -253,8 +363,21 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
// 2) Errors are propagated to res.Deserialize call below
m["@context"] = []string{activitystreams.Namespace}
b, _ := json.Marshal(m)
log.Info("Follow: %s", b)
if debugging {
log.Info("Follow: %s", b)
}
_, followID := f.GetId()
if followID == nil {
log.Error("Didn't resolve follow ID")
} else {
aID := c.FederatedAccount() + "#accept-" + id.GenerateFriendlyRandomString(20)
acceptID, err := url.Parse(aID)
if err != nil {
log.Error("Couldn't parse generated Accept URL '%s': %v", aID, err)
}
a.SetId(acceptID)
}
a.AppendObject(f.Raw())
_, to = f.GetActor(0)
obj := f.Raw().GetObjectIRI(0)
@ -275,7 +398,9 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
m["@context"] = []string{activitystreams.Namespace}
b, _ := json.Marshal(m)
log.Info("Undo: %s", b)
if debugging {
log.Info("Undo: %s", b)
}
a.AppendObject(u.Raw())
_, to = u.GetActor(0)
@ -311,6 +436,13 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
}
go func() {
if to == nil {
if debugging {
log.Error("No `to` value!")
}
return
}
time.Sleep(2 * time.Second)
am, err := a.Serialize()
if err != nil {
@ -319,11 +451,7 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
}
am["@context"] = []string{activitystreams.Namespace}
if to == nil {
log.Error("No to! %v", err)
return
}
err = makeActivityPost(p, fullActor.Inbox, am)
err = makeActivityPost(app.cfg.App.Host, p, fullActor.Inbox, am)
if err != nil {
log.Error("Unable to make activity POST: %v", err)
return
@ -342,19 +470,13 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
followerID = remoteUser.ID
} else {
// Add follower locally, since it wasn't found before
res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox) VALUES (?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox)
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 {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number != mySQLErrDuplicateKey {
t.Rollback()
log.Error("Couldn't add new remoteuser in DB: %v\n", err)
return
}
} else {
t.Rollback()
log.Error("Couldn't add new remoteuser in DB: %v\n", err)
return
}
// if duplicate key, res will be nil and panic on
// res.LastInsertId below
t.Rollback()
log.Error("Couldn't add new remoteuser in DB: %v\n", err)
return
}
followerID, err = res.LastInsertId()
@ -367,13 +489,7 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
// Add in key
_, err = t.Exec("INSERT INTO remoteuserkeys (id, remote_user_id, public_key) VALUES (?, ?, ?)", fullActor.PublicKey.ID, followerID, fullActor.PublicKey.PublicKeyPEM)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number != mySQLErrDuplicateKey {
t.Rollback()
log.Error("Couldn't add follower keys in DB: %v\n", err)
return
}
} else {
if !app.db.isDuplicateKeyErr(err) {
t.Rollback()
log.Error("Couldn't add follower keys in DB: %v\n", err)
return
@ -382,15 +498,9 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
}
// Add follow
_, err = t.Exec("INSERT INTO remotefollows (collection_id, remote_user_id, created) VALUES (?, ?, NOW())", c.ID, followerID)
_, err = t.Exec("INSERT INTO remotefollows (collection_id, remote_user_id, created) VALUES (?, ?, "+app.db.now()+")", c.ID, followerID)
if err != nil {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
if mysqlErr.Number != mySQLErrDuplicateKey {
t.Rollback()
log.Error("Couldn't add follower in DB: %v\n", err)
return
}
} else {
if !app.db.isDuplicateKeyErr(err) {
t.Rollback()
log.Error("Couldn't add follower in DB: %v\n", err)
return
@ -415,7 +525,7 @@ func handleFetchCollectionInbox(app *app, w http.ResponseWriter, r *http.Request
return nil
}
func makeActivityPost(p *activitystreams.Person, url string, m interface{}) error {
func makeActivityPost(hostName string, p *activitystreams.Person, url string, m interface{}) error {
log.Info("POST %s", url)
b, err := json.Marshal(m)
if err != nil {
@ -424,7 +534,7 @@ func makeActivityPost(p *activitystreams.Person, url string, m interface{}) erro
r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b))
r.Header.Add("Content-Type", "application/activity+json")
r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
r.Header.Set("User-Agent", ServerUserAgent(hostName))
h := sha256.New()
h.Write(b)
r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
@ -449,16 +559,15 @@ func makeActivityPost(p *activitystreams.Person, url string, m interface{}) erro
}
}
resp, err := http.DefaultClient.Do(r)
resp, err := activityPubClient().Do(r)
if err != nil {
return err
}
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
if resp == nil {
log.Error("No response.")
return fmt.Errorf("No resonse.")
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
@ -470,12 +579,28 @@ func makeActivityPost(p *activitystreams.Person, url string, m interface{}) erro
return nil
}
func resolveIRI(url string) ([]byte, error) {
func resolveIRI(hostName, url string) ([]byte, error) {
log.Info("GET %s", url)
r, _ := http.NewRequest("GET", url, nil)
r.Header.Add("Accept", "application/activity+json")
r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
r.Header.Set("User-Agent", ServerUserAgent(hostName))
p := instanceColl.PersonObject()
h := sha256.New()
h.Write([]byte{})
r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
// Sign using the 'Signature' header
privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey())
if err != nil {
return nil, err
}
signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"})
err = signer.SignSigHeader(r)
if err != nil {
log.Error("Can't sign: %v", err)
}
if debugging {
dump, err := httputil.DumpRequestOut(r, true)
@ -486,12 +611,15 @@ func resolveIRI(url string) ([]byte, error) {
}
}
resp, err := http.DefaultClient.Do(r)
resp, err := activityPubClient().Do(r)
if err != nil {
return nil, err
}
if resp != nil && resp.Body != nil {
defer resp.Body.Close()
}
body, err := ioutil.ReadAll(resp.Body)
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@ -503,12 +631,13 @@ func resolveIRI(url string) ([]byte, error) {
return body, nil
}
func deleteFederatedPost(app *app, p *PublicPost, collID int64) error {
func deleteFederatedPost(app *App, p *PublicPost, collID int64) error {
if debugging {
log.Info("Deleting federated post!")
}
p.Collection.hostName = app.cfg.App.Host
actor := p.Collection.PersonObject(collID)
na := p.ActivityObject()
na := p.ActivityObject(app)
// Add followers
p.Collection.ID = collID
@ -520,20 +649,26 @@ func deleteFederatedPost(app *app, p *PublicPost, collID int64) error {
inboxes := map[string][]string{}
for _, f := range *followers {
if _, ok := inboxes[f.SharedInbox]; ok {
inboxes[f.SharedInbox] = append(inboxes[f.SharedInbox], f.ActorID)
inbox := f.SharedInbox
if inbox == "" {
inbox = f.Inbox
}
if _, ok := inboxes[inbox]; ok {
inboxes[inbox] = append(inboxes[inbox], f.ActorID)
} else {
inboxes[f.SharedInbox] = []string{f.ActorID}
inboxes[inbox] = []string{f.ActorID}
}
}
for si, instFolls := range inboxes {
na.CC = []string{}
for _, f := range instFolls {
na.CC = append(na.CC, f)
}
na.CC = append(na.CC, instFolls...)
da := activitystreams.NewDeleteActivity(na)
// Make the ID unique to ensure it works in Pleroma
// See: https://git.pleroma.social/pleroma/pleroma/issues/1481
da.ID += "#Delete"
err = makeActivityPost(actor, si, activitystreams.NewDeleteActivity(na))
err = makeActivityPost(app.cfg.App.Host, actor, si, da)
if err != nil {
log.Error("Couldn't delete post! %v", err)
}
@ -541,7 +676,17 @@ func deleteFederatedPost(app *app, p *PublicPost, collID int64) error {
return nil
}
func federatePost(app *app, p *PublicPost, collID int64, isUpdate bool) error {
func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
// If app is private, do not federate
if app.cfg.App.Private {
return nil
}
// Do not federate posts from private or protected blogs
if p.Collection.Visibility == CollPrivate || p.Collection.Visibility == CollProtected {
return nil
}
if debugging {
if isUpdate {
log.Info("Federating updated post!")
@ -549,8 +694,9 @@ func federatePost(app *app, p *PublicPost, collID int64, isUpdate bool) error {
log.Info("Federating new post!")
}
}
actor := p.Collection.PersonObject(collID)
na := p.ActivityObject()
na := p.ActivityObject(app)
// Add followers
p.Collection.ID = collID
@ -563,35 +709,77 @@ func federatePost(app *app, p *PublicPost, collID int64, isUpdate bool) error {
inboxes := map[string][]string{}
for _, f := range *followers {
if _, ok := inboxes[f.SharedInbox]; ok {
inboxes[f.SharedInbox] = append(inboxes[f.SharedInbox], f.ActorID)
inbox := f.SharedInbox
if inbox == "" {
inbox = f.Inbox
}
if _, ok := inboxes[inbox]; ok {
// check if we're already sending to this shared inbox
inboxes[inbox] = append(inboxes[inbox], f.ActorID)
} else {
inboxes[f.SharedInbox] = []string{f.ActorID}
// add the new shared inbox to the list
inboxes[inbox] = []string{f.ActorID}
}
}
var activity *activitystreams.Activity
// for each one of the shared inboxes
for si, instFolls := range inboxes {
// add all followers from that instance
// to the CC field
na.CC = []string{}
for _, f := range instFolls {
na.CC = append(na.CC, f)
}
var activity *activitystreams.Activity
na.CC = append(na.CC, instFolls...)
// create a new "Create" activity
// with our article as object
if isUpdate {
na.Updated = &p.Updated
activity = activitystreams.NewUpdateActivity(na)
} else {
activity = activitystreams.NewCreateActivity(na)
activity.To = na.To
activity.CC = na.CC
}
err = makeActivityPost(actor, si, activity)
// and post it to that sharedInbox
err = makeActivityPost(app.cfg.App.Host, actor, si, activity)
if err != nil {
log.Error("Couldn't post! %v", err)
}
}
// re-create the object so that the CC list gets reset and has
// the mentioned users. This might seem wasteful but the code is
// cleaner than adding the mentioned users to CC here instead of
// in p.ActivityObject()
na = p.ActivityObject(app)
for _, tag := range na.Tag {
if tag.Type == "Mention" {
activity = activitystreams.NewCreateActivity(na)
activity.To = na.To
activity.CC = na.CC
// This here might be redundant in some cases as we might have already
// sent this to the sharedInbox of this instance above, but we need too
// much logic to catch this at the expense of the odd extra request.
// I don't believe we'd ever have too many mentions in a single post that this
// could become a burden.
remoteUser, err := getRemoteUser(app, tag.HRef)
if err != nil {
log.Error("Unable to find remote user %s. Skipping: %v", tag.HRef, err)
continue
}
err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity)
if err != nil {
log.Error("Couldn't post! %v", err)
}
}
}
return nil
}
func getRemoteUser(app *app, actorID string) (*RemoteUser, error) {
func getRemoteUser(app *App, actorID string) (*RemoteUser, error) {
u := RemoteUser{ActorID: actorID}
err := app.db.QueryRow("SELECT id, inbox, shared_inbox FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox)
var urlVal, handle sql.NullString
err := app.db.QueryRow("SELECT id, inbox, shared_inbox, url, handle FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox, &urlVal, &handle)
switch {
case err == sql.ErrNoRows:
return nil, impart.HTTPError{http.StatusNotFound, "No remote user with that ID."}
@ -600,10 +788,30 @@ func getRemoteUser(app *app, actorID string) (*RemoteUser, error) {
return nil, err
}
u.URL = urlVal.String
u.Handle = handle.String
return &u, nil
}
func getActor(app *app, actorIRI string) (*activitystreams.Person, *RemoteUser, error) {
// getRemoteUserFromHandle retrieves the profile page of a remote user
// from the @user@server.tld handle
func getRemoteUserFromHandle(app *App, handle string) (*RemoteUser, error) {
u := RemoteUser{Handle: handle}
var urlVal sql.NullString
err := app.db.QueryRow("SELECT id, actor_id, inbox, shared_inbox, url FROM remoteusers WHERE handle = ?", handle).Scan(&u.ID, &u.ActorID, &u.Inbox, &u.SharedInbox, &urlVal)
switch {
case err == sql.ErrNoRows:
return nil, ErrRemoteUserNotFound
case err != nil:
log.Error("Couldn't get remote user %s: %v", handle, err)
return nil, err
}
u.URL = urlVal.String
return &u, nil
}
func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser, error) {
log.Info("Fetching actor %s locally", actorIRI)
actor := &activitystreams.Person{}
remoteUser, err := getRemoteUser(app, actorIRI)
@ -612,13 +820,12 @@ func getActor(app *app, actorIRI string) (*activitystreams.Person, *RemoteUser,
if iErr.Status == http.StatusNotFound {
// Fetch remote actor
log.Info("Not found; fetching actor %s remotely", actorIRI)
actorResp, err := resolveIRI(actorIRI)
actorResp, err := resolveIRI(app.cfg.App.Host, actorIRI)
if err != nil {
log.Error("Unable to get actor! %v", err)
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."}
}
if err := json.Unmarshal(actorResp, &actor); err != nil {
// FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string
if err := unmarshalActor(actorResp, actor); err != nil {
log.Error("Unable to unmarshal actor! %v", err)
return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."}
}
@ -633,3 +840,115 @@ func getActor(app *app, actorIRI string) (*activitystreams.Person, *RemoteUser,
}
return actor, remoteUser, nil
}
func GetProfileURLFromHandle(app *App, handle string) (string, error) {
handle = strings.TrimLeft(handle, "@")
actorIRI := ""
parts := strings.Split(handle, "@")
if len(parts) != 2 {
return "", fmt.Errorf("invalid handle format")
}
domain := parts[1]
// Check non-AP instances
if siloProfileURL := silobridge.Profile(parts[0], domain); siloProfileURL != "" {
return siloProfileURL, nil
}
remoteUser, err := getRemoteUserFromHandle(app, handle)
if err != nil {
// can't find using handle in the table but the table may already have this user without
// handle from a previous version
// TODO: Make this determination. We should know whether a user exists without a handle, or doesn't exist at all
actorIRI = RemoteLookup(handle)
_, errRemoteUser := getRemoteUser(app, actorIRI)
// if it exists then we need to update the handle
if errRemoteUser == nil {
_, err := app.db.Exec("UPDATE remoteusers SET handle = ? WHERE actor_id = ?", handle, actorIRI)
if err != nil {
log.Error("Couldn't update handle '%s' for user %s", handle, actorIRI)
}
} else {
// this probably means we don't have the user in the table so let's try to insert it
// here we need to ask the server for the inboxes
remoteActor, err := activityserve.NewRemoteActor(actorIRI)
if err != nil {
log.Error("Couldn't fetch remote actor: %v", err)
}
if debugging {
log.Info("Got remote actor: %s %s %s %s %s", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), remoteActor.URL(), handle)
}
_, err = app.db.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url, handle) VALUES(?, ?, ?, ?, ?)", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), remoteActor.URL(), handle)
if err != nil {
log.Error("Couldn't insert remote user: %v", err)
return "", err
}
actorIRI = remoteActor.URL()
}
} else if remoteUser.URL == "" {
log.Info("Remote user %s URL empty, fetching", remoteUser.ActorID)
newRemoteActor, err := activityserve.NewRemoteActor(remoteUser.ActorID)
if err != nil {
log.Error("Couldn't fetch remote actor: %v", err)
} else {
_, err := app.db.Exec("UPDATE remoteusers SET url = ? WHERE actor_id = ?", newRemoteActor.URL(), remoteUser.ActorID)
if err != nil {
log.Error("Couldn't update handle '%s' for user %s", handle, actorIRI)
} else {
actorIRI = newRemoteActor.URL()
}
}
} else {
actorIRI = remoteUser.URL
}
return actorIRI, nil
}
// unmarshal actor normalizes the actor response to conform to
// the type Person from github.com/writeas/web-core/activitysteams
//
// some implementations return different context field types
// this converts any non-slice contexts into a slice
func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error {
// FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string
// flexActor overrides the Context field to allow
// all valid representations during unmarshal
flexActor := struct {
activitystreams.Person
Context json.RawMessage `json:"@context,omitempty"`
}{}
if err := json.Unmarshal(actorResp, &flexActor); err != nil {
return err
}
actor.Endpoints = flexActor.Endpoints
actor.Followers = flexActor.Followers
actor.Following = flexActor.Following
actor.ID = flexActor.ID
actor.Icon = flexActor.Icon
actor.Inbox = flexActor.Inbox
actor.Name = flexActor.Name
actor.Outbox = flexActor.Outbox
actor.PreferredUsername = flexActor.PreferredUsername
actor.PublicKey = flexActor.PublicKey
actor.Summary = flexActor.Summary
actor.Type = flexActor.Type
actor.URL = flexActor.URL
func(val interface{}) {
switch val.(type) {
case []interface{}:
// already a slice, do nothing
actor.Context = val.([]interface{})
default:
actor.Context = []interface{}{val}
}
}(flexActor.Context)
return nil
}
func setCacheControl(w http.ResponseWriter, ttl time.Duration) {
w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", ttl.Seconds()))
}

31
activitypub_test.go Normal file
View File

@ -0,0 +1,31 @@
package writefreely
import (
"testing"
"github.com/writeas/web-core/activitystreams"
)
var actorTestTable = []struct {
Name string
Resp []byte
}{
{
"Context as a string",
[]byte(`{"@context":"https://www.w3.org/ns/activitystreams"}`),
},
{
"Context as a list",
[]byte(`{"@context":["one string", "two strings"]}`),
},
}
func TestUnmarshalActor(t *testing.T) {
for _, tc := range actorTestTable {
actor := activitystreams.Person{}
err := unmarshalActor(tc.Resp, &actor)
if err != nil {
t.Errorf("%s failed with error %s", tc.Name, err)
}
}
}

693
admin.go Normal file
View File

@ -0,0 +1,693 @@
/*
* Copyright © 2018-2021 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"
"html/template"
"net/http"
"runtime"
"strconv"
"strings"
"time"
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/log"
"github.com/writeas/web-core/passgen"
"github.com/writefreely/writefreely/appstats"
"github.com/writefreely/writefreely/config"
)
var (
appStartTime = time.Now()
sysStatus systemStatus
)
const adminUsersPerPage = 30
type systemStatus struct {
Uptime string
NumGoroutine int
// General statistics.
MemAllocated string // bytes allocated and still in use
MemTotal string // bytes allocated (even if freed)
MemSys string // bytes obtained from system (sum of XxxSys below)
Lookups uint64 // number of pointer lookups
MemMallocs uint64 // number of mallocs
MemFrees uint64 // number of frees
// Main allocation heap statistics.
HeapAlloc string // bytes allocated and still in use
HeapSys string // bytes obtained from system
HeapIdle string // bytes in idle spans
HeapInuse string // bytes in non-idle span
HeapReleased string // bytes released to the OS
HeapObjects uint64 // total number of allocated objects
// Low-level fixed-size structure allocator statistics.
// Inuse is bytes used now.
// Sys is bytes obtained from system.
StackInuse string // bootstrap stacks
StackSys string
MSpanInuse string // mspan structures
MSpanSys string
MCacheInuse string // mcache structures
MCacheSys string
BuckHashSys string // profiling bucket hash table
GCSys string // GC metadata
OtherSys string // other system allocations
// Garbage collector statistics.
NextGC string // next run in HeapAlloc time (bytes)
LastGC string // last run in absolute time (ns)
PauseTotalNs string
PauseNs string // circular buffer of recent GC pause times, most recent at [(NumGC+255)%256]
NumGC uint32
}
type inspectedCollection struct {
CollectionObj
Followers int
LastPost string
}
type instanceContent struct {
ID string
Type string
Title sql.NullString
Content string
Updated time.Time
}
type AdminPage struct {
UpdateAvailable bool
}
func NewAdminPage(app *App) *AdminPage {
ap := &AdminPage{}
if app.updates != nil {
ap.UpdateAvailable = app.updates.AreAvailableNoCheck()
}
return ap
}
func (c instanceContent) UpdatedFriendly() template.HTML {
/*
// TODO: accept a locale in this method and use that for the format
var loc monday.Locale = monday.LocaleEnUS
return monday.Format(u.Created, monday.DateTimeFormatsByLocale[loc], loc)
*/
if c.Updated.IsZero() {
return "<em>Never</em>"
}
return template.HTML(c.Updated.Format("January 2, 2006, 3:04 PM"))
}
func handleViewAdminDash(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
p := struct {
*UserPage
*AdminPage
Message string
UsersCount, CollectionsCount, PostsCount int64
}{
UserPage: NewUserPage(app, r, u, "Admin", nil),
AdminPage: NewAdminPage(app),
Message: r.FormValue("m"),
}
// Get user stats
p.UsersCount = app.db.GetAllUsersCount()
var err error
p.CollectionsCount, err = app.db.GetTotalCollections()
if err != nil {
return err
}
p.PostsCount, err = app.db.GetTotalPosts()
if err != nil {
return err
}
showUserPage(w, "admin", p)
return nil
}
func handleViewAdminMonitor(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
updateAppStats()
p := struct {
*UserPage
*AdminPage
SysStatus systemStatus
Config config.AppCfg
Message, ConfigMessage string
}{
UserPage: NewUserPage(app, r, u, "Admin", nil),
AdminPage: NewAdminPage(app),
SysStatus: sysStatus,
Config: app.cfg.App,
Message: r.FormValue("m"),
ConfigMessage: r.FormValue("cm"),
}
showUserPage(w, "monitor", p)
return nil
}
func handleViewAdminSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
p := struct {
*UserPage
*AdminPage
Config config.AppCfg
Message, ConfigMessage string
}{
UserPage: NewUserPage(app, r, u, "Admin", nil),
AdminPage: NewAdminPage(app),
Config: app.cfg.App,
Message: r.FormValue("m"),
ConfigMessage: r.FormValue("cm"),
}
showUserPage(w, "app-settings", p)
return nil
}
func handleViewAdminUsers(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
p := struct {
*UserPage
*AdminPage
Config config.AppCfg
Message string
Flashes []string
Users *[]User
CurPage int
TotalUsers int64
TotalPages []int
}{
UserPage: NewUserPage(app, r, u, "Users", nil),
AdminPage: NewAdminPage(app),
Config: app.cfg.App,
Message: r.FormValue("m"),
}
p.Flashes, _ = getSessionFlashes(app, w, r, nil)
p.TotalUsers = app.db.GetAllUsersCount()
ttlPages := p.TotalUsers / adminUsersPerPage
p.TotalPages = []int{}
for i := 1; i <= int(ttlPages); i++ {
p.TotalPages = append(p.TotalPages, i)
}
var err error
p.CurPage, err = strconv.Atoi(r.FormValue("p"))
if err != nil || p.CurPage < 1 {
p.CurPage = 1
} else if p.CurPage > int(ttlPages) {
p.CurPage = int(ttlPages)
}
p.Users, err = app.db.GetAllUsers(uint(p.CurPage))
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get users: %v", err)}
}
showUserPage(w, "users", p)
return nil
}
func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["username"]
if username == "" {
return impart.HTTPError{http.StatusFound, "/admin/users"}
}
p := struct {
*UserPage
*AdminPage
Config config.AppCfg
Message string
User *User
Colls []inspectedCollection
LastPost string
NewPassword string
TotalPosts int64
ClearEmail string
}{
AdminPage: NewAdminPage(app),
Config: app.cfg.App,
Message: r.FormValue("m"),
Colls: []inspectedCollection{},
}
var err error
p.User, err = app.db.GetUserForAuth(username)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("Could not get user: %v", err)
return impart.HTTPError{http.StatusInternalServerError, err.Error()}
}
flashes, _ := getSessionFlashes(app, w, r, nil)
for _, flash := range flashes {
if strings.HasPrefix(flash, "SUCCESS: ") {
p.NewPassword = strings.TrimPrefix(flash, "SUCCESS: ")
p.ClearEmail = p.User.EmailClear(app.keys)
}
}
p.UserPage = NewUserPage(app, r, u, p.User.Username, nil)
p.TotalPosts = app.db.GetUserPostsCount(p.User.ID)
lp, err := app.db.GetUserLastPostTime(p.User.ID)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user's last post time: %v", err)}
}
if lp != nil {
p.LastPost = lp.Format("January 2, 2006, 3:04 PM")
}
colls, err := app.db.GetCollections(p.User, app.cfg.App.Host)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user's collections: %v", err)}
}
for _, c := range *colls {
ic := inspectedCollection{
CollectionObj: CollectionObj{Collection: c},
}
if app.cfg.App.Federation {
folls, err := app.db.GetAPFollowers(&c)
if err == nil {
// TODO: handle error here (at least log it)
ic.Followers = len(*folls)
}
}
app.db.GetPostsCount(&ic.CollectionObj, true)
lp, err := app.db.GetCollectionLastPostTime(c.ID)
if err != nil {
log.Error("Didn't get last post time for collection %d: %v", c.ID, err)
}
if lp != nil {
ic.LastPost = lp.Format("January 2, 2006, 3:04 PM")
}
p.Colls = append(p.Colls, ic)
}
showUserPage(w, "view-user", p)
return nil
}
func handleAdminDeleteUser(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
if !u.IsAdmin() {
return impart.HTTPError{http.StatusForbidden, "Administrator privileges required for this action"}
}
vars := mux.Vars(r)
username := vars["username"]
confirmUsername := r.PostFormValue("confirm-username")
if confirmUsername != username {
return impart.HTTPError{http.StatusBadRequest, "Username was not confirmed"}
}
user, err := app.db.GetUserForAuth(username)
if err == ErrUserNotFound {
return impart.HTTPError{http.StatusNotFound, fmt.Sprintf("User '%s' was not found", username)}
} else if err != nil {
log.Error("get user for deletion: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user with username '%s': %v", username, err)}
}
err = app.db.DeleteAccount(user.ID)
if err != nil {
log.Error("delete user %s: %v", user.Username, err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not delete user account for '%s': %v", username, err)}
}
_ = addSessionFlash(app, w, r, fmt.Sprintf("User \"%s\" was deleted successfully.", username), nil)
return impart.HTTPError{http.StatusFound, "/admin/users"}
}
func handleAdminToggleUserStatus(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["username"]
if username == "" {
return impart.HTTPError{http.StatusFound, "/admin/users"}
}
user, err := app.db.GetUserForAuth(username)
if err != nil {
log.Error("failed to get user: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user from username: %v", err)}
}
if user.IsSilenced() {
err = app.db.SetUserStatus(user.ID, UserActive)
} else {
err = app.db.SetUserStatus(user.ID, UserSilenced)
// reset the cache to removed silence user posts
updateTimelineCache(app.timeline, true)
}
if err != nil {
log.Error("toggle user silenced: %v", err)
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not toggle user status: %v", err)}
}
return impart.HTTPError{http.StatusFound, fmt.Sprintf("/admin/user/%s#status", username)}
}
func handleAdminResetUserPass(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
username := vars["username"]
if username == "" {
return impart.HTTPError{http.StatusFound, "/admin/users"}
}
// Generate new random password since none supplied
pass := passgen.NewWordish()
hashedPass, err := auth.HashPass([]byte(pass))
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not create password hash: %v", err)}
}
userIDVal := r.FormValue("user")
log.Info("ADMIN: Changing user %s password", userIDVal)
id, err := strconv.Atoi(userIDVal)
if err != nil {
return impart.HTTPError{http.StatusBadRequest, fmt.Sprintf("Invalid user ID: %v", err)}
}
err = app.db.ChangePassphrase(int64(id), true, "", hashedPass)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not update passphrase: %v", err)}
}
log.Info("ADMIN: Successfully changed.")
addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: %s", pass), nil)
return impart.HTTPError{http.StatusFound, fmt.Sprintf("/admin/user/%s", username)}
}
func handleViewAdminPages(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
p := struct {
*UserPage
*AdminPage
Config config.AppCfg
Message string
Pages []*instanceContent
}{
UserPage: NewUserPage(app, r, u, "Pages", nil),
AdminPage: NewAdminPage(app),
Config: app.cfg.App,
Message: r.FormValue("m"),
}
var err error
p.Pages, err = app.db.GetInstancePages()
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get pages: %v", err)}
}
// Add in default pages
var hasAbout, hasContact, hasPrivacy bool
for i, c := range p.Pages {
if hasAbout && hasContact && hasPrivacy {
break
}
if c.ID == "about" {
hasAbout = true
if !c.Title.Valid {
p.Pages[i].Title = defaultAboutTitle(app.cfg)
}
} else if c.ID == "contact" {
hasContact = true
if !c.Title.Valid {
p.Pages[i].Title = defaultContactTitle()
}
} else if c.ID == "privacy" {
hasPrivacy = true
if !c.Title.Valid {
p.Pages[i].Title = defaultPrivacyTitle()
}
}
}
if !hasAbout {
p.Pages = append(p.Pages, &instanceContent{
ID: "about",
Title: defaultAboutTitle(app.cfg),
Content: defaultAboutPage(app.cfg),
Updated: defaultPageUpdatedTime,
})
}
if !hasContact {
p.Pages = append(p.Pages, &instanceContent{
ID: "contact",
Title: defaultContactTitle(),
Content: defaultContactPage(app),
})
}
if !hasPrivacy {
p.Pages = append(p.Pages, &instanceContent{
ID: "privacy",
Title: defaultPrivacyTitle(),
Content: defaultPrivacyPolicy(app.cfg),
Updated: defaultPageUpdatedTime,
})
}
showUserPage(w, "pages", p)
return nil
}
func handleViewAdminPage(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
slug := vars["slug"]
if slug == "" {
return impart.HTTPError{http.StatusFound, "/admin/pages"}
}
p := struct {
*UserPage
*AdminPage
Config config.AppCfg
Message string
Banner *instanceContent
Content *instanceContent
}{
AdminPage: NewAdminPage(app),
Config: app.cfg.App,
Message: r.FormValue("m"),
}
var err error
// Get pre-defined pages, or select slug
if slug == "about" {
p.Content, err = getAboutPage(app)
} else if slug == "contact" {
p.Content, err = getContactPage(app)
} else if slug == "privacy" {
p.Content, err = getPrivacyPage(app)
} else if slug == "landing" {
p.Banner, err = getLandingBanner(app)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get banner: %v", err)}
}
p.Content, err = getLandingBody(app)
p.Content.ID = "landing"
} else if slug == "reader" {
p.Content, err = getReaderSection(app)
} else {
p.Content, err = app.db.GetDynamicContent(slug)
}
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get page: %v", err)}
}
title := "New page"
if p.Content != nil {
title = "Edit " + p.Content.ID
} else {
p.Content = &instanceContent{}
}
p.UserPage = NewUserPage(app, r, u, title, nil)
showUserPage(w, "view-page", p)
return nil
}
func handleAdminUpdateSite(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
id := vars["page"]
// Validate
if id != "about" && id != "contact" && id != "privacy" && id != "landing" && id != "reader" {
return impart.HTTPError{http.StatusNotFound, "No such page."}
}
var err error
m := ""
if id == "landing" {
// Handle special landing page
err = app.db.UpdateDynamicContent("landing-banner", "", r.FormValue("banner"), "section")
if err != nil {
m = "?m=" + err.Error()
return impart.HTTPError{http.StatusFound, "/admin/page/" + id + m}
}
err = app.db.UpdateDynamicContent("landing-body", "", r.FormValue("content"), "section")
} else if id == "reader" {
// Update sections with titles
err = app.db.UpdateDynamicContent(id, r.FormValue("title"), r.FormValue("content"), "section")
} else {
// Update page
err = app.db.UpdateDynamicContent(id, r.FormValue("title"), r.FormValue("content"), "page")
}
if err != nil {
m = "?m=" + err.Error()
}
return impart.HTTPError{http.StatusFound, "/admin/page/" + id + m}
}
func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *http.Request) error {
apper.App().cfg.App.SiteName = r.FormValue("site_name")
apper.App().cfg.App.SiteDesc = r.FormValue("site_desc")
apper.App().cfg.App.Landing = r.FormValue("landing")
apper.App().cfg.App.OpenRegistration = r.FormValue("open_registration") == "on"
apper.App().cfg.App.OpenDeletion = r.FormValue("open_deletion") == "on"
mul, err := strconv.Atoi(r.FormValue("min_username_len"))
if err == nil {
apper.App().cfg.App.MinUsernameLen = mul
}
mb, err := strconv.Atoi(r.FormValue("max_blogs"))
if err == nil {
apper.App().cfg.App.MaxBlogs = mb
}
apper.App().cfg.App.Federation = r.FormValue("federation") == "on"
apper.App().cfg.App.PublicStats = r.FormValue("public_stats") == "on"
apper.App().cfg.App.Monetization = r.FormValue("monetization") == "on"
apper.App().cfg.App.Private = r.FormValue("private") == "on"
apper.App().cfg.App.LocalTimeline = r.FormValue("local_timeline") == "on"
if apper.App().cfg.App.LocalTimeline && apper.App().timeline == nil {
log.Info("Initializing local timeline...")
initLocalTimeline(apper.App())
}
apper.App().cfg.App.UserInvites = r.FormValue("user_invites")
if apper.App().cfg.App.UserInvites == "none" {
apper.App().cfg.App.UserInvites = ""
}
apper.App().cfg.App.DefaultVisibility = r.FormValue("default_visibility")
m := "?cm=Configuration+saved."
err = apper.SaveConfig(apper.App().cfg)
if err != nil {
m = "?cm=" + err.Error()
}
return impart.HTTPError{http.StatusFound, "/admin/settings" + m + "#config"}
}
func updateAppStats() {
sysStatus.Uptime = appstats.TimeSincePro(appStartTime)
m := new(runtime.MemStats)
runtime.ReadMemStats(m)
sysStatus.NumGoroutine = runtime.NumGoroutine()
sysStatus.MemAllocated = appstats.FileSize(int64(m.Alloc))
sysStatus.MemTotal = appstats.FileSize(int64(m.TotalAlloc))
sysStatus.MemSys = appstats.FileSize(int64(m.Sys))
sysStatus.Lookups = m.Lookups
sysStatus.MemMallocs = m.Mallocs
sysStatus.MemFrees = m.Frees
sysStatus.HeapAlloc = appstats.FileSize(int64(m.HeapAlloc))
sysStatus.HeapSys = appstats.FileSize(int64(m.HeapSys))
sysStatus.HeapIdle = appstats.FileSize(int64(m.HeapIdle))
sysStatus.HeapInuse = appstats.FileSize(int64(m.HeapInuse))
sysStatus.HeapReleased = appstats.FileSize(int64(m.HeapReleased))
sysStatus.HeapObjects = m.HeapObjects
sysStatus.StackInuse = appstats.FileSize(int64(m.StackInuse))
sysStatus.StackSys = appstats.FileSize(int64(m.StackSys))
sysStatus.MSpanInuse = appstats.FileSize(int64(m.MSpanInuse))
sysStatus.MSpanSys = appstats.FileSize(int64(m.MSpanSys))
sysStatus.MCacheInuse = appstats.FileSize(int64(m.MCacheInuse))
sysStatus.MCacheSys = appstats.FileSize(int64(m.MCacheSys))
sysStatus.BuckHashSys = appstats.FileSize(int64(m.BuckHashSys))
sysStatus.GCSys = appstats.FileSize(int64(m.GCSys))
sysStatus.OtherSys = appstats.FileSize(int64(m.OtherSys))
sysStatus.NextGC = appstats.FileSize(int64(m.NextGC))
sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000)
sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000)
sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000)
sysStatus.NumGC = m.NumGC
}
func adminResetPassword(app *App, u *User, newPass string) error {
hashedPass, err := auth.HashPass([]byte(newPass))
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not create password hash: %v", err)}
}
err = app.db.ChangePassphrase(u.ID, true, "", hashedPass)
if err != nil {
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not update passphrase: %v", err)}
}
return nil
}
func handleViewAdminUpdates(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
check := r.URL.Query().Get("check")
if check == "now" && app.cfg.App.UpdateChecks {
app.updates.CheckNow()
}
p := struct {
*UserPage
*AdminPage
CurReleaseNotesURL string
LastChecked string
LastChecked8601 string
LatestVersion string
LatestReleaseURL string
LatestReleaseNotesURL string
CheckFailed bool
}{
UserPage: NewUserPage(app, r, u, "Updates", nil),
AdminPage: NewAdminPage(app),
}
p.CurReleaseNotesURL = wfReleaseNotesURL(p.Version)
if app.cfg.App.UpdateChecks {
p.LastChecked = app.updates.lastCheck.Format("January 2, 2006, 3:04 PM")
p.LastChecked8601 = app.updates.lastCheck.Format("2006-01-02T15:04:05Z")
p.LatestVersion = app.updates.LatestVersion()
p.LatestReleaseURL = app.updates.ReleaseURL()
p.LatestReleaseNotesURL = app.updates.ReleaseNotesURL()
p.UpdateAvailable = app.updates.AreAvailable()
p.CheckFailed = app.updates.checkError != nil
}
showUserPage(w, "app-updates", p)
return nil
}

996
app.go

File diff suppressed because it is too large Load Diff

128
appstats/appstats.go Normal file
View File

@ -0,0 +1,128 @@
// Copyright 2014-2018 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style license that can be
// found in the LICENSE file of the Gogs project (github.com/gogs/gogs).
package appstats
import (
"fmt"
"math"
"strings"
"time"
)
// Borrowed from github.com/gogs/gogs/pkg/tool
// Seconds-based time units
const (
Minute = 60
Hour = 60 * Minute
Day = 24 * Hour
Week = 7 * Day
Month = 30 * Day
Year = 12 * Month
)
func computeTimeDiff(diff int64) (int64, string) {
diffStr := ""
switch {
case diff <= 0:
diff = 0
diffStr = "now"
case diff < 2:
diff = 0
diffStr = "1 second"
case diff < 1*Minute:
diffStr = fmt.Sprintf("%d seconds", diff)
diff = 0
case diff < 2*Minute:
diff -= 1 * Minute
diffStr = "1 minute"
case diff < 1*Hour:
diffStr = fmt.Sprintf("%d minutes", diff/Minute)
diff -= diff / Minute * Minute
case diff < 2*Hour:
diff -= 1 * Hour
diffStr = "1 hour"
case diff < 1*Day:
diffStr = fmt.Sprintf("%d hours", diff/Hour)
diff -= diff / Hour * Hour
case diff < 2*Day:
diff -= 1 * Day
diffStr = "1 day"
case diff < 1*Week:
diffStr = fmt.Sprintf("%d days", diff/Day)
diff -= diff / Day * Day
case diff < 2*Week:
diff -= 1 * Week
diffStr = "1 week"
case diff < 1*Month:
diffStr = fmt.Sprintf("%d weeks", diff/Week)
diff -= diff / Week * Week
case diff < 2*Month:
diff -= 1 * Month
diffStr = "1 month"
case diff < 1*Year:
diffStr = fmt.Sprintf("%d months", diff/Month)
diff -= diff / Month * Month
case diff < 2*Year:
diff -= 1 * Year
diffStr = "1 year"
default:
diffStr = fmt.Sprintf("%d years", diff/Year)
diff = 0
}
return diff, diffStr
}
// TimeSincePro calculates the time interval and generate full user-friendly string.
func TimeSincePro(then time.Time) string {
now := time.Now()
diff := now.Unix() - then.Unix()
if then.After(now) {
return "future"
}
var timeStr, diffStr string
for {
if diff == 0 {
break
}
diff, diffStr = computeTimeDiff(diff)
timeStr += ", " + diffStr
}
return strings.TrimPrefix(timeStr, ", ")
}
func logn(n, b float64) float64 {
return math.Log(n) / math.Log(b)
}
func humanateBytes(s uint64, base float64, sizes []string) string {
if s < 10 {
return fmt.Sprintf("%d B", s)
}
e := math.Floor(logn(float64(s), base))
suffix := sizes[int(e)]
val := float64(s) / math.Pow(base, math.Floor(e))
f := "%.0f"
if val < 10 {
f = "%.1f"
}
return fmt.Sprintf(f+" %s", val, suffix)
}
// FileSize calculates the file size and generate user-friendly string.
func FileSize(s int64) string {
sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(uint64(s), 1024, sizes)
}

10
auth.go
View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018 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
// AuthenticateUser ensures a user with the given accessToken is valid. Call

View File

@ -1,7 +1,18 @@
/*
* Copyright © 2018-2021 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 author
import (
"github.com/writeas/writefreely/config"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/config"
"os"
"path/filepath"
"regexp"
@ -28,6 +39,7 @@ var reservedUsernames = map[string]bool{
"categories": true,
"category": true,
"changes": true,
"community": true,
"create": true,
"css": true,
"data": true,
@ -44,6 +56,7 @@ var reservedUsernames = map[string]bool{
"guides": true,
"help": true,
"index": true,
"invite": true,
"js": true,
"login": true,
"logout": true,
@ -53,6 +66,7 @@ var reservedUsernames = map[string]bool{
"metadata": true,
"new": true,
"news": true,
"oauth": true,
"post": true,
"posts": true,
"privacy": true,
@ -100,11 +114,17 @@ func IsValidUsername(cfg *config.Config, username string) bool {
// Username is invalid if page with the same name exists. So traverse
// available pages, adding them to reservedUsernames map that'll be checked
// later.
// TODO: use pagesDir const
filepath.Walk("pages/", func(path string, i os.FileInfo, err error) error {
err := filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, "pages"), func(path string, i os.FileInfo, err error) error {
if err != nil {
return err
}
reservedUsernames[i.Name()] = true
return nil
})
if err != nil {
log.Error("[IMPORTANT WARNING]: Could not determine IsValidUsername! %s", err)
return false
}
// Username is invalid if it is reserved!
if _, reserved := reservedUsernames[username]; reserved {

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018 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 (

View File

@ -1 +1,2 @@
writefreely
writefreely.exe

60
cmd/writefreely/config.go Normal file
View File

@ -0,0 +1,60 @@
/*
* Copyright © 2020-2021 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 main
import (
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (
cmdConfig cli.Command = cli.Command{
Name: "config",
Usage: "config management tools",
Subcommands: []*cli.Command{
&cmdConfigGenerate,
&cmdConfigInteractive,
},
}
cmdConfigGenerate cli.Command = cli.Command{
Name: "generate",
Aliases: []string{"gen"},
Usage: "Generate a basic configuration",
Action: genConfigAction,
}
cmdConfigInteractive cli.Command = cli.Command{
Name: "start",
Usage: "Interactive configuration process",
Action: interactiveConfigAction,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "sections",
Value: "server db app",
Usage: "Which sections of the configuration to go through\n" +
"valid values of sections flag are any combination of 'server', 'db' and 'app' \n" +
"example: writefreely config start --sections \"db app\"",
},
},
}
)
func genConfigAction(c *cli.Context) error {
app := writefreely.NewApp(c.String("c"))
return writefreely.CreateConfig(app)
}
func interactiveConfigAction(c *cli.Context) error {
app := writefreely.NewApp(c.String("c"))
writefreely.DoConfig(app, c.String("sections"))
return nil
}

49
cmd/writefreely/db.go Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright © 2020-2021 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 main
import (
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (
cmdDB cli.Command = cli.Command{
Name: "db",
Usage: "db management tools",
Subcommands: []*cli.Command{
&cmdDBInit,
&cmdDBMigrate,
},
}
cmdDBInit cli.Command = cli.Command{
Name: "init",
Usage: "Initialize Database",
Action: initDBAction,
}
cmdDBMigrate cli.Command = cli.Command{
Name: "migrate",
Usage: "Migrate Database",
Action: migrateDBAction,
}
)
func initDBAction(c *cli.Context) error {
app := writefreely.NewApp(c.String("c"))
return writefreely.CreateSchema(app)
}
func migrateDBAction(c *cli.Context) error {
app := writefreely.NewApp(c.String("c"))
return writefreely.Migrate(app)
}

38
cmd/writefreely/keys.go Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright © 2020-2021 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 main
import (
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (
cmdKeys cli.Command = cli.Command{
Name: "keys",
Usage: "key management tools",
Subcommands: []*cli.Command{
&cmdGenerateKeys,
},
}
cmdGenerateKeys cli.Command = cli.Command{
Name: "generate",
Aliases: []string{"gen"},
Usage: "Generate encryption and authentication keys",
Action: genKeysAction,
}
)
func genKeysAction(c *cli.Context) error {
app := writefreely.NewApp(c.String("c"))
return writefreely.GenerateKeyFiles(app)
}

View File

@ -1,9 +1,183 @@
/*
* Copyright © 2018-2021 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 main
import (
"github.com/writeas/writefreely"
"fmt"
"os"
"strings"
"github.com/gorilla/mux"
"github.com/urfave/cli/v2"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely"
)
func main() {
writefreely.Serve()
cli.VersionPrinter = func(c *cli.Context) {
fmt.Printf("%s\n", c.App.Version)
}
app := &cli.App{
Name: "WriteFreely",
Usage: "A beautifully pared-down blogging platform",
Version: writefreely.FormatVersion(),
Action: legacyActions, // legacy due to use of flags for switching actions
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "create-config",
Value: false,
Usage: "Generate a basic configuration",
Hidden: true,
},
&cli.BoolFlag{
Name: "config",
Value: false,
Usage: "Interactive configuration process",
Hidden: true,
},
&cli.StringFlag{
Name: "sections",
Value: "server db app",
Usage: "Which sections of the configuration to go through (requires --config)\n" +
"valid values are any combination of 'server', 'db' and 'app' \n" +
"example: writefreely --config --sections \"db app\"",
Hidden: true,
},
&cli.BoolFlag{
Name: "gen-keys",
Value: false,
Usage: "Generate encryption and authentication keys",
Hidden: true,
},
&cli.BoolFlag{
Name: "init-db",
Value: false,
Usage: "Initialize app database",
Hidden: true,
},
&cli.BoolFlag{
Name: "migrate",
Value: false,
Usage: "Migrate the database",
Hidden: true,
},
&cli.StringFlag{
Name: "create-admin",
Usage: "Create an admin with the given username:password",
Hidden: true,
},
&cli.StringFlag{
Name: "create-user",
Usage: "Create a regular user with the given username:password",
Hidden: true,
},
&cli.StringFlag{
Name: "delete-user",
Usage: "Delete a user with the given username",
Hidden: true,
},
&cli.StringFlag{
Name: "reset-pass",
Usage: "Reset the given user's password",
Hidden: true,
},
}, // legacy flags (set to hidden to eventually switch to bash-complete compatible format)
}
defaultFlags := []cli.Flag{
&cli.StringFlag{
Name: "c",
Value: "config.ini",
Usage: "Load configuration from `FILE`",
},
&cli.BoolFlag{
Name: "debug",
Value: false,
Usage: "Enables debug logging",
},
}
app.Flags = append(app.Flags, defaultFlags...)
app.Commands = []*cli.Command{
&cmdUser,
&cmdDB,
&cmdConfig,
&cmdKeys,
&cmdServe,
}
err := app.Run(os.Args)
if err != nil {
log.Error(err.Error())
os.Exit(1)
}
}
func legacyActions(c *cli.Context) error {
app := writefreely.NewApp(c.String("c"))
switch true {
case c.IsSet("create-config"):
return writefreely.CreateConfig(app)
case c.IsSet("config"):
writefreely.DoConfig(app, c.String("sections"))
return nil
case c.IsSet("gen-keys"):
return writefreely.GenerateKeyFiles(app)
case c.IsSet("init-db"):
return writefreely.CreateSchema(app)
case c.IsSet("migrate"):
return writefreely.Migrate(app)
case c.IsSet("create-admin"):
username, password, err := parseCredentials(c.String("create-admin"))
if err != nil {
return err
}
return writefreely.CreateUser(app, username, password, true)
case c.IsSet("create-user"):
username, password, err := parseCredentials(c.String("create-user"))
if err != nil {
return err
}
return writefreely.CreateUser(app, username, password, false)
case c.IsSet("delete-user"):
return writefreely.DoDeleteAccount(app, c.String("delete-user"))
case c.IsSet("reset-pass"):
return writefreely.ResetPassword(app, c.String("reset-pass"))
}
// Initialize the application
var err error
log.Info("Starting %s...", writefreely.FormatVersion())
app, err = writefreely.Initialize(app, c.Bool("debug"))
if err != nil {
return err
}
// Set app routes
r := mux.NewRouter()
writefreely.InitRoutes(app, r)
app.InitStaticRoutes(r)
// Serve the application
writefreely.Serve(app, r)
return nil
}
func parseCredentials(credentialString string) (string, string, error) {
creds := strings.Split(credentialString, ":")
if len(creds) != 2 {
return "", "", fmt.Errorf("invalid format for passed credentials, must be username:password")
}
return creds[0], creds[1], nil
}

96
cmd/writefreely/user.go Normal file
View File

@ -0,0 +1,96 @@
/*
* Copyright © 2020-2021 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 main
import (
"fmt"
"github.com/urfave/cli/v2"
"github.com/writefreely/writefreely"
)
var (
cmdUser cli.Command = cli.Command{
Name: "user",
Usage: "user management tools",
Subcommands: []*cli.Command{
&cmdAddUser,
&cmdDelUser,
&cmdResetPass,
// TODO: possibly add a user list command
},
}
cmdAddUser cli.Command = cli.Command{
Name: "create",
Usage: "Add new user",
Aliases: []string{"a", "add"},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "admin",
Value: false,
Usage: "Create admin user",
},
},
Action: addUserAction,
}
cmdDelUser cli.Command = cli.Command{
Name: "delete",
Usage: "Delete user",
Aliases: []string{"del", "d"},
Action: delUserAction,
}
cmdResetPass cli.Command = cli.Command{
Name: "reset-pass",
Usage: "Reset user's password",
Aliases: []string{"resetpass", "reset"},
Action: resetPassAction,
}
)
func addUserAction(c *cli.Context) error {
credentials := ""
if c.NArg() > 0 {
credentials = c.Args().Get(0)
} else {
return fmt.Errorf("No user passed. Example: writefreely user add [USER]:[PASSWORD]")
}
username, password, err := parseCredentials(credentials)
if err != nil {
return err
}
app := writefreely.NewApp(c.String("c"))
return writefreely.CreateUser(app, username, password, c.Bool("admin"))
}
func delUserAction(c *cli.Context) error {
username := ""
if c.NArg() > 0 {
username = c.Args().Get(0)
} else {
return fmt.Errorf("No user passed. Example: writefreely user delete [USER]")
}
app := writefreely.NewApp(c.String("c"))
return writefreely.DoDeleteAccount(app, username)
}
func resetPassAction(c *cli.Context) error {
username := ""
if c.NArg() > 0 {
username = c.Args().Get(0)
} else {
return fmt.Errorf("No user passed. Example: writefreely user reset-pass [USER]")
}
app := writefreely.NewApp(c.String("c"))
return writefreely.ResetPassword(app, username)
}

48
cmd/writefreely/web.go Normal file
View File

@ -0,0 +1,48 @@
/*
* Copyright © 2020-2021 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 main
import (
"github.com/gorilla/mux"
"github.com/urfave/cli/v2"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely"
)
var (
cmdServe cli.Command = cli.Command{
Name: "serve",
Aliases: []string{"web"},
Usage: "Run web application",
Action: serveAction,
}
)
func serveAction(c *cli.Context) error {
// Initialize the application
app := writefreely.NewApp(c.String("c"))
var err error
log.Info("Starting %s...", writefreely.FormatVersion())
app, err = writefreely.Initialize(app, c.Bool("debug"))
if err != nil {
return err
}
// Set app routes
r := mux.NewRouter()
writefreely.InitRoutes(app, r)
app.InitStaticRoutes(r)
// Serve the application
writefreely.Serve(app, r)
return nil
}

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018-2022 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 (
@ -14,14 +24,26 @@ import (
"unicode"
"github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/impart"
"github.com/writeas/web-core/activitystreams"
"github.com/writeas/web-core/auth"
"github.com/writeas/web-core/bots"
"github.com/writeas/web-core/i18n"
"github.com/writeas/web-core/log"
waposts "github.com/writeas/web-core/posts"
"github.com/writeas/writefreely/author"
"github.com/writeas/writefreely/page"
"github.com/writeas/web-core/posts"
"github.com/writefreely/writefreely/author"
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page"
"github.com/writefreely/writefreely/spam"
"golang.org/x/net/idna"
)
const (
collAttrLetterReplyTo = "letter_reply_to"
collMaxLengthTitle = 255
collMaxLengthDescription = 160
)
type (
@ -36,6 +58,7 @@ type (
Language string `schema:"lang" json:"lang,omitempty"`
StyleSheet string `datastore:"style_sheet" schema:"style_sheet" json:"style_sheet"`
Script string `datastore:"script" schema:"script" json:"script,omitempty"`
Signature string `datastore:"post_signature" schema:"signature" json:"-"`
Public bool `datastore:"public" json:"public"`
Visibility collVisibility `datastore:"private" json:"-"`
Format string `datastore:"format" json:"format,omitempty"`
@ -44,22 +67,36 @@ type (
PublicOwner bool `datastore:"public_owner" json:"-"`
URL string `json:"url,omitempty"`
db *datastore
Monetization string `json:"monetization_pointer,omitempty"`
Verification string `json:"verification_link"`
db *datastore
hostName string
}
CollectionObj struct {
Collection
TotalPosts int `json:"total_posts"`
Owner *User `json:"owner,omitempty"`
Posts *[]PublicPost `json:"posts,omitempty"`
Format *CollectionFormat
}
DisplayCollection struct {
*CollectionObj
Prefix string
NavSuffix string
IsTopLevel bool
CurrentPage int
TotalPages int
Format *CollectionFormat
Silenced bool
}
CollectionNav struct {
*Collection
Path string
SingleUser bool
CanPost bool
}
SubmittedCollection struct {
// Data used for updating a given collection
ID int64
@ -70,16 +107,21 @@ type (
Privacy int `schema:"privacy" json:"privacy"`
Pass string `schema:"password" json:"password"`
MathJax bool `schema:"mathjax" json:"mathjax"`
EmailSubs bool `schema:"email_subs" json:"email_subs"`
Handle string `schema:"handle" json:"handle"`
// Actual collection values updated in the DB
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"`
Visibility *int `schema:"visibility" json:"public"`
Format *sql.NullString `schema:"format" json:"format"`
Alias *string `schema:"alias" json:"alias"`
Title *string `schema:"title" json:"title"`
Description *string `schema:"description" json:"description"`
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"`
Visibility *int `schema:"visibility" json:"public"`
Format *sql.NullString `schema:"format" json:"format"`
}
CollectionFormat struct {
Format string
@ -92,6 +134,8 @@ type (
// User-related fields
isCollOwner bool
isAuthorized bool
}
)
@ -115,6 +159,21 @@ const (
CollProtected
)
var collVisibilityStrings = map[string]collVisibility{
"unlisted": CollUnlisted,
"public": CollPublic,
"private": CollPrivate,
"protected": CollProtected,
}
func defaultVisibility(cfg *config.Config) collVisibility {
vis, ok := collVisibilityStrings[cfg.App.DefaultVisibility]
if !ok {
vis = CollUnlisted
}
return vis
}
func (cf *CollectionFormat) Ascending() bool {
return cf.Format == "novel"
}
@ -147,6 +206,11 @@ func (c *Collection) NewFormat() *CollectionFormat {
return cf
}
func (c *Collection) IsInstanceColl() bool {
ur, _ := url.Parse(c.hostName)
return c.Alias == ur.Host
}
func (c *Collection) IsUnlisted() bool {
return c.Visibility == 0
}
@ -196,29 +260,37 @@ func (c *Collection) DisplayCanonicalURL() string {
if p == "/" {
p = ""
}
return u.Hostname() + p
d := u.Hostname()
d, _ = idna.ToUnicode(d)
return d + p
}
// RedirectingCanonicalURL returns the fully-qualified canonical URL for the Collection, with a trailing slash. The
// hostName field needs to be populated for this to work correctly.
func (c *Collection) RedirectingCanonicalURL(isRedir bool) string {
if c.hostName == "" {
// If this is true, the human programmers screwed up. So ask for a bug report and fail, fail, fail
log.Error("[PROGRAMMER ERROR] WARNING: Collection.hostName is empty! Federation and many other things will fail! If you're seeing this in the wild, please report this bug and let us know what you were doing just before this: https://github.com/writefreely/writefreely/issues/new?template=bug_report.md")
}
if isSingleUser {
return hostName + "/"
return c.hostName + "/"
}
return fmt.Sprintf("%s/%s/", hostName, c.Alias)
return fmt.Sprintf("%s/%s/", c.hostName, c.Alias)
}
// PrevPageURL provides a full URL for the previous page of collection posts,
// returning a /page/N result for pages >1
func (c *Collection) PrevPageURL(prefix string, n int, tl bool) string {
func (c *Collection) PrevPageURL(prefix, navSuffix string, n int, tl bool) string {
u := ""
if n == 2 {
// Previous page is 1; no need for /page/ prefix
if prefix == "" {
u = "/"
u = navSuffix + "/"
}
// Else leave off trailing slash
} else {
u = fmt.Sprintf("/page/%d", n-1)
u = fmt.Sprintf("%s/page/%d", navSuffix, n-1)
}
if tl {
@ -228,11 +300,12 @@ func (c *Collection) PrevPageURL(prefix string, n int, tl bool) string {
}
// NextPageURL provides a full URL for the next page of collection posts
func (c *Collection) NextPageURL(prefix string, n int, tl bool) string {
func (c *Collection) NextPageURL(prefix, navSuffix string, n int, tl bool) string {
if tl {
return fmt.Sprintf("/page/%d", n+1)
return fmt.Sprintf("%s/page/%d", navSuffix, n+1)
}
return fmt.Sprintf("/%s%s/page/%d", prefix, c.Alias, n+1)
return fmt.Sprintf("/%s%s%s/page/%d", prefix, c.Alias, navSuffix, n+1)
}
func (c *Collection) DisplayTitle() string {
@ -249,11 +322,10 @@ func (c *Collection) StyleSheetDisplay() template.CSS {
// ForPublic modifies the Collection for public consumption, such as via
// the API.
func (c *Collection) ForPublic() {
c.ID = 0
c.URL = c.CanonicalURL()
}
var isLowerLetter = regexp.MustCompile("[a-z]").MatchString
var isAvatarChar = regexp.MustCompile("[a-z0-9]").MatchString
func (c *Collection) PersonObject(ids ...int64) *activitystreams.Person {
accountRoot := c.FederatedAccount()
@ -264,12 +336,11 @@ func (c *Collection) PersonObject(ids ...int64) *activitystreams.Person {
p.Name = c.DisplayTitle()
p.Summary = c.Description
if p.Name != "" {
fl := string(unicode.ToLower([]rune(p.Name)[0]))
if isLowerLetter(fl) {
if av := c.AvatarURL(); av != "" {
p.Icon = activitystreams.Image{
Type: "Image",
MediaType: "image/png",
URL: hostName + "/img/avatars/" + fl + ".png",
URL: av,
}
}
}
@ -287,8 +358,16 @@ func (c *Collection) PersonObject(ids ...int64) *activitystreams.Person {
return p
}
func (c *Collection) AvatarURL() string {
fl := string(unicode.ToLower([]rune(c.DisplayTitle())[0]))
if !isAvatarChar(fl) {
return ""
}
return c.hostName + "/img/avatars/" + fl + ".png"
}
func (c *Collection) FederatedAPIBase() string {
return hostName + "/"
return c.hostName + "/"
}
func (c *Collection) FederatedAccount() string {
@ -300,8 +379,53 @@ func (c *Collection) RenderMathJax() bool {
return c.db.CollectionHasAttribute(c.ID, "render_mathjax")
}
func newCollection(app *app, w http.ResponseWriter, r *http.Request) error {
reqJSON := IsJSON(r.Header.Get("Content-Type"))
func (c *Collection) EmailSubsEnabled() bool {
return c.db.CollectionHasAttribute(c.ID, "email_subs")
}
func (c *Collection) MonetizationURL() string {
if c.Monetization == "" {
return ""
}
return strings.Replace(c.Monetization, "$", "https://", 1)
}
// DisplayDescription returns the description with rendered Markdown and HTML.
func (c *Collection) DisplayDescription() *template.HTML {
if c.Description == "" {
s := template.HTML("")
return &s
}
t := template.HTML(posts.ApplyBasicAccessibleMarkdown([]byte(c.Description)))
return &t
}
// PlainDescription returns the description with all Markdown and HTML removed.
func (c *Collection) PlainDescription() string {
if c.Description == "" {
return ""
}
desc := stripHTMLWithoutEscaping(c.Description)
desc = stripmd.Strip(desc)
return desc
}
func (c CollectionPage) DisplayMonetization() string {
return displayMonetization(c.Monetization, c.Alias)
}
func (c *DisplayCollection) Direction() string {
if c.Language == "" {
return "auto"
}
if i18n.LangIsRTL(c.Language) {
return "rtl"
}
return "ltr"
}
func newCollection(app *App, w http.ResponseWriter, r *http.Request) error {
reqJSON := IsJSON(r)
alias := r.FormValue("alias")
title := r.FormValue("title")
@ -341,36 +465,41 @@ func newCollection(app *app, w http.ResponseWriter, r *http.Request) error {
return impart.HTTPError{http.StatusBadRequest, fmt.Sprintf("Parameter(s) %srequired.", missingParams)}
}
var userID int64
var err error
if reqJSON && !c.Web {
accessToken = r.Header.Get("Authorization")
if accessToken == "" {
return ErrNoAccessToken
}
userID = app.db.GetUserID(accessToken)
if userID == -1 {
return ErrBadAccessToken
}
} else {
u = getUserSession(app, r)
if u == nil {
return ErrNotLoggedIn
}
userID = u.ID
}
silenced, err := app.db.IsUserSilenced(userID)
if err != nil {
log.Error("new collection: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrUserSilenced
}
if !author.IsValidUsername(app.cfg, c.Alias) {
return impart.HTTPError{http.StatusPreconditionFailed, "Collection alias isn't valid."}
}
var coll *Collection
var err error
if accessToken != "" {
coll, err = app.db.CreateCollectionFromToken(c.Alias, c.Title, accessToken)
if err != nil {
// TODO: handle this
return err
}
} else {
coll, err = app.db.CreateCollection(c.Alias, c.Title, u.ID)
if err != nil {
// TODO: handle this
return err
}
coll, err := app.db.CreateCollection(app.cfg, c.Alias, c.Title, userID)
if err != nil {
// TODO: handle this
return err
}
res := &CollectionObj{Collection: *coll}
@ -383,7 +512,7 @@ func newCollection(app *app, w http.ResponseWriter, r *http.Request) error {
return impart.HTTPError{http.StatusFound, redirectTo}
}
func apiCheckCollectionPermissions(app *app, r *http.Request, c *Collection) (int64, error) {
func apiCheckCollectionPermissions(app *App, r *http.Request, c *Collection) (int64, error) {
accessToken := r.Header.Get("Authorization")
var userID int64 = -1
if accessToken != "" {
@ -403,9 +532,8 @@ func apiCheckCollectionPermissions(app *app, r *http.Request, c *Collection) (in
}
// fetchCollection handles the API endpoint for retrieving collection data.
func fetchCollection(app *app, w http.ResponseWriter, r *http.Request) error {
accept := r.Header.Get("Accept")
if strings.Contains(accept, "application/activity+json") {
func fetchCollection(app *App, w http.ResponseWriter, r *http.Request) error {
if IsActivityPubRequest(r) {
return handleFetchCollectionActivities(app, w, r)
}
@ -418,8 +546,10 @@ func fetchCollection(app *app, w http.ResponseWriter, r *http.Request) error {
if err != nil {
return err
}
c.hostName = app.cfg.App.Host
// Redirect users who aren't requesting JSON
reqJSON := IsJSON(r.Header.Get("Content-Type"))
reqJSON := IsJSON(r)
if !reqJSON {
return impart.HTTPError{http.StatusFound, c.CanonicalURL()}
}
@ -442,6 +572,7 @@ func fetchCollection(app *app, w http.ResponseWriter, r *http.Request) error {
res.Owner = u
}
}
// TODO: check status for silenced
app.db.GetPostsCount(res, isCollOwner)
// Strip non-public information
res.Collection.ForPublic()
@ -451,7 +582,7 @@ func fetchCollection(app *app, w http.ResponseWriter, r *http.Request) error {
// fetchCollectionPosts handles an API endpoint for retrieving a collection's
// posts.
func fetchCollectionPosts(app *app, w http.ResponseWriter, r *http.Request) error {
func fetchCollectionPosts(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
alias := vars["alias"]
@ -459,6 +590,7 @@ func fetchCollectionPosts(app *app, w http.ResponseWriter, r *http.Request) erro
if err != nil {
return err
}
c.hostName = app.cfg.App.Host
// Check permissions
userID, err := apiCheckCollectionPermissions(app, r, c)
@ -476,11 +608,11 @@ func fetchCollectionPosts(app *app, w http.ResponseWriter, r *http.Request) erro
}
}
posts, err := app.db.GetPosts(c, page, isCollOwner)
ps, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false)
if err != nil {
return err
}
coll := &CollectionObj{Collection: *c, Posts: posts}
coll := &CollectionObj{Collection: *c, Posts: ps}
app.db.GetPostsCount(coll, isCollOwner)
// Strip non-public information
coll.Collection.ForPublic()
@ -488,7 +620,7 @@ func fetchCollectionPosts(app *app, w http.ResponseWriter, r *http.Request) erro
// Transform post bodies if needed
if r.FormValue("body") == "html" {
for _, p := range *coll.Posts {
p.Content = waposts.ApplyMarkdown([]byte(p.Content))
p.Content = posts.ApplyMarkdown([]byte(p.Content))
}
}
@ -501,10 +633,52 @@ type CollectionPage struct {
IsCustomDomain bool
IsWelcome bool
IsOwner bool
IsCollLoggedIn bool
Honeypot string
IsSubscriber bool
CanPin bool
Username string
Monetization string
Flash template.HTML
Collections *[]Collection
PinnedPosts *[]PublicPost
IsAdmin bool
CanInvite bool
// Helper field for Chorus mode
CollAlias string
}
type TagCollectionPage struct {
CollectionPage
Tag string
}
func (tcp TagCollectionPage) PrevPageURL(prefix string, n int, tl bool) string {
u := fmt.Sprintf("/tag:%s", tcp.Tag)
if n > 2 {
u += fmt.Sprintf("/page/%d", n-1)
}
if tl {
return u
}
return "/" + prefix + tcp.Alias + u
}
func (tcp TagCollectionPage) NextPageURL(prefix string, n int, tl bool) string {
if tl {
return fmt.Sprintf("/tag:%s/page/%d", tcp.Tag, n+1)
}
return fmt.Sprintf("/%s%s/tag:%s/page/%d", prefix, tcp.Alias, tcp.Tag, n+1)
}
func NewCollectionObj(c *Collection) *CollectionObj {
return &CollectionObj{
Collection: *c,
Format: c.NewFormat(),
}
}
func (c *CollectionObj) ScriptDisplay() template.JS {
@ -547,7 +721,7 @@ func processCollectionRequest(cr *collectionReq, vars map[string]string, w http.
// domain that doesn't yet have a collection associated, or if a collection
// requires a password. In either case, this will return nil, nil -- thus both
// values should ALWAYS be checked to determine whether or not to continue.
func processCollectionPermissions(app *app, cr *collectionReq, u *User, w http.ResponseWriter, r *http.Request) (*Collection, error) {
func processCollectionPermissions(app *App, cr *collectionReq, u *User, w http.ResponseWriter, r *http.Request) (*Collection, error) {
// Display collection if this is a collection
var c *Collection
var err error
@ -584,6 +758,7 @@ func processCollectionPermissions(app *app, cr *collectionReq, u *User, w http.R
}
return nil, err
}
c.hostName = app.cfg.App.Host
// Update CollectionRequest to reflect owner status
cr.isCollOwner = u != nil && u.ID == c.OwnerID
@ -598,10 +773,20 @@ func processCollectionPermissions(app *app, cr *collectionReq, u *User, w http.R
uname = u.Username
}
// See if we've authorized this collection
authd := isAuthorizedForCollection(app, c.Alias, r)
// TODO: move this to all permission checks?
suspended, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("process protected collection permissions: %v", err)
return nil, err
}
if suspended {
return nil, ErrCollectionNotFound
}
if !authd {
// See if we've authorized this collection
cr.isAuthorized = isAuthorizedForCollection(app, c.Alias, r)
if !cr.isAuthorized {
p := struct {
page.StaticPage
*CollectionObj
@ -638,35 +823,34 @@ func processCollectionPermissions(app *app, cr *collectionReq, u *User, w http.R
return c, nil
}
func checkUserForCollection(app *app, cr *collectionReq, r *http.Request, isPostReq bool) (*User, error) {
func checkUserForCollection(app *App, cr *collectionReq, r *http.Request, isPostReq bool) (*User, error) {
u := getUserSession(app, r)
return u, nil
}
func newDisplayCollection(c *Collection, cr *collectionReq, page int) *DisplayCollection {
coll := &DisplayCollection{
CollectionObj: &CollectionObj{Collection: *c},
CollectionObj: NewCollectionObj(c),
CurrentPage: page,
Prefix: cr.prefix,
IsTopLevel: isSingleUser,
Format: c.NewFormat(),
}
c.db.GetPostsCount(coll.CollectionObj, cr.isCollOwner)
return coll
}
// getCollectionPage returns the collection page as an int. If the parsed page value is not
// greater than 0 then the default value of 1 is returned.
func getCollectionPage(vars map[string]string) int {
page := 1
var p int
p, _ = strconv.Atoi(vars["page"])
if p > 0 {
page = p
if p, _ := strconv.Atoi(vars["page"]); p > 0 {
return p
}
return page
return 1
}
// handleViewCollection displays the requested Collection
func handleViewCollection(app *app, w http.ResponseWriter, r *http.Request) error {
func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
cr := &collectionReq{}
@ -686,11 +870,19 @@ func handleViewCollection(app *app, w http.ResponseWriter, r *http.Request) erro
if c == nil || err != nil {
return err
}
c.hostName = app.cfg.App.Host
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("view collection: %v", err)
return ErrInternalGeneral
}
// Serve ActivityStreams data now, if requested
if strings.Contains(r.Header.Get("Accept"), "application/activity+json") {
if IsActivityPubRequest(r) {
ac := c.PersonObject()
ac.Context = []interface{}{activitystreams.Namespace}
setCacheControl(w, apCacheTime)
return impart.RenderActivityJSON(w, ac, http.StatusOK)
}
@ -707,32 +899,43 @@ func handleViewCollection(app *app, w http.ResponseWriter, r *http.Request) erro
return impart.HTTPError{http.StatusFound, redirURL}
}
coll.Posts, _ = app.db.GetPosts(c, page, cr.isCollOwner)
coll.Posts, _ = app.db.GetPosts(app.cfg, c, page, cr.isCollOwner, false, false)
// Serve collection
displayPage := CollectionPage{
DisplayCollection: coll,
IsCollLoggedIn: cr.isAuthorized,
StaticPage: pageForReq(app, r),
IsCustomDomain: cr.isCustomDomain,
IsWelcome: r.FormValue("greeting") != "",
Honeypot: spam.HoneypotFieldName(),
CollAlias: c.Alias,
}
flashes, _ := getSessionFlashes(app, w, r, nil)
for _, f := range flashes {
displayPage.Flash = template.HTML(f)
}
displayPage.IsAdmin = u != nil && u.IsAdmin()
displayPage.CanInvite = canUserInvite(app.cfg, displayPage.IsAdmin)
var owner *User
if u != nil {
displayPage.Username = u.Username
displayPage.IsOwner = u.ID == coll.OwnerID
displayPage.IsSubscriber = u.IsEmailSubscriber(app, coll.ID)
if displayPage.IsOwner {
// Add in needed information for users viewing their own collection
owner = u
displayPage.CanPin = true
pubColls, err := app.db.GetPublishableCollections(owner)
pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host)
if err != nil {
log.Error("unable to fetch collections: %v", err)
}
displayPage.Collections = pubColls
}
}
if owner == nil {
isOwner := owner != nil
if !isOwner {
// Current user doesn't own collection; retrieve owner information
owner, err = app.db.GetUserByID(coll.OwnerID)
if err != nil {
@ -740,14 +943,23 @@ func handleViewCollection(app *app, w http.ResponseWriter, r *http.Request) erro
log.Error("Error getting user for collection: %v", err)
}
}
if !isOwner && silenced {
return ErrCollectionNotFound
}
displayPage.Silenced = isOwner && silenced
displayPage.Owner = owner
coll.Owner = displayPage.Owner
// Add more data
// TODO: fix this mess of collections inside collections
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj)
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
err = templates["collection"].ExecuteTemplate(w, "collection", displayPage)
collTmpl := "collection"
if app.cfg.App.Chorus {
collTmpl = "chorus-collection"
}
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
if err != nil {
log.Error("Unable to render collection index: %v", err)
}
@ -772,7 +984,20 @@ func handleViewCollection(app *app, w http.ResponseWriter, r *http.Request) erro
return err
}
func handleViewCollectionTag(app *app, w http.ResponseWriter, r *http.Request) error {
func handleViewMention(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
handle := vars["handle"]
remoteUser, err := app.db.GetProfilePageFromHandle(app, handle)
if err != nil || remoteUser == "" {
log.Error("Couldn't find user %s: %v", handle, err)
return ErrRemoteUserNotFound
}
return impart.HTTPError{Status: http.StatusFound, Message: remoteUser}
}
func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
tag := vars["tag"]
@ -796,16 +1021,29 @@ func handleViewCollectionTag(app *app, w http.ResponseWriter, r *http.Request) e
coll := newDisplayCollection(c, cr, page)
coll.Posts, _ = app.db.GetPostsTagged(c, tag, page, cr.isCollOwner)
taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner)
if err != nil {
return err
}
ttlPosts := len(taggedPostIDs)
pagePosts := coll.Format.PostsPerPage()
coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts)))
if coll.TotalPages > 0 && page > coll.TotalPages {
redirURL := fmt.Sprintf("/page/%d", coll.TotalPages)
if !app.cfg.App.SingleUser {
redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL)
}
return impart.HTTPError{http.StatusFound, redirURL}
}
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, page, cr.isCollOwner)
if coll.Posts != nil && len(*coll.Posts) == 0 {
return ErrCollectionPageNotFound
}
// Serve collection
displayPage := struct {
CollectionPage
Tag string
}{
displayPage := TagCollectionPage{
CollectionPage: CollectionPage{
DisplayCollection: coll,
StaticPage: pageForReq(app, r),
@ -822,26 +1060,32 @@ func handleViewCollectionTag(app *app, w http.ResponseWriter, r *http.Request) e
owner = u
displayPage.CanPin = true
pubColls, err := app.db.GetPublishableCollections(owner)
pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host)
if err != nil {
log.Error("unable to fetch collections: %v", err)
}
displayPage.Collections = pubColls
}
}
if owner == nil {
isOwner := owner != nil
if !isOwner {
// Current user doesn't own collection; retrieve owner information
owner, err = app.db.GetUserByID(coll.OwnerID)
if err != nil {
// Log the error and just continue
log.Error("Error getting user for collection: %v", err)
}
if owner.IsSilenced() {
return ErrCollectionNotFound
}
}
displayPage.Silenced = owner != nil && owner.IsSilenced()
displayPage.Owner = owner
coll.Owner = displayPage.Owner
// Add more data
// TODO: fix this mess of collections inside collections
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj)
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
err = templates["collection-tags"].ExecuteTemplate(w, "collection-tags", displayPage)
if err != nil {
@ -851,7 +1095,112 @@ func handleViewCollectionTag(app *app, w http.ResponseWriter, r *http.Request) e
return nil
}
func handleCollectionPostRedirect(app *app, w http.ResponseWriter, r *http.Request) error {
func handleViewCollectionLang(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
lang := vars["lang"]
cr := &collectionReq{}
err := processCollectionRequest(cr, vars, w, r)
if err != nil {
return err
}
u, err := checkUserForCollection(app, cr, r, false)
if err != nil {
return err
}
page := getCollectionPage(vars)
c, err := processCollectionPermissions(app, cr, u, w, r)
if c == nil || err != nil {
return err
}
coll := newDisplayCollection(c, cr, page)
coll.Language = lang
coll.NavSuffix = fmt.Sprintf("/lang:%s", lang)
ttlPosts, err := app.db.GetCollLangTotalPosts(coll.ID, lang)
if err != nil {
log.Error("Unable to getCollLangTotalPosts: %s", err)
}
pagePosts := coll.Format.PostsPerPage()
coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts)))
if coll.TotalPages > 0 && page > coll.TotalPages {
redirURL := fmt.Sprintf("/lang:%s/page/%d", lang, coll.TotalPages)
if !app.cfg.App.SingleUser {
redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL)
}
return impart.HTTPError{http.StatusFound, redirURL}
}
coll.Posts, _ = app.db.GetLangPosts(app.cfg, c, lang, page, cr.isCollOwner)
if err != nil {
return ErrCollectionPageNotFound
}
// Serve collection
displayPage := struct {
CollectionPage
Tag string
}{
CollectionPage: CollectionPage{
DisplayCollection: coll,
StaticPage: pageForReq(app, r),
IsCustomDomain: cr.isCustomDomain,
},
Tag: lang,
}
var owner *User
if u != nil {
displayPage.Username = u.Username
displayPage.IsOwner = u.ID == coll.OwnerID
if displayPage.IsOwner {
// Add in needed information for users viewing their own collection
owner = u
displayPage.CanPin = true
pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host)
if err != nil {
log.Error("unable to fetch collections: %v", err)
}
displayPage.Collections = pubColls
}
}
isOwner := owner != nil
if !isOwner {
// Current user doesn't own collection; retrieve owner information
owner, err = app.db.GetUserByID(coll.OwnerID)
if err != nil {
// Log the error and just continue
log.Error("Error getting user for collection: %v", err)
}
if owner.IsSilenced() {
return ErrCollectionNotFound
}
}
displayPage.Silenced = owner != nil && owner.IsSilenced()
displayPage.Owner = owner
coll.Owner = displayPage.Owner
// Add more data
// TODO: fix this mess of collections inside collections
displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner)
displayPage.Monetization = app.db.GetCollectionAttribute(coll.ID, "monetization_pointer")
collTmpl := "collection"
if app.cfg.App.Chorus {
collTmpl = "chorus-collection"
}
err = templates[collTmpl].ExecuteTemplate(w, "collection", displayPage)
if err != nil {
log.Error("Unable to render collection lang page: %v", err)
}
return nil
}
func handleCollectionPostRedirect(app *App, w http.ResponseWriter, r *http.Request) error {
vars := mux.Vars(r)
slug := vars["slug"]
@ -869,17 +1218,16 @@ func handleCollectionPostRedirect(app *app, w http.ResponseWriter, r *http.Reque
return impart.HTTPError{http.StatusFound, loc}
}
func existingCollection(app *app, w http.ResponseWriter, r *http.Request) error {
reqJSON := IsJSON(r.Header.Get("Content-Type"))
func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error {
reqJSON := IsJSON(r)
vars := mux.Vars(r)
collAlias := vars["alias"]
isWeb := r.FormValue("web") == "1"
var u *User
u := &User{}
if reqJSON && !isWeb {
// Ensure an access token was given
accessToken := r.Header.Get("Authorization")
u = &User{}
u.ID = app.db.GetUserID(accessToken)
if u.ID == -1 {
return ErrBadAccessToken
@ -891,6 +1239,16 @@ func existingCollection(app *app, w http.ResponseWriter, r *http.Request) error
}
}
silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil {
log.Error("existing collection: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrUserSilenced
}
if r.Method == "DELETE" {
err := app.db.DeleteCollection(collAlias, u.ID)
if err != nil {
@ -903,7 +1261,6 @@ func existingCollection(app *app, w http.ResponseWriter, r *http.Request) error
}
c := SubmittedCollection{OwnerID: uint64(u.ID)}
var err error
if reqJSON {
// Decode JSON request
@ -927,7 +1284,7 @@ func existingCollection(app *app, w http.ResponseWriter, r *http.Request) error
}
}
err = app.db.UpdateCollection(&c, collAlias)
err = app.db.UpdateCollection(app, &c, collAlias)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
if reqJSON {
@ -964,7 +1321,7 @@ func collectionAliasFromReq(r *http.Request) string {
return alias
}
func handleWebCollectionUnlock(app *app, w http.ResponseWriter, r *http.Request) error {
func handleWebCollectionUnlock(app *App, w http.ResponseWriter, r *http.Request) error {
var readReq struct {
Alias string `schema:"alias" json:"alias"`
Pass string `schema:"password" json:"password"`
@ -1031,7 +1388,7 @@ func handleWebCollectionUnlock(app *app, w http.ResponseWriter, r *http.Request)
return impart.HTTPError{http.StatusFound, next}
}
func isAuthorizedForCollection(app *app, alias string, r *http.Request) bool {
func isAuthorizedForCollection(app *App, alias string, r *http.Request) bool {
authd := false
session, err := app.sessionStore.Get(r, blogPassCookieName)
if err == nil {
@ -1039,3 +1396,43 @@ func isAuthorizedForCollection(app *app, alias string, r *http.Request) bool {
}
return authd
}
func logOutCollection(app *App, alias string, w http.ResponseWriter, r *http.Request) error {
session, err := app.sessionStore.Get(r, blogPassCookieName)
if err != nil {
return err
}
// Remove this from map of blogs logged into
delete(session.Values, alias)
// If not auth'd with any blog, delete entire cookie
if len(session.Values) == 0 {
session.Options.MaxAge = -1
}
return session.Save(r, w)
}
func handleLogOutCollection(app *App, w http.ResponseWriter, r *http.Request) error {
alias := collectionAliasFromReq(r)
var c *Collection
var err error
if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
}
if err != nil {
return err
}
if !c.IsProtected() {
// Invalid to log out of this collection
return ErrCollectionPageNotFound
}
err = logOutCollection(app, c.Alias, w, r)
if err != nil {
addSessionFlash(app, w, r, "Logging out failed. Try clearing cookies for this site, instead.", nil)
}
return impart.HTTPError{http.StatusFound, c.CanonicalURL()}
}

View File

@ -1,67 +1,200 @@
/*
* Copyright © 2018-2021 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 config holds and assists in the configuration of a writefreely instance.
package config
import (
"gopkg.in/ini.v1"
"net/url"
"strings"
"github.com/go-ini/ini"
"github.com/writeas/web-core/log"
"golang.org/x/net/idna"
)
const (
// FileName is the default configuration file name
FileName = "config.ini"
UserNormal UserType = "user"
UserAdmin = "admin"
)
type (
UserType string
// ServerCfg holds values that affect how the HTTP server runs
ServerCfg struct {
HiddenHost string `ini:"hidden_host"`
Port int `ini:"port"`
Bind string `ini:"bind"`
TLSCertPath string `ini:"tls_cert_path"`
TLSKeyPath string `ini:"tls_key_path"`
Autocert bool `ini:"autocert"`
TemplatesParentDir string `ini:"templates_parent_dir"`
StaticParentDir string `ini:"static_parent_dir"`
PagesParentDir string `ini:"pages_parent_dir"`
KeysParentDir string `ini:"keys_parent_dir"`
HashSeed string `ini:"hash_seed"`
GopherPort int `ini:"gopher_port"`
Dev bool `ini:"-"`
}
// DatabaseCfg holds values that determine how the application connects to a datastore
DatabaseCfg struct {
Type string `ini:"type"`
FileName string `ini:"filename"`
User string `ini:"username"`
Password string `ini:"password"`
Database string `ini:"database"`
Host string `ini:"host"`
Port int `ini:"port"`
TLS bool `ini:"tls"`
}
WriteAsOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
AuthLocation string `ini:"auth_location"`
TokenLocation string `ini:"token_location"`
InspectLocation string `ini:"inspect_location"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
GitlabOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
Host string `ini:"host"`
DisplayName string `ini:"display_name"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
GiteaOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
Host string `ini:"host"`
DisplayName string `ini:"display_name"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
SlackOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
TeamID string `ini:"team_id"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
}
GenericOauthCfg struct {
ClientID string `ini:"client_id"`
ClientSecret string `ini:"client_secret"`
Host string `ini:"host"`
DisplayName string `ini:"display_name"`
CallbackProxy string `ini:"callback_proxy"`
CallbackProxyAPI string `ini:"callback_proxy_api"`
TokenEndpoint string `ini:"token_endpoint"`
InspectEndpoint string `ini:"inspect_endpoint"`
AuthEndpoint string `ini:"auth_endpoint"`
Scope string `ini:"scope"`
AllowDisconnect bool `ini:"allow_disconnect"`
MapUserID string `ini:"map_user_id"`
MapUsername string `ini:"map_username"`
MapDisplayName string `ini:"map_display_name"`
MapEmail string `ini:"map_email"`
}
// AppCfg holds values that affect how the application functions
AppCfg struct {
SiteName string `ini:"site_name"`
SiteDesc string `ini:"site_description"`
Host string `ini:"host"`
// Site appearance
Theme string `ini:"theme"`
Editor string `ini:"editor"`
JSDisabled bool `ini:"disable_js"`
WebFonts bool `ini:"webfonts"`
Landing string `ini:"landing"`
SimpleNav bool `ini:"simple_nav"`
WFModesty bool `ini:"wf_modesty"`
// Site functionality
Chorus bool `ini:"chorus"`
Forest bool `ini:"forest"` // The admin cares about the forest, not the trees. Hide unnecessary technical info.
DisableDrafts bool `ini:"disable_drafts"`
// Users
SingleUser bool `ini:"single_user"`
OpenRegistration bool `ini:"open_registration"`
OpenDeletion bool `ini:"open_deletion"`
MinUsernameLen int `ini:"min_username_len"`
MaxBlogs int `ini:"max_blogs"`
// Options for public instances
// Federation
Federation bool `ini:"federation"`
PublicStats bool `ini:"public_stats"`
Private bool `ini:"private"`
Federation bool `ini:"federation"`
PublicStats bool `ini:"public_stats"`
Monetization bool `ini:"monetization"`
NotesOnly bool `ini:"notes_only"`
// Access
Private bool `ini:"private"`
// Additional functions
LocalTimeline bool `ini:"local_timeline"`
UserInvites string `ini:"user_invites"`
// Defaults
DefaultVisibility string `ini:"default_visibility"`
// Check for Updates
UpdateChecks bool `ini:"update_checks"`
// Disable password authentication if use only Oauth
DisablePasswordAuth bool `ini:"disable_password_auth"`
}
EmailCfg struct {
Domain string `ini:"domain"`
MailgunPrivate string `ini:"mailgun_private"`
}
// Config holds the complete configuration for running a writefreely instance
Config struct {
Server ServerCfg `ini:"server"`
Database DatabaseCfg `ini:"database"`
App AppCfg `ini:"app"`
Server ServerCfg `ini:"server"`
Database DatabaseCfg `ini:"database"`
App AppCfg `ini:"app"`
Email EmailCfg `ini:"email"`
SlackOauth SlackOauthCfg `ini:"oauth.slack"`
WriteAsOauth WriteAsOauthCfg `ini:"oauth.writeas"`
GitlabOauth GitlabOauthCfg `ini:"oauth.gitlab"`
GiteaOauth GiteaOauthCfg `ini:"oauth.gitea"`
GenericOauth GenericOauthCfg `ini:"oauth.generic"`
}
)
// New creates a new Config with sane defaults
func New() *Config {
return &Config{
c := &Config{
Server: ServerCfg{
Port: 8080,
},
Database: DatabaseCfg{
Type: "mysql",
Host: "localhost",
Port: 3306,
Bind: "localhost", /* IPV6 support when not using localhost? */
},
App: AppCfg{
Host: "http://localhost:8080",
@ -74,10 +207,60 @@ func New() *Config {
PublicStats: true,
},
}
c.UseMySQL(true)
return c
}
func Load() (*Config, error) {
cfg, err := ini.Load(FileName)
// UseMySQL resets the Config's Database to use default values for a MySQL setup.
func (cfg *Config) UseMySQL(fresh bool) {
cfg.Database.Type = "mysql"
if fresh {
cfg.Database.Host = "localhost"
cfg.Database.Port = 3306
}
}
// UseSQLite resets the Config's Database to use default values for a SQLite setup.
func (cfg *Config) UseSQLite(fresh bool) {
cfg.Database.Type = "sqlite3"
if fresh {
cfg.Database.FileName = "writefreely.db"
}
}
// IsSecureStandalone returns whether or not the application is running as a
// standalone server with TLS enabled.
func (cfg *Config) IsSecureStandalone() bool {
return cfg.Server.Port == 443 && cfg.Server.TLSCertPath != "" && cfg.Server.TLSKeyPath != ""
}
func (ac *AppCfg) LandingPath() string {
if !strings.HasPrefix(ac.Landing, "/") {
return "/" + ac.Landing
}
return ac.Landing
}
func (lc EmailCfg) Enabled() bool {
return lc.Domain != "" && lc.MailgunPrivate != ""
}
func (ac AppCfg) SignupPath() string {
if !ac.OpenRegistration {
return ""
}
if ac.Chorus || ac.Private || (ac.Landing != "" && ac.Landing != "/") {
return "/signup"
}
return "/"
}
// Load reads the given configuration file, then parses and returns it as a Config.
func Load(fname string) (*Config, error) {
if fname == "" {
fname = FileName
}
cfg, err := ini.Load(fname)
if err != nil {
return nil, err
}
@ -88,15 +271,35 @@ func Load() (*Config, error) {
if err != nil {
return nil, err
}
// Do any transformations
u, err := url.Parse(uc.App.Host)
if err != nil {
return nil, err
}
d, err := idna.ToASCII(u.Hostname())
if err != nil {
log.Error("idna.ToASCII for %s: %s", u.Hostname(), err)
return nil, err
}
uc.App.Host = u.Scheme + "://" + d
if u.Port() != "" {
uc.App.Host += ":" + u.Port()
}
return uc, nil
}
func Save(uc *Config) error {
// Save writes the given Config to the given file.
func Save(uc *Config, fname string) error {
cfg := ini.Empty()
err := ini.ReflectFrom(cfg, uc)
if err != nil {
return err
}
return cfg.SaveTo(FileName)
if fname == "" {
fname = FileName
}
return cfg.SaveTo(fname)
}

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018 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 config
type UserCreation struct {

View File

@ -1,12 +1,44 @@
/*
* Copyright © 2018, 2020-2021 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 config
import (
"github.com/writeas/web-core/log"
"golang.org/x/net/idna"
"net/http"
"net/url"
"strings"
"time"
)
// FriendlyHost returns the app's Host sans any schema
func (ac AppCfg) FriendlyHost() string {
return ac.Host[strings.Index(ac.Host, "://")+len("://"):]
rawHost := ac.Host[strings.Index(ac.Host, "://")+len("://"):]
u, err := url.Parse(ac.Host)
if err != nil {
log.Error("url.Parse failed on %s: %s", ac.Host, err)
return rawHost
}
d, err := idna.ToUnicode(u.Hostname())
if err != nil {
log.Error("idna.ToUnicode failed on %s: %s", ac.Host, err)
return rawHost
}
res := d
if u.Port() != "" {
res += ":" + u.Port()
}
return res
}
func (ac AppCfg) CanCreateBlogs(currentlyUsed uint64) bool {
@ -15,3 +47,16 @@ func (ac AppCfg) CanCreateBlogs(currentlyUsed uint64) bool {
}
return int(currentlyUsed) < ac.MaxBlogs
}
// OrDefaultString returns input or a default value if input is empty.
func OrDefaultString(input, defaultValue string) string {
if len(input) == 0 {
return defaultValue
}
return input
}
// DefaultHTTPClient returns a sane default HTTP client.
func DefaultHTTPClient() *http.Client {
return &http.Client{Timeout: 10 * time.Second}
}

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018 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 config
import (
@ -7,6 +17,7 @@ import (
"github.com/mitchellh/go-wordwrap"
"github.com/writeas/web-core/auth"
"strconv"
"strings"
)
type SetupData struct {
@ -14,242 +25,358 @@ type SetupData struct {
Config *Config
}
func Configure() (*SetupData, error) {
func Configure(fname string, configSections string) (*SetupData, error) {
data := &SetupData{}
var err error
if fname == "" {
fname = FileName
}
data.Config, err = Load()
data.Config, err = Load(fname)
var action string
isNewCfg := false
if err != nil {
fmt.Println("No configuration yet. Creating new.")
fmt.Printf("No %s configuration yet. Creating new.\n", fname)
data.Config = New()
action = "generate"
isNewCfg = true
} else {
fmt.Println("Configuration loaded.")
fmt.Printf("Loaded configuration %s.\n", fname)
action = "update"
}
title := color.New(color.Bold, color.BgGreen).PrintlnFunc()
title := color.New(color.Bold, color.BgGreen).PrintFunc()
intro := color.New(color.Bold, color.FgWhite).PrintlnFunc()
fmt.Println()
intro(" ✍ Write Freely Configuration ✍")
intro(" ✍ WriteFreely Configuration ✍")
fmt.Println()
fmt.Println(wordwrap.WrapString(" This quick configuration process will "+action+" the application's config file, "+FileName+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75))
fmt.Println()
title(" Server setup ")
fmt.Println(wordwrap.WrapString(" This quick configuration process will "+action+" the application's config file, "+fname+".\n\n It validates your input along the way, so you can be sure any future errors aren't caused by a bad configuration. If you'd rather configure your server manually, instead run: writefreely --create-config and edit that file.", 75))
fmt.Println()
tmpls := &promptui.PromptTemplates{
Success: "{{ . | bold | faint }}: ",
}
selTmpls := &promptui.SelectTemplates{
Selected: fmt.Sprintf(`{{.Label}} {{ . | faint }}`),
Selected: `{{.Label}} {{ . | faint }}`,
}
prompt := promptui.Prompt{
Templates: tmpls,
Label: "Local port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Server.Port),
}
port, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number
var selPrompt promptui.Select
var prompt promptui.Prompt
fmt.Println()
title(" Database setup ")
fmt.Println()
if strings.Contains(configSections, "server") {
title(" Server setup ")
fmt.Println()
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Username",
Validate: validateNonEmpty,
Default: data.Config.Database.User,
}
data.Config.Database.User, err = prompt.Run()
if err != nil {
return data, err
// Environment selection
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Environment",
Items: []string{"Development", "Production, standalone", "Production, behind reverse proxy"},
}
_, envType, err := selPrompt.Run()
if err != nil {
return data, err
}
isDevEnv := envType == "Development"
isStandalone := envType == "Production, standalone"
data.Config.Server.Dev = isDevEnv
if isDevEnv || !isStandalone {
// Running in dev environment or behind reverse proxy; ask for port
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Local port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Server.Port),
}
port, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.Server.Port, _ = strconv.Atoi(port) // Ignore error, as we've already validated number
}
if isStandalone {
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Web server mode",
Items: []string{"Insecure (port 80)", "Secure (port 443), manual certificate", "Secure (port 443), auto certificate"},
}
sel, _, err := selPrompt.Run()
if err != nil {
return data, err
}
if sel == 0 {
data.Config.Server.Autocert = false
data.Config.Server.Port = 80
data.Config.Server.TLSCertPath = ""
data.Config.Server.TLSKeyPath = ""
} else if sel == 1 || sel == 2 {
data.Config.Server.Port = 443
data.Config.Server.Autocert = sel == 2
if sel == 1 {
// Manual certificate configuration
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Certificate path",
Validate: validateNonEmpty,
Default: data.Config.Server.TLSCertPath,
}
data.Config.Server.TLSCertPath, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Key path",
Validate: validateNonEmpty,
Default: data.Config.Server.TLSKeyPath,
}
data.Config.Server.TLSKeyPath, err = prompt.Run()
if err != nil {
return data, err
}
} else {
// Automatic certificate
data.Config.Server.TLSCertPath = "certs"
data.Config.Server.TLSKeyPath = "certs"
}
}
} else {
data.Config.Server.TLSCertPath = ""
data.Config.Server.TLSKeyPath = ""
}
fmt.Println()
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Password",
Validate: validateNonEmpty,
Default: data.Config.Database.Password,
Mask: '*',
}
data.Config.Database.Password, err = prompt.Run()
if err != nil {
return data, err
if strings.Contains(configSections, "db") {
title(" Database setup ")
fmt.Println()
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Database driver",
Items: []string{"MySQL", "SQLite"},
}
sel, _, err := selPrompt.Run()
if err != nil {
return data, err
}
if sel == 0 {
// Configure for MySQL
data.Config.UseMySQL(isNewCfg)
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Username",
Validate: validateNonEmpty,
Default: data.Config.Database.User,
}
data.Config.Database.User, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Password",
Validate: validateNonEmpty,
Default: data.Config.Database.Password,
Mask: '*',
}
data.Config.Database.Password, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Database name",
Validate: validateNonEmpty,
Default: data.Config.Database.Database,
}
data.Config.Database.Database, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Host",
Validate: validateNonEmpty,
Default: data.Config.Database.Host,
}
data.Config.Database.Host, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Database.Port),
}
dbPort, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number
} else if sel == 1 {
// Configure for SQLite
data.Config.UseSQLite(isNewCfg)
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Filename",
Validate: validateNonEmpty,
Default: data.Config.Database.FileName,
}
data.Config.Database.FileName, err = prompt.Run()
if err != nil {
return data, err
}
}
fmt.Println()
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Database name",
Validate: validateNonEmpty,
Default: data.Config.Database.Database,
}
data.Config.Database.Database, err = prompt.Run()
if err != nil {
return data, err
}
if strings.Contains(configSections, "app") {
title(" App setup ")
fmt.Println()
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Host",
Validate: validateNonEmpty,
Default: data.Config.Database.Host,
}
data.Config.Database.Host, err = prompt.Run()
if err != nil {
return data, err
}
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Site type",
Items: []string{"Single user blog", "Multi-user instance"},
}
_, usersType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.SingleUser = usersType == "Single user blog"
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Port",
Validate: validatePort,
Default: fmt.Sprintf("%d", data.Config.Database.Port),
}
dbPort, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.Database.Port, _ = strconv.Atoi(dbPort) // Ignore error, as we've already validated number
if data.Config.App.SingleUser {
data.User = &UserCreation{}
fmt.Println()
title(" App setup ")
fmt.Println()
// prompt for username
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Admin username",
Validate: validateNonEmpty,
}
data.User.Username, err = prompt.Run()
if err != nil {
return data, err
}
selPrompt := promptui.Select{
Templates: selTmpls,
Label: "Site type",
Items: []string{"Single user blog", "Multi-user instance"},
}
_, usersType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.SingleUser = usersType == "Single user blog"
// prompt for password
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Admin password",
Validate: validateNonEmpty,
}
newUserPass, err := prompt.Run()
if err != nil {
return data, err
}
if data.Config.App.SingleUser {
data.User = &UserCreation{}
data.User.HashedPass, err = auth.HashPass([]byte(newUserPass))
if err != nil {
return data, err
}
}
// prompt for username
siteNameLabel := "Instance name"
if data.Config.App.SingleUser {
siteNameLabel = "Blog name"
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Admin username",
Label: siteNameLabel,
Validate: validateNonEmpty,
Default: data.Config.App.SiteName,
}
data.User.Username, err = prompt.Run()
data.Config.App.SiteName, err = prompt.Run()
if err != nil {
return data, err
}
// prompt for password
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Admin password",
Validate: validateNonEmpty,
}
newUserPass, err := prompt.Run()
if err != nil {
return data, err
}
data.User.HashedPass, err = auth.HashPass([]byte(newUserPass))
if err != nil {
return data, err
}
}
siteNameLabel := "Instance name"
if data.Config.App.SingleUser {
siteNameLabel = "Blog name"
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: siteNameLabel,
Validate: validateNonEmpty,
Default: data.Config.App.SiteName,
}
data.Config.App.SiteName, err = prompt.Run()
if err != nil {
return data, err
}
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Public URL",
Validate: validateDomain,
Default: data.Config.App.Host,
}
data.Config.App.Host, err = prompt.Run()
if err != nil {
return data, err
}
if !data.Config.App.SingleUser {
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Registration",
Items: []string{"Open", "Closed"},
}
_, regType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.OpenRegistration = regType == "Open"
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Max blogs per user",
Default: fmt.Sprintf("%d", data.Config.App.MaxBlogs),
Label: "Public URL",
Validate: validateDomain,
Default: data.Config.App.Host,
}
maxBlogs, err := prompt.Run()
data.Config.App.Host, err = prompt.Run()
if err != nil {
return data, err
}
data.Config.App.MaxBlogs, _ = strconv.Atoi(maxBlogs) // Ignore error, as we've already validated number
}
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Federation",
Items: []string{"Enabled", "Disabled"},
}
_, fedType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.Federation = fedType == "Enabled"
if !data.Config.App.SingleUser {
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Registration",
Items: []string{"Open", "Closed"},
}
_, regType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.OpenRegistration = regType == "Open"
if data.Config.App.Federation {
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Federation usage stats",
Items: []string{"Public", "Private"},
prompt = promptui.Prompt{
Templates: tmpls,
Label: "Max blogs per user",
Default: fmt.Sprintf("%d", data.Config.App.MaxBlogs),
}
maxBlogs, err := prompt.Run()
if err != nil {
return data, err
}
data.Config.App.MaxBlogs, _ = strconv.Atoi(maxBlogs) // Ignore error, as we've already validated number
}
_, fedStatsType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.PublicStats = fedStatsType == "Public"
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Instance metadata privacy",
Items: []string{"Public", "Private"},
Label: "Federation",
Items: []string{"Enabled", "Disabled"},
}
_, fedStatsType, err = selPrompt.Run()
_, fedType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.Private = fedStatsType == "Private"
data.Config.App.Federation = fedType == "Enabled"
if data.Config.App.Federation {
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Usage stats (active users, posts)",
Items: []string{"Public", "Private"},
}
_, fedStatsType, err := selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.PublicStats = fedStatsType == "Public"
selPrompt = promptui.Select{
Templates: selTmpls,
Label: "Instance metadata privacy",
Items: []string{"Public", "Private"},
}
_, fedStatsType, err = selPrompt.Run()
if err != nil {
return data, err
}
data.Config.App.Private = fedStatsType == "Private"
}
}
return data, Save(data.Config)
return data, Save(data.Config, fname)
}

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018 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 config
import (

29
database-lib.go Normal file
View File

@ -0,0 +1,29 @@
//go:build wflib
// +build wflib
/*
* Copyright © 2019-2020 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.
*/
// This file contains dummy database funcs for when writefreely is used as a
// library.
package writefreely
func (db *datastore) isDuplicateKeyErr(err error) bool {
return false
}
func (db *datastore) isIgnorableError(err error) bool {
return false
}
func (db *datastore) isHighLoadError(err error) bool {
return false
}

53
database-no-sqlite.go Normal file
View File

@ -0,0 +1,53 @@
//go:build !sqlite && !wflib
// +build !sqlite,!wflib
/*
* Copyright © 2019-2020 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 (
"github.com/go-sql-driver/mysql"
"github.com/writeas/web-core/log"
)
func (db *datastore) isDuplicateKeyErr(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrDuplicateKey
}
} else {
log.Error("isDuplicateKeyErr: failed check for unrecognized driver '%s'", db.driverName)
}
return false
}
func (db *datastore) isIgnorableError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrCollationMix
}
} else {
log.Error("isIgnorableError: failed check for unrecognized driver '%s'", db.driverName)
}
return false
}
func (db *datastore) isHighLoadError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrMaxUserConns || mysqlErr.Number == mySQLErrTooManyConns
}
}
return false
}

73
database-sqlite.go Normal file
View File

@ -0,0 +1,73 @@
//go:build sqlite && !wflib
// +build sqlite,!wflib
/*
* Copyright © 2019-2020 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"
"github.com/go-sql-driver/mysql"
"github.com/mattn/go-sqlite3"
"github.com/writeas/web-core/log"
"regexp"
)
func init() {
SQLiteEnabled = true
regex := func(re, s string) (bool, error) {
return regexp.MatchString(re, s)
}
sql.Register("sqlite3_with_regex", &sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
return conn.RegisterFunc("regexp", regex, true)
},
})
}
func (db *datastore) isDuplicateKeyErr(err error) bool {
if db.driverName == driverSQLite {
if err, ok := err.(sqlite3.Error); ok {
return err.Code == sqlite3.ErrConstraint
}
} else if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrDuplicateKey
}
} else {
log.Error("isDuplicateKeyErr: failed check for unrecognized driver '%s'", db.driverName)
}
return false
}
func (db *datastore) isIgnorableError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrCollationMix
}
} else {
log.Error("isIgnorableError: failed check for unrecognized driver '%s'", db.driverName)
}
return false
}
func (db *datastore) isHighLoadError(err error) bool {
if db.driverName == driverMySQL {
if mysqlErr, ok := err.(*mysql.MySQLError); ok {
return mysqlErr.Number == mySQLErrMaxUserConns || mysqlErr.Number == mySQLErrTooManyConns
}
}
return false
}

File diff suppressed because it is too large Load Diff

50
database_test.go Normal file
View File

@ -0,0 +1,50 @@
package writefreely
import (
"context"
"database/sql"
"github.com/stretchr/testify/assert"
"testing"
)
func TestOAuthDatastore(t *testing.T) {
if !runMySQLTests() {
t.Skip("skipping mysql tests")
}
withTestDB(t, func(db *sql.DB) {
ctx := context.Background()
ds := &datastore{
DB: db,
driverName: "",
}
state, err := ds.GenerateOAuthState(ctx, "test", "development", 0, "")
assert.NoError(t, err)
assert.Len(t, state, 24)
countRows(t, ctx, db, 1, "SELECT COUNT(*) FROM `oauth_client_states` WHERE `state` = ? AND `used` = false", state)
_, _, _, _, err = ds.ValidateOAuthState(ctx, state)
assert.NoError(t, err)
countRows(t, ctx, db, 1, "SELECT COUNT(*) FROM `oauth_client_states` WHERE `state` = ? AND `used` = true", state)
var localUserID int64 = 99
var remoteUserID = "100"
err = ds.RecordRemoteUserID(ctx, localUserID, remoteUserID, "test", "test", "access_token_a")
assert.NoError(t, err)
countRows(t, ctx, db, 1, "SELECT COUNT(*) FROM `oauth_users` WHERE `user_id` = ? AND `remote_user_id` = ? AND access_token = 'access_token_a'", localUserID, remoteUserID)
err = ds.RecordRemoteUserID(ctx, localUserID, remoteUserID, "test", "test", "access_token_b")
assert.NoError(t, err)
countRows(t, ctx, db, 1, "SELECT COUNT(*) FROM `oauth_users` WHERE `user_id` = ? AND `remote_user_id` = ? AND access_token = 'access_token_b'", localUserID, remoteUserID)
countRows(t, ctx, db, 1, "SELECT COUNT(*) FROM `oauth_users`")
foundUserID, err := ds.GetIDForRemoteUser(ctx, remoteUserID, "test", "test")
assert.NoError(t, err)
assert.Equal(t, localUserID, foundUserID)
})
}

52
db/alter.go Normal file
View File

@ -0,0 +1,52 @@
package db
import (
"fmt"
"strings"
)
type AlterTableSqlBuilder struct {
Dialect DialectType
Name string
Changes []string
}
func (b *AlterTableSqlBuilder) AddColumn(col *Column) *AlterTableSqlBuilder {
if colVal, err := col.String(); err == nil {
b.Changes = append(b.Changes, fmt.Sprintf("ADD COLUMN %s", colVal))
}
return b
}
func (b *AlterTableSqlBuilder) ChangeColumn(name string, col *Column) *AlterTableSqlBuilder {
if colVal, err := col.String(); err == nil {
b.Changes = append(b.Changes, fmt.Sprintf("CHANGE COLUMN %s %s", name, colVal))
}
return b
}
func (b *AlterTableSqlBuilder) AddUniqueConstraint(name string, columns ...string) *AlterTableSqlBuilder {
b.Changes = append(b.Changes, fmt.Sprintf("ADD CONSTRAINT %s UNIQUE (%s)", name, strings.Join(columns, ", ")))
return b
}
func (b *AlterTableSqlBuilder) ToSQL() (string, error) {
var str strings.Builder
str.WriteString("ALTER TABLE ")
str.WriteString(b.Name)
str.WriteString(" ")
if len(b.Changes) == 0 {
return "", fmt.Errorf("no changes provide for table: %s", b.Name)
}
changeCount := len(b.Changes)
for i, thing := range b.Changes {
str.WriteString(thing)
if i < changeCount-1 {
str.WriteString(", ")
}
}
return str.String(), nil
}

56
db/alter_test.go Normal file
View File

@ -0,0 +1,56 @@
package db
import "testing"
func TestAlterTableSqlBuilder_ToSQL(t *testing.T) {
type fields struct {
Dialect DialectType
Name string
Changes []string
}
tests := []struct {
name string
builder *AlterTableSqlBuilder
want string
wantErr bool
}{
{
name: "MySQL add int",
builder: DialectMySQL.
AlterTable("the_table").
AddColumn(DialectMySQL.Column("the_col", ColumnTypeInteger, UnsetSize)),
want: "ALTER TABLE the_table ADD COLUMN the_col INT NOT NULL",
wantErr: false,
},
{
name: "MySQL add string",
builder: DialectMySQL.
AlterTable("the_table").
AddColumn(DialectMySQL.Column("the_col", ColumnTypeVarChar, OptionalInt{true, 128})),
want: "ALTER TABLE the_table ADD COLUMN the_col VARCHAR(128) NOT NULL",
wantErr: false,
},
{
name: "MySQL add int and string",
builder: DialectMySQL.
AlterTable("the_table").
AddColumn(DialectMySQL.Column("first_col", ColumnTypeInteger, UnsetSize)).
AddColumn(DialectMySQL.Column("second_col", ColumnTypeVarChar, OptionalInt{true, 128})),
want: "ALTER TABLE the_table ADD COLUMN first_col INT NOT NULL, ADD COLUMN second_col VARCHAR(128) NOT NULL",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.builder.ToSQL()
if (err != nil) != tt.wantErr {
t.Errorf("ToSQL() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ToSQL() got = %v, want %v", got, tt.want)
}
})
}
}

263
db/create.go Normal file
View File

@ -0,0 +1,263 @@
/*
* Copyright © 2019-2020 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 db
import (
"fmt"
"strings"
)
type ColumnType int
type OptionalInt struct {
Set bool
Value int
}
type OptionalString struct {
Set bool
Value string
}
type SQLBuilder interface {
ToSQL() (string, error)
}
type Column struct {
Dialect DialectType
Name string
Nullable bool
Default OptionalString
Type ColumnType
Size OptionalInt
PrimaryKey bool
}
type CreateTableSqlBuilder struct {
Dialect DialectType
Name string
IfNotExists bool
ColumnOrder []string
Columns map[string]*Column
Constraints []string
}
const (
ColumnTypeBool ColumnType = iota
ColumnTypeSmallInt ColumnType = iota
ColumnTypeInteger ColumnType = iota
ColumnTypeChar ColumnType = iota
ColumnTypeVarChar ColumnType = iota
ColumnTypeText ColumnType = iota
ColumnTypeDateTime ColumnType = iota
)
var _ SQLBuilder = &CreateTableSqlBuilder{}
var UnsetSize OptionalInt = OptionalInt{Set: false, Value: 0}
var UnsetDefault OptionalString = OptionalString{Set: false, Value: ""}
func (d ColumnType) Format(dialect DialectType, size OptionalInt) (string, error) {
if dialect != DialectMySQL && dialect != DialectSQLite {
return "", fmt.Errorf("unsupported column type %d for dialect %d and size %v", d, dialect, size)
}
switch d {
case ColumnTypeSmallInt:
{
if dialect == DialectSQLite {
return "INTEGER", nil
}
mod := ""
if size.Set {
mod = fmt.Sprintf("(%d)", size.Value)
}
return "SMALLINT" + mod, nil
}
case ColumnTypeInteger:
{
if dialect == DialectSQLite {
return "INTEGER", nil
}
mod := ""
if size.Set {
mod = fmt.Sprintf("(%d)", size.Value)
}
return "INT" + mod, nil
}
case ColumnTypeChar:
{
if dialect == DialectSQLite {
return "TEXT", nil
}
mod := ""
if size.Set {
mod = fmt.Sprintf("(%d)", size.Value)
}
return "CHAR" + mod, nil
}
case ColumnTypeVarChar:
{
if dialect == DialectSQLite {
return "TEXT", nil
}
mod := ""
if size.Set {
mod = fmt.Sprintf("(%d)", size.Value)
}
return "VARCHAR" + mod, nil
}
case ColumnTypeBool:
{
if dialect == DialectSQLite {
return "INTEGER", nil
}
return "TINYINT(1)", nil
}
case ColumnTypeDateTime:
return "DATETIME", nil
case ColumnTypeText:
return "TEXT", nil
}
return "", fmt.Errorf("unsupported column type %d for dialect %d and size %v", d, dialect, size)
}
func (c *Column) SetName(name string) *Column {
c.Name = name
return c
}
func (c *Column) SetNullable(nullable bool) *Column {
c.Nullable = nullable
return c
}
func (c *Column) SetPrimaryKey(pk bool) *Column {
c.PrimaryKey = pk
return c
}
func (c *Column) SetDefault(value string) *Column {
c.Default = OptionalString{Set: true, Value: value}
return c
}
func (c *Column) SetDefaultCurrentTimestamp() *Column {
def := "NOW()"
if c.Dialect == DialectSQLite {
def = "CURRENT_TIMESTAMP"
}
c.Default = OptionalString{Set: true, Value: def}
return c
}
func (c *Column) SetType(t ColumnType) *Column {
c.Type = t
return c
}
func (c *Column) SetSize(size int) *Column {
c.Size = OptionalInt{Set: true, Value: size}
return c
}
func (c *Column) String() (string, error) {
var str strings.Builder
str.WriteString(c.Name)
str.WriteString(" ")
typeStr, err := c.Type.Format(c.Dialect, c.Size)
if err != nil {
return "", err
}
str.WriteString(typeStr)
if !c.Nullable {
str.WriteString(" NOT NULL")
}
if c.Default.Set {
str.WriteString(" DEFAULT ")
val := c.Default.Value
if val == "" {
val = "''"
}
str.WriteString(val)
}
if c.PrimaryKey {
str.WriteString(" PRIMARY KEY")
}
return str.String(), nil
}
func (b *CreateTableSqlBuilder) Column(column *Column) *CreateTableSqlBuilder {
if b.Columns == nil {
b.Columns = make(map[string]*Column)
}
b.Columns[column.Name] = column
b.ColumnOrder = append(b.ColumnOrder, column.Name)
return b
}
func (b *CreateTableSqlBuilder) UniqueConstraint(columns ...string) *CreateTableSqlBuilder {
for _, column := range columns {
if _, ok := b.Columns[column]; !ok {
// This fails silently.
return b
}
}
b.Constraints = append(b.Constraints, fmt.Sprintf("UNIQUE(%s)", strings.Join(columns, ",")))
return b
}
func (b *CreateTableSqlBuilder) SetIfNotExists(ine bool) *CreateTableSqlBuilder {
b.IfNotExists = ine
return b
}
func (b *CreateTableSqlBuilder) ToSQL() (string, error) {
var str strings.Builder
str.WriteString("CREATE TABLE ")
if b.IfNotExists {
str.WriteString("IF NOT EXISTS ")
}
str.WriteString(b.Name)
var things []string
for _, columnName := range b.ColumnOrder {
column, ok := b.Columns[columnName]
if !ok {
return "", fmt.Errorf("column not found: %s", columnName)
}
columnStr, err := column.String()
if err != nil {
return "", err
}
things = append(things, columnStr)
}
things = append(things, b.Constraints...)
if thingLen := len(things); thingLen > 0 {
str.WriteString(" ( ")
for i, thing := range things {
str.WriteString(thing)
if i < thingLen-1 {
str.WriteString(", ")
}
}
str.WriteString(" )")
}
return str.String(), nil
}

146
db/create_test.go Normal file
View File

@ -0,0 +1,146 @@
package db
import (
"github.com/stretchr/testify/assert"
"testing"
)
func TestDialect_Column(t *testing.T) {
c1 := DialectSQLite.Column("foo", ColumnTypeBool, UnsetSize)
assert.Equal(t, DialectSQLite, c1.Dialect)
c2 := DialectMySQL.Column("foo", ColumnTypeBool, UnsetSize)
assert.Equal(t, DialectMySQL, c2.Dialect)
}
func TestColumnType_Format(t *testing.T) {
type args struct {
dialect DialectType
size OptionalInt
}
tests := []struct {
name string
d ColumnType
args args
want string
wantErr bool
}{
{"Sqlite bool", ColumnTypeBool, args{dialect: DialectSQLite}, "INTEGER", false},
{"Sqlite small int", ColumnTypeSmallInt, args{dialect: DialectSQLite}, "INTEGER", false},
{"Sqlite int", ColumnTypeInteger, args{dialect: DialectSQLite}, "INTEGER", false},
{"Sqlite char", ColumnTypeChar, args{dialect: DialectSQLite}, "TEXT", false},
{"Sqlite varchar", ColumnTypeVarChar, args{dialect: DialectSQLite}, "TEXT", false},
{"Sqlite text", ColumnTypeText, args{dialect: DialectSQLite}, "TEXT", false},
{"Sqlite datetime", ColumnTypeDateTime, args{dialect: DialectSQLite}, "DATETIME", false},
{"MySQL bool", ColumnTypeBool, args{dialect: DialectMySQL}, "TINYINT(1)", false},
{"MySQL small int", ColumnTypeSmallInt, args{dialect: DialectMySQL}, "SMALLINT", false},
{"MySQL small int with param", ColumnTypeSmallInt, args{dialect: DialectMySQL, size: OptionalInt{true, 3}}, "SMALLINT(3)", false},
{"MySQL int", ColumnTypeInteger, args{dialect: DialectMySQL}, "INT", false},
{"MySQL int with param", ColumnTypeInteger, args{dialect: DialectMySQL, size: OptionalInt{true, 11}}, "INT(11)", false},
{"MySQL char", ColumnTypeChar, args{dialect: DialectMySQL}, "CHAR", false},
{"MySQL char with param", ColumnTypeChar, args{dialect: DialectMySQL, size: OptionalInt{true, 4}}, "CHAR(4)", false},
{"MySQL varchar", ColumnTypeVarChar, args{dialect: DialectMySQL}, "VARCHAR", false},
{"MySQL varchar with param", ColumnTypeVarChar, args{dialect: DialectMySQL, size: OptionalInt{true, 25}}, "VARCHAR(25)", false},
{"MySQL text", ColumnTypeText, args{dialect: DialectMySQL}, "TEXT", false},
{"MySQL datetime", ColumnTypeDateTime, args{dialect: DialectMySQL}, "DATETIME", false},
{"invalid column type", 10000, args{dialect: DialectMySQL}, "", true},
{"invalid dialect", ColumnTypeBool, args{dialect: 10000}, "", true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.d.Format(tt.args.dialect, tt.args.size)
if (err != nil) != tt.wantErr {
t.Errorf("Format() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Format() got = %v, want %v", got, tt.want)
}
})
}
}
func TestColumn_Build(t *testing.T) {
type fields struct {
Dialect DialectType
Name string
Nullable bool
Default OptionalString
Type ColumnType
Size OptionalInt
PrimaryKey bool
}
tests := []struct {
name string
fields fields
want string
wantErr bool
}{
{"Sqlite bool", fields{DialectSQLite, "foo", false, UnsetDefault, ColumnTypeBool, UnsetSize, false}, "foo INTEGER NOT NULL", false},
{"Sqlite bool nullable", fields{DialectSQLite, "foo", true, UnsetDefault, ColumnTypeBool, UnsetSize, false}, "foo INTEGER", false},
{"Sqlite small int", fields{DialectSQLite, "foo", false, UnsetDefault, ColumnTypeSmallInt, UnsetSize, true}, "foo INTEGER NOT NULL PRIMARY KEY", false},
{"Sqlite small int nullable", fields{DialectSQLite, "foo", true, UnsetDefault, ColumnTypeSmallInt, UnsetSize, false}, "foo INTEGER", false},
{"Sqlite int", fields{DialectSQLite, "foo", false, UnsetDefault, ColumnTypeInteger, UnsetSize, false}, "foo INTEGER NOT NULL", false},
{"Sqlite int nullable", fields{DialectSQLite, "foo", true, UnsetDefault, ColumnTypeInteger, UnsetSize, false}, "foo INTEGER", false},
{"Sqlite char", fields{DialectSQLite, "foo", false, UnsetDefault, ColumnTypeChar, UnsetSize, false}, "foo TEXT NOT NULL", false},
{"Sqlite char nullable", fields{DialectSQLite, "foo", true, UnsetDefault, ColumnTypeChar, UnsetSize, false}, "foo TEXT", false},
{"Sqlite varchar", fields{DialectSQLite, "foo", false, UnsetDefault, ColumnTypeVarChar, UnsetSize, false}, "foo TEXT NOT NULL", false},
{"Sqlite varchar nullable", fields{DialectSQLite, "foo", true, UnsetDefault, ColumnTypeVarChar, UnsetSize, false}, "foo TEXT", false},
{"Sqlite text", fields{DialectSQLite, "foo", false, UnsetDefault, ColumnTypeText, UnsetSize, false}, "foo TEXT NOT NULL", false},
{"Sqlite text nullable", fields{DialectSQLite, "foo", true, UnsetDefault, ColumnTypeText, UnsetSize, false}, "foo TEXT", false},
{"Sqlite datetime", fields{DialectSQLite, "foo", false, UnsetDefault, ColumnTypeDateTime, UnsetSize, false}, "foo DATETIME NOT NULL", false},
{"Sqlite datetime nullable", fields{DialectSQLite, "foo", true, UnsetDefault, ColumnTypeDateTime, UnsetSize, false}, "foo DATETIME", false},
{"MySQL bool", fields{DialectMySQL, "foo", false, UnsetDefault, ColumnTypeBool, UnsetSize, false}, "foo TINYINT(1) NOT NULL", false},
{"MySQL bool nullable", fields{DialectMySQL, "foo", true, UnsetDefault, ColumnTypeBool, UnsetSize, false}, "foo TINYINT(1)", false},
{"MySQL small int", fields{DialectMySQL, "foo", false, UnsetDefault, ColumnTypeSmallInt, UnsetSize, true}, "foo SMALLINT NOT NULL PRIMARY KEY", false},
{"MySQL small int nullable", fields{DialectMySQL, "foo", true, UnsetDefault, ColumnTypeSmallInt, UnsetSize, false}, "foo SMALLINT", false},
{"MySQL int", fields{DialectMySQL, "foo", false, UnsetDefault, ColumnTypeInteger, UnsetSize, false}, "foo INT NOT NULL", false},
{"MySQL int nullable", fields{DialectMySQL, "foo", true, UnsetDefault, ColumnTypeInteger, UnsetSize, false}, "foo INT", false},
{"MySQL char", fields{DialectMySQL, "foo", false, UnsetDefault, ColumnTypeChar, UnsetSize, false}, "foo CHAR NOT NULL", false},
{"MySQL char nullable", fields{DialectMySQL, "foo", true, UnsetDefault, ColumnTypeChar, UnsetSize, false}, "foo CHAR", false},
{"MySQL varchar", fields{DialectMySQL, "foo", false, UnsetDefault, ColumnTypeVarChar, UnsetSize, false}, "foo VARCHAR NOT NULL", false},
{"MySQL varchar nullable", fields{DialectMySQL, "foo", true, UnsetDefault, ColumnTypeVarChar, UnsetSize, false}, "foo VARCHAR", false},
{"MySQL text", fields{DialectMySQL, "foo", false, UnsetDefault, ColumnTypeText, UnsetSize, false}, "foo TEXT NOT NULL", false},
{"MySQL text nullable", fields{DialectMySQL, "foo", true, UnsetDefault, ColumnTypeText, UnsetSize, false}, "foo TEXT", false},
{"MySQL datetime", fields{DialectMySQL, "foo", false, UnsetDefault, ColumnTypeDateTime, UnsetSize, false}, "foo DATETIME NOT NULL", false},
{"MySQL datetime nullable", fields{DialectMySQL, "foo", true, UnsetDefault, ColumnTypeDateTime, UnsetSize, false}, "foo DATETIME", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Column{
Dialect: tt.fields.Dialect,
Name: tt.fields.Name,
Nullable: tt.fields.Nullable,
Default: tt.fields.Default,
Type: tt.fields.Type,
Size: tt.fields.Size,
PrimaryKey: tt.fields.PrimaryKey,
}
if got, err := c.String(); got != tt.want {
if (err != nil) != tt.wantErr {
t.Errorf("String() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("String() got = %v, want %v", got, tt.want)
}
}
})
}
}
func TestCreateTableSqlBuilder_ToSQL(t *testing.T) {
sql, err := DialectMySQL.
Table("foo").
SetIfNotExists(true).
Column(DialectMySQL.Column("bar", ColumnTypeInteger, UnsetSize).SetPrimaryKey(true)).
Column(DialectMySQL.Column("baz", ColumnTypeText, UnsetSize)).
Column(DialectMySQL.Column("qux", ColumnTypeDateTime, UnsetSize).SetDefault("NOW()")).
UniqueConstraint("bar").
UniqueConstraint("bar", "baz").
ToSQL()
assert.NoError(t, err)
assert.Equal(t, "CREATE TABLE IF NOT EXISTS foo ( bar INT NOT NULL PRIMARY KEY, baz TEXT NOT NULL, qux DATETIME NOT NULL DEFAULT NOW(), UNIQUE(bar), UNIQUE(bar,baz) )", sql)
}

76
db/dialect.go Normal file
View File

@ -0,0 +1,76 @@
package db
import "fmt"
type DialectType int
const (
DialectSQLite DialectType = iota
DialectMySQL DialectType = iota
)
func (d DialectType) Column(name string, t ColumnType, size OptionalInt) *Column {
switch d {
case DialectSQLite:
return &Column{Dialect: DialectSQLite, Name: name, Type: t, Size: size}
case DialectMySQL:
return &Column{Dialect: DialectMySQL, Name: name, Type: t, Size: size}
default:
panic(fmt.Sprintf("unexpected dialect: %d", d))
}
}
func (d DialectType) Table(name string) *CreateTableSqlBuilder {
switch d {
case DialectSQLite:
return &CreateTableSqlBuilder{Dialect: DialectSQLite, Name: name}
case DialectMySQL:
return &CreateTableSqlBuilder{Dialect: DialectMySQL, Name: name}
default:
panic(fmt.Sprintf("unexpected dialect: %d", d))
}
}
func (d DialectType) AlterTable(name string) *AlterTableSqlBuilder {
switch d {
case DialectSQLite:
return &AlterTableSqlBuilder{Dialect: DialectSQLite, Name: name}
case DialectMySQL:
return &AlterTableSqlBuilder{Dialect: DialectMySQL, Name: name}
default:
panic(fmt.Sprintf("unexpected dialect: %d", d))
}
}
func (d DialectType) CreateUniqueIndex(name, table string, columns ...string) *CreateIndexSqlBuilder {
switch d {
case DialectSQLite:
return &CreateIndexSqlBuilder{Dialect: DialectSQLite, Name: name, Table: table, Unique: true, Columns: columns}
case DialectMySQL:
return &CreateIndexSqlBuilder{Dialect: DialectMySQL, Name: name, Table: table, Unique: true, Columns: columns}
default:
panic(fmt.Sprintf("unexpected dialect: %d", d))
}
}
func (d DialectType) CreateIndex(name, table string, columns ...string) *CreateIndexSqlBuilder {
switch d {
case DialectSQLite:
return &CreateIndexSqlBuilder{Dialect: DialectSQLite, Name: name, Table: table, Unique: false, Columns: columns}
case DialectMySQL:
return &CreateIndexSqlBuilder{Dialect: DialectMySQL, Name: name, Table: table, Unique: false, Columns: columns}
default:
panic(fmt.Sprintf("unexpected dialect: %d", d))
}
}
func (d DialectType) DropIndex(name, table string) *DropIndexSqlBuilder {
switch d {
case DialectSQLite:
return &DropIndexSqlBuilder{Dialect: DialectSQLite, Name: name, Table: table}
case DialectMySQL:
return &DropIndexSqlBuilder{Dialect: DialectMySQL, Name: name, Table: table}
default:
panic(fmt.Sprintf("unexpected dialect: %d", d))
}
}

53
db/index.go Normal file
View File

@ -0,0 +1,53 @@
package db
import (
"fmt"
"strings"
)
type CreateIndexSqlBuilder struct {
Dialect DialectType
Name string
Table string
Unique bool
Columns []string
}
type DropIndexSqlBuilder struct {
Dialect DialectType
Name string
Table string
}
func (b *CreateIndexSqlBuilder) ToSQL() (string, error) {
var str strings.Builder
str.WriteString("CREATE ")
if b.Unique {
str.WriteString("UNIQUE ")
}
str.WriteString("INDEX ")
str.WriteString(b.Name)
str.WriteString(" on ")
str.WriteString(b.Table)
if len(b.Columns) == 0 {
return "", fmt.Errorf("columns provided for this index: %s", b.Name)
}
str.WriteString(" (")
columnCount := len(b.Columns)
for i, thing := range b.Columns {
str.WriteString(thing)
if i < columnCount-1 {
str.WriteString(", ")
}
}
str.WriteString(")")
return str.String(), nil
}
func (b *DropIndexSqlBuilder) ToSQL() (string, error) {
return fmt.Sprintf("DROP INDEX %s on %s", b.Name, b.Table), nil
}

9
db/raw.go Normal file
View File

@ -0,0 +1,9 @@
package db
type RawSqlBuilder struct {
Query string
}
func (b *RawSqlBuilder) ToSQL() (string, error) {
return b.Query, nil
}

25
db/tx.go Normal file
View File

@ -0,0 +1,25 @@
package db
import (
"context"
"database/sql"
)
// TransactionScopedWork describes code executed within a database transaction.
type TransactionScopedWork func(ctx context.Context, db *sql.Tx) error
// RunTransactionWithOptions executes a block of code within a database transaction.
func RunTransactionWithOptions(ctx context.Context, db *sql.DB, txOpts *sql.TxOptions, txWork TransactionScopedWork) error {
tx, err := db.BeginTx(ctx, txOpts)
if err != nil {
return err
}
if err = txWork(ctx, tx); err != nil {
if txErr := tx.Rollback(); txErr != nil {
return txErr
}
return err
}
return tx.Commit()
}

47
docker-compose.yml Normal file
View File

@ -0,0 +1,47 @@
version: "3"
volumes:
web-keys:
db-data:
networks:
external_writefreely:
internal_writefreely:
internal: true
services:
writefreely-web:
container_name: "writefreely-web"
image: "writeas/writefreely:latest"
volumes:
- "web-keys:/go/keys"
- "./config.ini:/go/config.ini"
networks:
- "internal_writefreely"
- "external_writefreely"
ports:
- "8080:8080"
depends_on:
- "writefreely-db"
restart: unless-stopped
writefreely-db:
container_name: "writefreely-db"
image: "mariadb:latest"
volumes:
- "db-data:/var/lib/mysql/data"
networks:
- "internal_writefreely"
environment:
- MYSQL_DATABASE=writefreely
- MYSQL_ROOT_PASSWORD=changeme
restart: unless-stopped

4
docker-setup.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
docker-compose exec db sh -c 'exec mysql -u root -pchangeme writefreely < /tmp/schema.sql'
docker exec writefreely_web_1 writefreely --gen-keys
docker exec -it writefreely_web_1 writefreely --config

462
email.go Normal file
View File

@ -0,0 +1,462 @@
/*
* Copyright © 2019-2021 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"
"encoding/json"
"fmt"
"html/template"
"net/http"
"strings"
"time"
"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"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/key"
"github.com/writefreely/writefreely/spam"
)
const (
emailSendDelay = 15
)
type (
SubmittedSubscription struct {
CollAlias string
UserID int64
Email string `schema:"email" json:"email"`
Web bool `schema:"web" json:"web"`
Slug string `schema:"slug" json:"slug"`
From string `schema:"from" json:"from"`
}
EmailSubscriber struct {
ID string
CollID int64
UserID sql.NullInt64
Email sql.NullString
Subscribed time.Time
Token string
Confirmed bool
AllowExport bool
acctEmail sql.NullString
}
)
func (es *EmailSubscriber) FinalEmail(keys *key.Keychain) string {
if !es.UserID.Valid || es.Email.Valid {
return es.Email.String
}
decEmail, err := data.Decrypt(keys.EmailKey, []byte(es.acctEmail.String))
if err != nil {
log.Error("Error decrypting user email: %v", err)
return ""
}
return string(decEmail)
}
func (es *EmailSubscriber) SubscribedFriendly() string {
return es.Subscribed.Format("January 2, 2006")
}
func handleCreateEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error {
reqJSON := IsJSON(r)
vars := mux.Vars(r)
var err error
ss := SubmittedSubscription{
CollAlias: vars["alias"],
}
u := getUserSession(app, r)
if u != nil {
ss.UserID = u.ID
}
if reqJSON {
// Decode JSON request
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&ss)
if err != nil {
log.Error("Couldn't parse new subscription JSON request: %v\n", err)
return ErrBadJSON
}
} else {
err = r.ParseForm()
if err != nil {
log.Error("Couldn't parse new subscription form request: %v\n", err)
return ErrBadFormData
}
err = app.formDecoder.Decode(&ss, r.PostForm)
if err != nil {
log.Error("Continuing, but error decoding new subscription form request: %v\n", err)
//return ErrBadFormData
}
}
c, err := app.db.GetCollection(ss.CollAlias)
if err != nil {
log.Error("getCollection: %s", err)
return err
}
c.hostName = app.cfg.App.Host
from := c.CanonicalURL()
isAuthorBanned, err := app.db.IsUserSilenced(c.OwnerID)
if isAuthorBanned {
log.Info("Author is silenced, so subscription is blocked.")
return impart.HTTPError{http.StatusFound, from}
}
if ss.Web {
if u != nil && u.ID == c.OwnerID {
from = "/" + c.Alias + "/"
}
from += ss.Slug
}
if r.FormValue(spam.HoneypotFieldName()) != "" || r.FormValue("fake_password") != "" {
log.Info("Honeypot field was filled out! Not subscribing.")
return impart.HTTPError{http.StatusFound, from}
}
if ss.Email == "" && ss.UserID < 1 {
log.Info("No subscriber data. Not subscribing.")
return impart.HTTPError{http.StatusFound, from}
}
confirmed := app.db.IsSubscriberConfirmed(ss.Email)
es, err := app.db.AddEmailSubscription(c.ID, ss.UserID, ss.Email, confirmed)
if err != nil {
log.Error("addEmailSubscription: %s", err)
return err
}
// Send confirmation email if needed
if !confirmed {
err = sendSubConfirmEmail(app, c, ss.Email, es.ID, es.Token)
if err != nil {
log.Error("Failed to send subscription confirmation email: %s", err)
return err
}
}
if ss.Web {
session, err := app.sessionStore.Get(r, userEmailCookieName)
if err != nil {
// The cookie should still save, even if there's an error.
// Source: https://github.com/gorilla/sessions/issues/16#issuecomment-143642144
log.Error("Getting user email cookie: %v; ignoring", err)
}
if confirmed {
addSessionFlash(app, w, r, "<strong>Subscribed</strong>. You'll now receive future blog posts via email.", nil)
} else {
addSessionFlash(app, w, r, "Please check your email and <strong>click the confirmation link</strong> to subscribe.", nil)
}
session.Values[userEmailCookieVal] = ss.Email
err = session.Save(r, w)
if err != nil {
log.Error("save email cookie: %s", err)
return err
}
return impart.HTTPError{http.StatusFound, from}
}
return impart.WriteSuccess(w, "", http.StatusAccepted)
}
func handleDeleteEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error {
alias := collectionAliasFromReq(r)
vars := mux.Vars(r)
subID := vars["subscriber"]
email := r.FormValue("email")
token := r.FormValue("t")
slug := r.FormValue("slug")
isWeb := r.Method == "GET"
// Display collection if this is a collection
var c *Collection
var err error
if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
}
if err != nil {
log.Error("Get collection: %s", err)
return err
}
from := c.CanonicalURL()
if subID != "" {
// User unsubscribing via email, so assume action is taken by either current
// user or not current user, and only use the request's information to
// satisfy this unsubscribe, i.e. subscriberID and token.
err = app.db.DeleteEmailSubscriber(subID, token)
} else {
// User unsubscribing through the web app, so assume action is taken by
// currently-auth'd user.
var userID int64
u := getUserSession(app, r)
if u != nil {
// User is logged in
userID = u.ID
if userID == c.OwnerID {
from = "/" + c.Alias + "/"
}
}
if email == "" && userID <= 0 {
// Get email address from saved cookie
session, err := app.sessionStore.Get(r, userEmailCookieName)
if err != nil {
log.Error("Unable to get email cookie: %s", err)
} else {
email = session.Values[userEmailCookieVal].(string)
}
}
if email == "" && userID <= 0 {
err = fmt.Errorf("No subscriber given.")
log.Error("Not deleting subscription: %s", err)
return err
}
err = app.db.DeleteEmailSubscriberByUser(email, userID, c.ID)
}
if err != nil {
log.Error("Unable to delete subscriber: %v", err)
return err
}
if isWeb {
from += slug
addSessionFlash(app, w, r, "<strong>Unsubscribed</strong>. You will no longer receive these blog posts via email.", nil)
return impart.HTTPError{http.StatusFound, from}
}
return impart.WriteSuccess(w, "", http.StatusAccepted)
}
func handleConfirmEmailSubscription(app *App, w http.ResponseWriter, r *http.Request) error {
alias := collectionAliasFromReq(r)
subID := mux.Vars(r)["subscriber"]
token := r.FormValue("t")
var c *Collection
var err error
if app.cfg.App.SingleUser {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
}
if err != nil {
log.Error("Get collection: %s", err)
return err
}
from := c.CanonicalURL()
err = app.db.UpdateSubscriberConfirmed(subID, token)
if err != nil {
addSessionFlash(app, w, r, err.Error(), nil)
return impart.HTTPError{http.StatusFound, from}
}
addSessionFlash(app, w, r, "<strong>Confirmed</strong>! Thanks. Now you'll receive future blog posts via email.", nil)
return impart.HTTPError{http.StatusFound, from}
}
func emailPost(app *App, p *PublicPost, collID int64) error {
p.augmentContent()
// Do some shortcode replacement.
// Since the user is receiving this email, we can assume they're subscribed via email.
p.Content = strings.Replace(p.Content, "<!--emailsub-->", `<p id="emailsub">You're subscribed to email updates.</p>`, -1)
if p.HTMLContent == template.HTML("") {
p.formatContent(app.cfg, false, false)
}
p.augmentReadingDestination()
title := p.Title.String
if title != "" {
title = p.Title.String + "\n\n"
}
plainMsg := title + "A new post from " + p.CanonicalURL(app.cfg.App.Host) + "\n\n" + stripmd.Strip(p.Content)
plainMsg += `
---------------------------------------------------------------------------------
Originally published on ` + p.Collection.DisplayTitle() + ` (` + p.Collection.CanonicalURL() + `), a blog you subscribe to.
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)
replyTo := app.db.GetCollectionAttribute(collID, collAttrLetterReplyTo)
if replyTo != "" {
m.SetReplyTo(replyTo)
}
subs, err := app.db.GetEmailSubscribers(collID, true)
if err != nil {
log.Error("Unable to get email subscribers: %v", err)
return err
}
if len(subs) == 0 {
return nil
}
if title != "" {
title = string(`<h2 id="title">` + p.FormattedDisplayTitle() + `</h2>`)
}
m.AddTag("New post")
fontFam := "Lora, Palatino, Baskerville, serif"
if p.IsSans() {
fontFam = `"Open Sans", Tahoma, Arial, sans-serif`
} else if p.IsMonospace() {
fontFam = `Hack, consolas, Menlo-Regular, Menlo, Monaco, monospace, monospace`
}
// TODO: move this to a templated file and LESS-generated stylesheet
fullHTML := `<html>
<head>
<style>
body {
font-size: 120%;
font-family: ` + fontFam + `;
margin: 1em 2em;
}
#article {
line-height: 1.5;
margin: 1.5em 0;
white-space: pre-wrap;
word-wrap: break-word;
}
h1, h2, h3, h4, h5, h6, p, code {
display: inline
}
img, iframe, video {
max-width: 100%
}
#title {
margin-bottom: 1em;
display: block;
}
.intro {
font-style: italic;
font-size: 0.95em;
}
div#footer {
text-align: center;
max-width: 35em;
margin: 2em auto;
}
div#footer p {
display: block;
font-size: 0.86em;
color: #666;
}
hr {
border: 1px solid #ccc;
margin: 2em 1em;
}
p#emailsub {
text-align: center;
display: inline-block !important;
width: 100%;
font-style: italic;
}
</style>
</head>
<body>
<div id="article">` + title + `<p class="intro">From <a href="` + p.CanonicalURL(app.cfg.App.Host) + `">` + p.DisplayCanonicalURL() + `</a></p>
` + string(p.HTMLContent) + `</div>
<hr />
<div id="footer">
<p>Originally published on <a href="` + p.Collection.CanonicalURL() + `">` + p.Collection.DisplayTitle() + `</a>, a blog you subscribe to.</p>
<p>Sent to %recipient.to%. <a href="` + p.Collection.CanonicalURL() + `email/unsubscribe/%recipient.id%?t=%recipient.token%">Unsubscribe</a>.</p>
</div>
</body>
</html>`
// inline CSS
html, err := inliner.Inline(fullHTML)
if err != nil {
log.Error("Unable to inline email HTML: %v", err)
return err
}
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{}{
"id": s.ID,
"to": e,
"token": s.Token,
})
if err != nil {
log.Error("Unable to add receipient %s: %s", e, err)
}
}
res, _, err := gun.Send(m)
log.Info("[email] Send result: %s", res)
if err != nil {
log.Error("Unable to send post email: %v", err)
return err
}
return nil
}
func sendSubConfirmEmail(app *App, c *Collection, email, subID, token string) error {
if email == "" {
return fmt.Errorf("You must supply an email to verify.")
}
// Send email
gun := mailgun.NewMailgun(app.cfg.Email.Domain, app.cfg.Email.MailgunPrivate)
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.AddTag("Email Verification")
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>
<p><a href="` + c.CanonicalURL() + `email/confirm/` + subID + `?t=` + token + `">Subscribe to ` + c.DisplayTitle() + `</a></p>
<p>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.</p>
</div>
</body>
</html>`)
gun.Send(m)
return nil
}

View File

@ -1,8 +1,19 @@
/*
* Copyright © 2018-2020 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 (
"github.com/writeas/impart"
"net/http"
"github.com/writeas/impart"
)
// Commonly returned HTTP errors
@ -26,6 +37,8 @@ var (
ErrInternalGeneral = impart.HTTPError{http.StatusInternalServerError, "The humans messed something up. They've been notified."}
ErrInternalCookieSession = impart.HTTPError{http.StatusInternalServerError, "Could not get cookie session."}
ErrUnavailable = impart.HTTPError{http.StatusServiceUnavailable, "Service temporarily unavailable due to high load."}
ErrCollectionNotFound = impart.HTTPError{http.StatusNotFound, "Collection doesn't exist."}
ErrCollectionGone = impart.HTTPError{http.StatusGone, "This blog was unpublished."}
ErrCollectionPageNotFound = impart.HTTPError{http.StatusNotFound, "Collection page doesn't exist."}
@ -34,8 +47,13 @@ var (
ErrPostUnpublished = impart.HTTPError{Status: http.StatusGone, Message: "Post unpublished by author."}
ErrPostFetchError = impart.HTTPError{Status: http.StatusInternalServerError, Message: "We encountered an error getting the post. The humans have been alerted."}
ErrUserNotFound = impart.HTTPError{http.StatusNotFound, "User doesn't exist."}
ErrUserNotFoundEmail = impart.HTTPError{http.StatusNotFound, "Please enter your username instead of your email address."}
ErrUserNotFound = impart.HTTPError{http.StatusNotFound, "User doesn't exist."}
ErrRemoteUserNotFound = impart.HTTPError{http.StatusNotFound, "Remote user not found."}
ErrUserNotFoundEmail = impart.HTTPError{http.StatusNotFound, "Please enter your username instead of your email address."}
ErrUserSilenced = impart.HTTPError{http.StatusForbidden, "Account is silenced."}
ErrDisabledPasswordAuth = impart.HTTPError{http.StatusForbidden, "Password authentication is disabled."}
)
// Post operation errors

View File

@ -1,15 +1,26 @@
/*
* Copyright © 2018-2019 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 (
"archive/zip"
"bytes"
"encoding/csv"
"github.com/writeas/web-core/log"
"strings"
"time"
"github.com/writeas/web-core/log"
)
func exportPostsCSV(u *User, posts *[]PublicPost) []byte {
func exportPostsCSV(hostName string, u *User, posts *[]PublicPost) []byte {
var b bytes.Buffer
r := [][]string{
@ -19,23 +30,25 @@ func exportPostsCSV(u *User, posts *[]PublicPost) []byte {
var blog string
if p.Collection != nil {
blog = p.Collection.Alias
p.Collection.hostName = hostName
}
f := []string{p.ID, p.Slug.String, blog, p.CanonicalURL(), p.Created8601(), p.Title.String, strings.Replace(p.Content, "\n", "\\n", -1)}
f := []string{p.ID, p.Slug.String, blog, p.CanonicalURL(hostName), p.Created8601(), p.Title.String, strings.Replace(p.Content, "\n", "\\n", -1)}
r = append(r, f)
}
w := csv.NewWriter(&b)
w.WriteAll(r) // calls Flush internally
if err := w.Error(); err != nil {
log.Info("error writing csv:", err)
log.Info("error writing csv: %v", err)
}
return b.Bytes()
}
type exportedTxt struct {
Name, Body string
Mod time.Time
Name, Title, Body string
Mod time.Time
}
func exportPostsZip(u *User, posts *[]PublicPost) []byte {
@ -57,7 +70,7 @@ func exportPostsZip(u *User, posts *[]PublicPost) []byte {
filename += p.Slug.String + "_"
}
filename += p.ID + ".txt"
files = append(files, exportedTxt{filename, p.Content, p.Created})
files = append(files, exportedTxt{filename, p.Title.String, p.Content, p.Created})
}
for _, file := range files {
@ -67,7 +80,12 @@ func exportPostsZip(u *User, posts *[]PublicPost) []byte {
if err != nil {
log.Error("export zip header: %v", err)
}
_, err = f.Write([]byte(file.Body))
var fullPost string
if file.Title != "" {
fullPost = "# " + file.Title + "\n\n"
}
fullPost += file.Body
_, err = f.Write([]byte(fullPost))
if err != nil {
log.Error("export zip write: %v", err)
}
@ -82,17 +100,17 @@ func exportPostsZip(u *User, posts *[]PublicPost) []byte {
return b.Bytes()
}
func compileFullExport(app *app, u *User) *ExportUser {
func compileFullExport(app *App, u *User) *ExportUser {
exportUser := &ExportUser{
User: u,
}
colls, err := app.db.GetCollections(u)
colls, err := app.db.GetCollections(u, app.cfg.App.Host)
if err != nil {
log.Error("unable to fetch collections: %v", err)
}
posts, err := app.db.GetAnonymousPosts(u)
posts, err := app.db.GetAnonymousPosts(u, 0)
if err != nil {
log.Error("unable to fetch anon posts: %v", err)
}
@ -101,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(&c, 0, 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)
}

57
feed.go
View File

@ -1,30 +1,51 @@
/*
* Copyright © 2018-2021 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 (
"fmt"
. "github.com/gorilla/feeds"
"github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown"
"github.com/writeas/web-core/log"
"net/http"
"time"
"github.com/gorilla/feeds"
"github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/web-core/log"
)
func ViewFeed(app *app, w http.ResponseWriter, req *http.Request) error {
func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
alias := collectionAliasFromReq(req)
// Display collection if this is a collection
var c *Collection
var err error
if app.cfg.App.SingleUser {
c, err = app.db.GetCollection(alias)
} else {
c, err = app.db.GetCollectionByID(1)
} else {
c, err = app.db.GetCollection(alias)
}
if err != nil {
return nil
}
silenced, err := app.db.IsUserSilenced(c.OwnerID)
if err != nil {
log.Error("view feed: get user: %v", err)
return ErrInternalGeneral
}
if silenced {
return ErrCollectionNotFound
}
c.hostName = app.cfg.App.Host
if c.IsPrivate() || c.IsProtected() {
return ErrCollectionNotFound
}
@ -44,9 +65,9 @@ func ViewFeed(app *app, w http.ResponseWriter, req *http.Request) error {
tag := mux.Vars(req)["tag"]
if tag != "" {
coll.Posts, _ = app.db.GetPostsTagged(c, tag, 1, false)
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, 1, false)
} else {
coll.Posts, _ = app.db.GetPosts(c, 1, false)
coll.Posts, _ = app.db.GetPosts(app.cfg, c, 1, false, true, false)
}
author := ""
@ -66,25 +87,29 @@ func ViewFeed(app *app, w http.ResponseWriter, req *http.Request) error {
siteURL += "tag:" + tag
}
feed := &Feed{
feed := &feeds.Feed{
Title: collectionTitle,
Link: &Link{Href: siteURL},
Link: &feeds.Link{Href: siteURL},
Description: coll.Description,
Author: &Author{author, ""},
Author: &feeds.Author{author, ""},
Created: time.Now(),
}
var title, permalink string
for _, p := range *coll.Posts {
// Add necessary path back to the web browser for Web Monetization if needed
p.Collection = coll.CollectionObj // augmentReadingDestination requires a populated Collection field
p.augmentReadingDestination()
// Create the item for the feed
title = p.PlainDisplayTitle()
permalink = fmt.Sprintf("%s%s", baseUrl, p.Slug.String)
feed.Items = append(feed.Items, &Item{
feed.Items = append(feed.Items, &feeds.Item{
Id: fmt.Sprintf("%s%s", basePermalinkUrl, p.Slug.String),
Title: title,
Link: &Link{Href: permalink},
Link: &feeds.Link{Href: permalink},
Description: "<![CDATA[" + stripmd.Strip(p.Content) + "]]>",
Content: applyMarkdown([]byte(p.Content)),
Author: &Author{author, ""},
Content: string(p.HTMLContent),
Author: &feeds.Author{author, ""},
Created: p.Created,
Updated: p.Updated,
})

92
go.mod Normal file
View File

@ -0,0 +1,92 @@
module github.com/writefreely/writefreely
require (
github.com/PuerkitoBio/goquery v1.8.1 // indirect
github.com/aymerick/douceur v0.2.0
github.com/clbanning/mxj v1.8.4 // indirect
github.com/dustin/go-humanize v1.0.1
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c // indirect
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 // indirect
github.com/fatih/color v1.16.0
github.com/go-ini/ini v1.67.0
github.com/go-sql-driver/mysql v1.7.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.2
github.com/gorilla/feeds v1.1.2
github.com/gorilla/mux v1.8.1
github.com/gorilla/schema v1.2.1
github.com/gorilla/sessions v1.2.2
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.21
github.com/microcosm-cc/bluemonday v1.0.26
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
github.com/onsi/gomega v1.13.0 // indirect
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.9.0
github.com/urfave/cli/v2 v2.27.1
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
github.com/writeas/go-webfinger v1.1.0
github.com/writeas/httpsig v1.0.0
github.com/writeas/impart v1.1.1
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.21.0
golang.org/x/net v0.22.0
)
require (
code.as/core/socks v1.0.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/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.2 // 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.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.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/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.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
go 1.19

319
go.sum Normal file
View File

@ -0,0 +1,319 @@
code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs=
code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY=
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=
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1 h1:AFSJaASPGYNbkUa5c8ZybrcW9pP3Cy7+z5dnpcc/qG8=
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1/go.mod h1:EIlIeMufZ8nqdUhnesledB15xLRl4wIJUppwDLPrdrQ=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c h1:8ISkoahWXwZR41ois5lSJBSVw4D0OV19Ht/JSTzvSv0=
github.com/facebookgo/ensure v0.0.0-20200202191622-63f1cf65ac4c/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4 h1:7HZCaLC5+BZpmbhCOZJ293Lz68O7PYrF2EzeiFMwCLk=
github.com/facebookgo/subset v0.0.0-20200203212716-c811ad88dec4/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/fatih/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=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-fed/httpsig v0.1.0/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4Lb71UuprPHqhjxGv3Jqonb9fgcaJH8=
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-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=
github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE=
github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c=
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
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/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/csrf v1.7.2 h1:oTUjx0vyf2T+wkrx09Trsev1TE+/EbDAeHtSTbtC2eI=
github.com/gorilla/csrf v1.7.2/go.mod h1:F1Fj3KG23WYHE6gozCmBAezKookxbIvUJT+121wTuLk=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
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.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/schema v1.2.1 h1:tjDxcmdb+siIqkTNoV+qRH2mjYdr2hHe5MKXbp61ziM=
github.com/gorilla/schema v1.2.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.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
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=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM=
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/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=
github.com/mailgun/mailgun-go v2.0.0+incompatible/go.mod h1:NWTyU+O4aczg/nsGhQnvHL6v2n5Gy6Sv5tNDVvC6FbU=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
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.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/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=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak=
github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/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=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho=
github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
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=
github.com/writeas/activityserve v0.0.0-20230428180247-dc13a4f4d835/go.mod h1:4akDJSl+sSp+QhrQKMqzAqdV1gJ1pPx6XPI77zgMM8o=
github.com/writeas/go-strip-markdown/v2 v2.1.1 h1:hAxUM21Uhznf/FnbVGiJciqzska6iLei22Ijc3q2e28=
github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA=
github.com/writeas/go-webfinger v1.1.0 h1:MzNyt0ry/GMsRmJGftn2o9mPwqK1Q5MLdh4VuJCfb1Q=
github.com/writeas/go-webfinger v1.1.0/go.mod h1:w2VxyRO/J5vfNjJHYVubsjUGHd3RLDoVciz0DE3ApOc=
github.com/writeas/go-writeas v1.1.0/go.mod h1:oh9U1rWaiE0p3kzdKwwvOpNXgp0P0IELI7OLOwV4fkA=
github.com/writeas/go-writeas/v2 v2.0.2 h1:akvdMg89U5oBJiCkBwOXljVLTqP354uN6qnG2oOMrbk=
github.com/writeas/go-writeas/v2 v2.0.2/go.mod h1:9sjczQJKmru925fLzg0usrU1R1tE4vBmQtGnItUMR0M=
github.com/writeas/httpsig v1.0.0 h1:peIAoIA3DmlP8IG8tMNZqI4YD1uEnWBmkcC9OFPjt3A=
github.com/writeas/httpsig v1.0.0/go.mod h1:7ClMGSrSVXJbmiLa17bZ1LrG1oibGZmUMlh3402flPY=
github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
github.com/writeas/impart v1.1.1 h1:RyA9+CqbdbDuz53k+nXCWUY+NlEkdyw6+nWanxSBl5o=
github.com/writeas/impart v1.1.1/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
github.com/writeas/import v0.2.1 h1:3k+bDNCyqaWdZinyUZtEO4je3mR6fr/nE4ozTh9/9Wg=
github.com/writeas/import v0.2.1/go.mod h1:gFe0Pl7ZWYiXbI0TJxeMMyylPGZmhVvCfQxhMEc8CxM=
github.com/writeas/monday v1.3.0 h1:h51wJ0DULXIDZ1w11zutLL7YCBRO5LznXISSzqVLZeA=
github.com/writeas/monday v1.3.0/go.mod h1:9/CdGLDdIeAvzvf4oeihX++PE/qXUT2+tUlPQKCfRWY=
github.com/writeas/openssl-go v1.0.0 h1:YXM1tDXeYOlTyJjoMlYLQH1xOloUimSR1WMF8kjFc5o=
github.com/writeas/openssl-go v1.0.0/go.mod h1:WsKeK5jYl0B5y8ggOmtVjbmb+3rEGqSD25TppjJnETA=
github.com/writeas/saturday v1.7.1/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 h1:PozPZ29CQ/xt6ym/+FvIz+KvKEObSSc5ye+95zbTjVU=
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g=
github.com/writeas/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ=
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431 h1:ruqL2u87k504PXkR/fC4DcfZyyHmCindlpjOQKmyOsY=
github.com/writeas/web-core v1.6.1-0.20231003013047-d81124d45431/go.mod h1:7+idL4Y4woF7MnUfNX2mvkaQ8nLIJXths2y5iYPtA3k=
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ50NNi5k9yrFeyFszt3LyqyVK4+xUHFYY8B0=
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/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=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-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.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
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=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
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.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
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=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.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=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

167
gopher.go Normal file
View File

@ -0,0 +1,167 @@
/*
* Copyright © 2020 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 (
"bytes"
"fmt"
"io"
"net/url"
"regexp"
"strings"
"github.com/writeas/web-core/log"
"github.com/writefreely/go-gopher"
)
func initGopher(apper Apper) {
handler := NewWFHandler(apper)
gopher.HandleFunc("/", handler.Gopher(handleGopher))
log.Info("Serving on gopher://localhost:%d", apper.App().Config().Server.GopherPort)
gopher.ListenAndServe(fmt.Sprintf(":%d", apper.App().Config().Server.GopherPort), nil)
}
// Utility function to strip the URL from the hostname provided by app.cfg.App.Host
func stripHostProtocol(app *App) string {
u, err := url.Parse(app.cfg.App.Host)
if err != nil {
// Fall back to host, with scheme stripped
return string(regexp.MustCompile("^.*://").ReplaceAll([]byte(app.cfg.App.Host), []byte("")))
}
return u.Hostname()
}
func handleGopher(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
parts := strings.Split(r.Selector, "/")
if app.cfg.App.SingleUser {
if parts[1] != "" {
return handleGopherCollectionPost(app, w, r)
}
return handleGopherCollection(app, w, r)
}
// Show all public collections (a gopher Reader view, essentially)
if len(parts) == 3 {
return handleGopherCollection(app, w, r)
}
w.WriteInfo(fmt.Sprintf("Welcome to %s", app.cfg.App.SiteName))
colls, err := app.db.GetPublicCollections(app.cfg.App.Host)
if err != nil {
return err
}
for _, c := range *colls {
w.WriteItem(&gopher.Item{
Host: stripHostProtocol(app),
Port: app.cfg.Server.GopherPort,
Type: gopher.DIRECTORY,
Description: c.DisplayTitle(),
Selector: "/" + c.Alias + "/",
})
}
return w.End()
}
func handleGopherCollection(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
var collAlias, slug string
var c *Collection
var err error
var baseSel = "/"
parts := strings.Split(r.Selector, "/")
if app.cfg.App.SingleUser {
// sanity check
slug = parts[1]
if slug != "" {
return handleGopherCollectionPost(app, w, r)
}
c, err = app.db.GetCollectionByID(1)
if err != nil {
return err
}
} else {
collAlias = parts[1]
slug = parts[2]
if slug != "" {
return handleGopherCollectionPost(app, w, r)
}
c, err = app.db.GetCollection(collAlias)
if err != nil {
return err
}
baseSel = "/" + c.Alias + "/"
}
c.hostName = app.cfg.App.Host
w.WriteInfo(c.DisplayTitle())
if c.Description != "" {
w.WriteInfo(c.Description)
}
posts, err := app.db.GetPosts(app.cfg, c, 0, false, false, false)
if err != nil {
return err
}
for _, p := range *posts {
w.WriteItem(&gopher.Item{
Port: app.cfg.Server.GopherPort,
Host: stripHostProtocol(app),
Type: gopher.FILE,
Description: p.CreatedDate() + " - " + p.DisplayTitle(),
Selector: baseSel + p.Slug.String,
})
}
return w.End()
}
func handleGopherCollectionPost(app *App, w gopher.ResponseWriter, r *gopher.Request) error {
var collAlias, slug string
var c *Collection
var err error
parts := strings.Split(r.Selector, "/")
if app.cfg.App.SingleUser {
slug = parts[1]
c, err = app.db.GetCollectionByID(1)
if err != nil {
return err
}
} else {
collAlias = parts[1]
slug = parts[2]
c, err = app.db.GetCollection(collAlias)
if err != nil {
return err
}
}
c.hostName = app.cfg.App.Host
p, err := app.db.GetPost(slug, c.ID)
if err != nil {
return err
}
b := bytes.Buffer{}
if p.Title.String != "" {
b.WriteString(p.Title.String + "\n")
}
b.WriteString(p.DisplayDate + "\n\n")
b.WriteString(p.Content)
io.Copy(w, &b)
return w.End()
}

577
handle.go
View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018-2021 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 (
@ -13,29 +23,60 @@ import (
"github.com/gorilla/sessions"
"github.com/writeas/impart"
"github.com/writeas/web-core/log"
"github.com/writeas/writefreely/page"
"github.com/writefreely/go-gopher"
"github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page"
)
// UserLevel represents the required user level for accessing an endpoint
type UserLevel int
const (
UserLevelNone UserLevel = iota // user or not -- ignored
UserLevelOptional // user or not -- object fetched if user
UserLevelNoneRequired // non-user (required)
UserLevelUser // user (required)
UserLevelNoneType UserLevel = iota // user or not -- ignored
UserLevelOptionalType // user or not -- object fetched if user
UserLevelNoneRequiredType // non-user (required)
UserLevelUserType // user (required)
)
func UserLevelNone(cfg *config.Config) UserLevel {
return UserLevelNoneType
}
func UserLevelOptional(cfg *config.Config) UserLevel {
return UserLevelOptionalType
}
func UserLevelNoneRequired(cfg *config.Config) UserLevel {
return UserLevelNoneRequiredType
}
func UserLevelUser(cfg *config.Config) UserLevel {
return UserLevelUserType
}
// UserLevelReader returns the permission level required for any route where
// users can read published content.
func UserLevelReader(cfg *config.Config) UserLevel {
if cfg.App.Private {
return UserLevelUserType
}
return UserLevelOptionalType
}
type (
handlerFunc func(app *app, w http.ResponseWriter, r *http.Request) error
userHandlerFunc func(app *app, u *User, w http.ResponseWriter, r *http.Request) error
dataHandlerFunc func(app *app, w http.ResponseWriter, r *http.Request) ([]byte, string, error)
authFunc func(app *app, r *http.Request) (*User, error)
handlerFunc func(app *App, w http.ResponseWriter, r *http.Request) error
gopherFunc func(app *App, w gopher.ResponseWriter, r *gopher.Request) error
userHandlerFunc func(app *App, u *User, w http.ResponseWriter, r *http.Request) error
userApperHandlerFunc func(apper Apper, u *User, w http.ResponseWriter, r *http.Request) error
dataHandlerFunc func(app *App, w http.ResponseWriter, r *http.Request) ([]byte, string, error)
authFunc func(app *App, r *http.Request) (*User, error)
UserLevelFunc func(cfg *config.Config) UserLevel
)
type Handler struct {
errors *ErrorPages
sessionStore *sessions.CookieStore
app *app
sessionStore sessions.Store
app Apper
}
// ErrorPages hold template HTML error pages for displaying errors to the user.
@ -44,26 +85,42 @@ type ErrorPages struct {
NotFound *template.Template
Gone *template.Template
InternalServerError *template.Template
UnavailableError *template.Template
Blank *template.Template
}
// NewHandler returns a new Handler instance, using the given StaticPage data,
// and saving alias to the application's CookieStore.
func NewHandler(app *app) *Handler {
func NewHandler(apper Apper) *Handler {
h := &Handler{
errors: &ErrorPages{
NotFound: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>404</title></head><body><p>Not found.</p></body></html>{{end}}")),
Gone: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>410</title></head><body><p>Gone.</p></body></html>{{end}}")),
InternalServerError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>500</title></head><body><p>Internal server error.</p></body></html>{{end}}")),
UnavailableError: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>503</title></head><body><p>Service is temporarily unavailable.</p></body></html>{{end}}")),
Blank: template.Must(template.New("").Parse("{{define \"base\"}}<html><head><title>{{.Title}}</title></head><body><p>{{.Content}}</p></body></html>{{end}}")),
},
sessionStore: app.sessionStore,
app: app,
sessionStore: apper.App().SessionStore(),
app: apper,
}
return h
}
// NewWFHandler returns a new Handler instance, using WriteFreely template files.
// You MUST call writefreely.InitTemplates() before this.
func NewWFHandler(apper Apper) *Handler {
h := NewHandler(apper)
h.SetErrorPages(&ErrorPages{
NotFound: pages["404-general.tmpl"],
Gone: pages["410.tmpl"],
InternalServerError: pages["500.tmpl"],
UnavailableError: pages["503.tmpl"],
Blank: pages["blank.tmpl"],
})
return h
}
// SetErrorPages sets the given set of ErrorPages as templates for any errors
// that come up.
func (h *Handler) SetErrorPages(e *ErrorPages) {
@ -81,20 +138,102 @@ func (h *Handler) User(f userHandlerFunc) http.HandlerFunc {
defer func() {
if e := recover(); e != nil {
log.Error("%s: %s", e, debug.Stack())
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = http.StatusInternalServerError
}
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
u := getUserSession(h.app, r)
u := getUserSession(h.app.App(), r)
if u == nil {
err := ErrNotLoggedIn
status = err.Status
return err
}
err := f(h.app.App(), u, w, r)
if err == nil {
status = http.StatusOK
} else if impErr, ok := err.(impart.HTTPError); ok {
status = impErr.Status
if impErr == ErrUserNotFound {
log.Info("Logged-in user not found. Logging out.")
sendRedirect(w, http.StatusFound, "/me/logout?to="+h.app.App().cfg.App.LandingPath())
// Reset err so handleHTTPError does nothing
err = nil
}
} else {
status = http.StatusInternalServerError
}
return err
}())
}
}
// Admin handles requests on /admin routes
func (h *Handler) Admin(f userHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleHTTPError(w, r, func() error {
var status int
start := time.Now()
defer func() {
if e := recover(); e != nil {
log.Error("%s: %s", e, debug.Stack())
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = http.StatusInternalServerError
}
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
u := getUserSession(h.app.App(), r)
if u == nil || !u.IsAdmin() {
err := impart.HTTPError{http.StatusNotFound, ""}
status = err.Status
return err
}
err := f(h.app.App(), u, w, r)
if err == nil {
status = http.StatusOK
} else if err, ok := err.(impart.HTTPError); ok {
status = err.Status
} else {
status = http.StatusInternalServerError
}
return err
}())
}
}
// AdminApper handles requests on /admin routes that require an Apper.
func (h *Handler) AdminApper(f userApperHandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleHTTPError(w, r, func() error {
var status int
start := time.Now()
defer func() {
if e := recover(); e != nil {
log.Error("%s: %s", e, debug.Stack())
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = http.StatusInternalServerError
}
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
u := getUserSession(h.app.App(), r)
if u == nil || !u.IsAdmin() {
err := impart.HTTPError{http.StatusNotFound, ""}
status = err.Status
return err
}
err := f(h.app, u, w, r)
if err == nil {
status = http.StatusOK
@ -109,18 +248,65 @@ func (h *Handler) User(f userHandlerFunc) http.HandlerFunc {
}
}
func apiAuth(app *App, r *http.Request) (*User, error) {
// Authorize user from Authorization header
t := r.Header.Get("Authorization")
if t == "" {
return nil, ErrNoAccessToken
}
u := &User{ID: app.db.GetUserID(t)}
if u.ID == -1 {
return nil, ErrBadAccessToken
}
return u, nil
}
// optionalAPIAuth is used for endpoints that accept authenticated requests via
// Authorization header or cookie, unlike apiAuth. It returns a different err
// in the case where no Authorization header is present.
func optionalAPIAuth(app *App, r *http.Request) (*User, error) {
// Authorize user from Authorization header
t := r.Header.Get("Authorization")
if t == "" {
return nil, ErrNotLoggedIn
}
u := &User{ID: app.db.GetUserID(t)}
if u.ID == -1 {
return nil, ErrBadAccessToken
}
return u, nil
}
func webAuth(app *App, r *http.Request) (*User, error) {
u := getUserSession(app, r)
if u == nil {
return nil, ErrNotLoggedIn
}
return u, nil
}
// UserAPI handles requests made in the API by the authenticated user.
// This provides user-friendly HTML pages and actions that work in the browser.
func (h *Handler) UserAPI(f userHandlerFunc) http.HandlerFunc {
return h.UserAll(false, f, func(app *app, r *http.Request) (*User, error) {
// Authorize user from Authorization header
t := r.Header.Get("Authorization")
if t == "" {
return nil, ErrNoAccessToken
return h.UserAll(false, f, apiAuth)
}
// UserWebAPI handles endpoints that accept a user authorized either via the web (cookies) or an Authorization header.
func (h *Handler) UserWebAPI(f userHandlerFunc) http.HandlerFunc {
return h.UserAll(false, f, func(app *App, r *http.Request) (*User, error) {
// Authorize user via cookies
u := getUserSession(app, r)
if u != nil {
return u, nil
}
u := &User{ID: app.db.GetUserID(t)}
if u.ID == -1 {
return nil, ErrBadAccessToken
// Fall back to access token, since user isn't logged in via web
var err error
u, err = apiAuth(app, r)
if err != nil {
return nil, err
}
return u, nil
@ -140,10 +326,10 @@ func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerF
status = 500
}
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
u, err := a(h.app, r)
u, err := a(h.app.App(), r)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
status = err.Status
@ -153,7 +339,7 @@ func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerF
return err
}
err = f(h.app, u, w, r)
err = f(h.app.App(), u, w, r)
if err == nil {
status = 200
} else if err, ok := err.(impart.HTTPError); ok {
@ -174,7 +360,7 @@ func (h *Handler) UserAll(web bool, f userHandlerFunc, a authFunc) http.HandlerF
}
func (h *Handler) RedirectOnErr(f handlerFunc, loc string) handlerFunc {
return func(app *app, w http.ResponseWriter, r *http.Request) error {
return func(app *App, w http.ResponseWriter, r *http.Request) error {
err := f(app, w, r)
if err != nil {
if ie, ok := err.(impart.HTTPError); ok {
@ -191,7 +377,7 @@ func (h *Handler) RedirectOnErr(f handlerFunc, loc string) handlerFunc {
}
func (h *Handler) Page(n string) http.HandlerFunc {
return h.Web(func(app *app, w http.ResponseWriter, r *http.Request) error {
return h.Web(func(app *App, w http.ResponseWriter, r *http.Request) error {
t, ok := pages[n]
if !ok {
return impart.HTTPError{http.StatusNotFound, "Page not found."}
@ -207,7 +393,7 @@ func (h *Handler) Page(n string) http.HandlerFunc {
}, UserLevelOptional)
}
func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
func (h *Handler) WebErrors(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// TODO: factor out this logic shared with Web()
h.handleHTTPError(w, r, func() error {
@ -216,36 +402,36 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
defer func() {
if e := recover(); e != nil {
u := getUserSession(h.app, r)
u := getUserSession(h.app.App(), r)
username := "None"
if u != nil {
username = u.Username
}
log.Error("User: %s\n\n%s: %s", username, e, debug.Stack())
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = 500
}
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
var session *sessions.Session
var err error
if ul != UserLevelNone {
if ul(h.app.App().cfg) != UserLevelNoneType {
session, err = h.sessionStore.Get(r, cookieName)
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
// Cookie is required, but we can ignore this error
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul(h.app.App().cfg), err)
}
_, gotUser := session.Values[cookieUserVal].(*User)
if ul == UserLevelNoneRequired && gotUser {
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
to := correctPageFromLoginAttempt(r)
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
err := impart.HTTPError{http.StatusFound, to}
status = err.Status
return err
} else if ul == UserLevelUser && !gotUser {
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
err := ErrNotLoggedIn
status = err.Status
@ -254,13 +440,13 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
}
// TODO: pass User object to function
err = f(h.app, w, r)
err = f(h.app.App(), w, r)
if err == nil {
status = 200
} else if httpErr, ok := err.(impart.HTTPError); ok {
status = httpErr.Status
if status < 300 || status > 399 {
addSessionFlash(h.app, w, r, httpErr.Message, session)
addSessionFlash(h.app.App(), w, r, httpErr.Message, session)
return impart.HTTPError{http.StatusFound, r.Referer()}
}
} else {
@ -271,7 +457,7 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
log.Error(e)
}
log.Info("Web handler internal error render")
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = 500
}
@ -280,9 +466,25 @@ func (h *Handler) WebErrors(f handlerFunc, ul UserLevel) http.HandlerFunc {
}
}
func (h *Handler) CollectionPostOrStatic(w http.ResponseWriter, r *http.Request) {
if strings.Contains(r.URL.Path, ".") && !isRaw(r) {
start := time.Now()
status := 200
defer func() {
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
// Serve static file
h.app.App().shttp.ServeHTTP(w, r)
return
}
h.Web(viewCollectionPost, UserLevelReader)(w, r)
}
// Web handles requests made in the web application. This provides user-
// friendly HTML pages and actions that work in the browser.
func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
func (h *Handler) Web(f handlerFunc, ul UserLevelFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleHTTPError(w, r, func() error {
var status int
@ -290,35 +492,35 @@ func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
defer func() {
if e := recover(); e != nil {
u := getUserSession(h.app, r)
u := getUserSession(h.app.App(), r)
username := "None"
if u != nil {
username = u.Username
}
log.Error("User: %s\n\n%s: %s", username, e, debug.Stack())
log.Info("Web deferred internal error render")
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = 500
}
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
if ul != UserLevelNone {
if ul(h.app.App().cfg) != UserLevelNoneType {
session, err := h.sessionStore.Get(r, cookieName)
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
// Cookie is required, but we can ignore this error
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul(h.app.App().cfg), err)
}
_, gotUser := session.Values[cookieUserVal].(*User)
if ul == UserLevelNoneRequired && gotUser {
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
to := correctPageFromLoginAttempt(r)
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
err := impart.HTTPError{http.StatusFound, to}
status = err.Status
return err
} else if ul == UserLevelUser && !gotUser {
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
err := ErrNotLoggedIn
status = err.Status
@ -327,7 +529,7 @@ func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
}
// TODO: pass User object to function
err := f(h.app, w, r)
err := f(h.app.App(), w, r)
if err == nil {
status = 200
} else if httpErr, ok := err.(impart.HTTPError); ok {
@ -336,7 +538,7 @@ func (h *Handler) Web(f handlerFunc, ul UserLevel) http.HandlerFunc {
e := fmt.Sprintf("[Web handler] 500: %v", err)
log.Error(e)
log.Info("Web internal error render")
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = 500
}
@ -359,12 +561,12 @@ func (h *Handler) All(f handlerFunc) http.HandlerFunc {
status = 500
}
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
// TODO: do any needed authentication
err := f(h.app, w, r)
err := f(h.app.App(), w, r)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
status = err.Status
@ -378,7 +580,131 @@ func (h *Handler) All(f handlerFunc) http.HandlerFunc {
}
}
func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
func (h *Handler) PlainTextAPI(f handlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleTextError(w, r, func() error {
// TODO: return correct "success" status
status := 200
start := time.Now()
defer func() {
if e := recover(); e != nil {
log.Error("%s:\n%s", e, debug.Stack())
status = http.StatusInternalServerError
w.WriteHeader(status)
fmt.Fprintf(w, "Something didn't work quite right. The robots have alerted the humans.")
}
log.Info(fmt.Sprintf("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host))
}()
err := f(h.app.App(), w, r)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
status = err.Status
} else {
status = http.StatusInternalServerError
}
}
return err
}())
}
}
func (h *Handler) OAuth(f handlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleOAuthError(w, r, func() error {
// TODO: return correct "success" status
status := 200
start := time.Now()
defer func() {
if e := recover(); e != nil {
log.Error("%s:\n%s", e, debug.Stack())
impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "Something didn't work quite right."})
status = 500
}
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
err := f(h.app.App(), w, r)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
status = err.Status
} else {
status = 500
}
}
return err
}())
}
}
func (h *Handler) AllReader(f handlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleError(w, r, func() error {
status := 200
start := time.Now()
defer func() {
if e := recover(); e != nil {
log.Error("%s:\n%s", e, debug.Stack())
impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "Something didn't work quite right."})
status = 500
}
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
// Allow any origin, as public endpoints are handled in here
w.Header().Set("Access-Control-Allow-Origin", "*")
if h.app.App().cfg.App.Private {
// This instance is private, so ensure it's being accessed by a valid user
// Check if authenticated with an access token
_, apiErr := optionalAPIAuth(h.app.App(), r)
if apiErr != nil {
if err, ok := apiErr.(impart.HTTPError); ok {
status = err.Status
} else {
status = 500
}
if apiErr == ErrNotLoggedIn {
// Fall back to web auth since there was no access token given
_, err := webAuth(h.app.App(), r)
if err != nil {
if err, ok := apiErr.(impart.HTTPError); ok {
status = err.Status
} else {
status = 500
}
return err
}
} else {
return apiErr
}
}
}
err := f(h.app.App(), w, r)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
status = err.Status
} else {
status = 500
}
}
return err
}())
}
}
func (h *Handler) Download(f dataHandlerFunc, ul UserLevelFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleHTTPError(w, r, func() error {
var status int
@ -386,14 +712,14 @@ func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
defer func() {
if e := recover(); e != nil {
log.Error("%s: %s", e, debug.Stack())
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = 500
}
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
data, filename, err := f(h.app, w, r)
data, filename, err := f(h.app.App(), w, r)
if err != nil {
if err, ok := err.(impart.HTTPError); ok {
status = err.Status
@ -423,27 +749,27 @@ func (h *Handler) Download(f dataHandlerFunc, ul UserLevel) http.HandlerFunc {
}
}
func (h *Handler) Redirect(url string, ul UserLevel) http.HandlerFunc {
func (h *Handler) Redirect(url string, ul UserLevelFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
h.handleHTTPError(w, r, func() error {
start := time.Now()
var status int
if ul != UserLevelNone {
if ul(h.app.App().cfg) != UserLevelNoneType {
session, err := h.sessionStore.Get(r, cookieName)
if err != nil && (ul == UserLevelNoneRequired || ul == UserLevelUser) {
if err != nil && (ul(h.app.App().cfg) == UserLevelNoneRequiredType || ul(h.app.App().cfg) == UserLevelUserType) {
// Cookie is required, but we can ignore this error
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul, err)
log.Error("Handler: Unable to get session (for user permission %d); ignoring: %v", ul(h.app.App().cfg), err)
}
_, gotUser := session.Values[cookieUserVal].(*User)
if ul == UserLevelNoneRequired && gotUser {
if ul(h.app.App().cfg) == UserLevelNoneRequiredType && gotUser {
to := correctPageFromLoginAttempt(r)
log.Info("Handler: Required NO user, but got one. Redirecting to %s", to)
err := impart.HTTPError{http.StatusFound, to}
status = err.Status
return err
} else if ul == UserLevelUser && !gotUser {
} else if ul(h.app.App().cfg) == UserLevelUserType && !gotUser {
log.Info("Handler: Required a user, but DIDN'T get one. Sending not logged in.")
err := ErrNotLoggedIn
status = err.Status
@ -453,7 +779,7 @@ func (h *Handler) Redirect(url string, ul UserLevel) http.HandlerFunc {
status = sendRedirect(w, http.StatusFound, url)
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
return nil
}())
@ -477,11 +803,12 @@ func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err er
sendRedirect(w, http.StatusFound, "/login?to="+r.URL.Path+q)
return
} else if err.Status == http.StatusGone {
w.WriteHeader(err.Status)
p := &struct {
page.StaticPage
Content *template.HTML
}{
StaticPage: pageForReq(h.app, r),
StaticPage: pageForReq(h.app.App(), r),
}
if err.Message != "" {
co := template.HTML(err.Message)
@ -490,11 +817,21 @@ func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err er
h.errors.Gone.ExecuteTemplate(w, "base", p)
return
} else if err.Status == http.StatusNotFound {
h.errors.NotFound.ExecuteTemplate(w, "base", pageForReq(h.app, r))
w.WriteHeader(err.Status)
if IsActivityPubRequest(r) {
// This is a fediverse request; simply return the header
return
}
h.errors.NotFound.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
return
} else if err.Status == http.StatusInternalServerError {
w.WriteHeader(err.Status)
log.Info("handleHTTPErorr internal error render")
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
return
} else if err.Status == http.StatusServiceUnavailable {
w.WriteHeader(err.Status)
h.errors.UnavailableError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
return
} else if err.Status == http.StatusAccepted {
impart.WriteSuccess(w, "", err.Status)
@ -505,7 +842,7 @@ func (h *Handler) handleHTTPError(w http.ResponseWriter, r *http.Request, err er
Title string
Content template.HTML
}{
pageForReq(h.app, r),
pageForReq(h.app.App(), r),
fmt.Sprintf("Uh oh (%d)", err.Status),
template.HTML(fmt.Sprintf("<p style=\"text-align: center\" class=\"introduction\">%s</p>", err.Message)),
}
@ -536,11 +873,50 @@ func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error)
return
}
if IsJSON(r.Header.Get("Content-Type")) {
if IsJSON(r) {
impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "This is an unhelpful error message for a miscellaneous internal error."})
return
}
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
}
func (h *Handler) handleTextError(w http.ResponseWriter, r *http.Request, err error) {
if err == nil {
return
}
if err, ok := err.(impart.HTTPError); ok {
if err.Status >= 300 && err.Status < 400 {
sendRedirect(w, err.Status, err.Message)
return
}
w.WriteHeader(err.Status)
fmt.Fprintf(w, http.StatusText(err.Status))
return
}
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprintf(w, "This is an unhelpful error message for a miscellaneous internal error.")
}
func (h *Handler) handleOAuthError(w http.ResponseWriter, r *http.Request, err error) {
if err == nil {
return
}
if err, ok := err.(impart.HTTPError); ok {
if err.Status >= 300 && err.Status < 400 {
sendRedirect(w, err.Status, err.Message)
return
}
impart.WriteOAuthError(w, err)
return
}
impart.WriteOAuthError(w, impart.HTTPError{http.StatusInternalServerError, "This is an unhelpful error message for a miscellaneous internal error."})
return
}
func correctPageFromLoginAttempt(r *http.Request) string {
@ -562,14 +938,42 @@ func (h *Handler) LogHandlerFunc(f http.HandlerFunc) http.HandlerFunc {
defer func() {
if e := recover(); e != nil {
log.Error("Handler.LogHandlerFunc\n\n%s: %s", e, debug.Stack())
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app, r))
h.errors.InternalServerError.ExecuteTemplate(w, "base", pageForReq(h.app.App(), r))
status = 500
}
// TODO: log actual status code returned
log.Info("\"%s %s\" %d %s \"%s\" \"%s\"", r.Method, r.RequestURI, status, time.Since(start), r.UserAgent(), r.Host)
log.Info(h.app.ReqLog(r, status, time.Since(start)))
}()
if h.app.App().cfg.App.Private {
// This instance is private, so ensure it's being accessed by a valid user
// Check if authenticated with an access token
_, apiErr := optionalAPIAuth(h.app.App(), r)
if apiErr != nil {
if err, ok := apiErr.(impart.HTTPError); ok {
status = err.Status
} else {
status = 500
}
if apiErr == ErrNotLoggedIn {
// Fall back to web auth since there was no access token given
_, err := webAuth(h.app.App(), r)
if err != nil {
if err, ok := apiErr.(impart.HTTPError); ok {
status = err.Status
} else {
status = 500
}
return err
}
} else {
return apiErr
}
}
}
f(w, r)
return nil
@ -577,8 +981,33 @@ func (h *Handler) LogHandlerFunc(f http.HandlerFunc) http.HandlerFunc {
}
}
func (h *Handler) Gopher(f gopherFunc) gopher.HandlerFunc {
return func(w gopher.ResponseWriter, r *gopher.Request) {
defer func() {
if e := recover(); e != nil {
log.Error("%s: %s", e, debug.Stack())
w.WriteError("An internal error occurred")
}
log.Info("gopher: %s", r.Selector)
}()
err := f(h.app.App(), w, r)
if err != nil {
log.Error("failed: %s", err)
w.WriteError("the page failed for some reason (see logs)")
}
}
}
func sendRedirect(w http.ResponseWriter, code int, location string) int {
w.Header().Set("Location", location)
w.WriteHeader(code)
return code
}
func cacheControl(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, max-age=604800, immutable")
next.ServeHTTP(w, r)
})
}

View File

@ -1,3 +1,13 @@
/*
* Copyright © 2018-2019 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 (
@ -5,7 +15,7 @@ import (
"net/http"
)
func handleViewHostMeta(app *app, w http.ResponseWriter, r *http.Request) error {
func handleViewHostMeta(app *App, w http.ResponseWriter, r *http.Request) error {
w.Header().Set("Server", serverSoftware)
w.Header().Set("Content-Type", "application/xrd+xml; charset=utf-8")

16
instance.go Normal file
View File

@ -0,0 +1,16 @@
/*
* Copyright © 2018 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
type InstanceStats struct {
NumPosts int64
NumBlogs int64
}

206
invites.go Normal file
View File

@ -0,0 +1,206 @@
/*
* Copyright © 2019-2021 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"
"html/template"
"net/http"
"strconv"
"time"
"github.com/gorilla/mux"
"github.com/writeas/impart"
"github.com/writeas/web-core/id"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/page"
)
type Invite struct {
ID string
MaxUses sql.NullInt64
Created time.Time
Expires *time.Time
Inactive bool
uses int64
}
func (i Invite) Uses() int64 {
return i.uses
}
func (i Invite) Expired() bool {
return i.Expires != nil && i.Expires.Before(time.Now())
}
func (i Invite) Active(db *datastore) bool {
if i.Expired() {
return false
}
if i.MaxUses.Valid && i.MaxUses.Int64 > 0 {
if c := db.GetUsersInvitedCount(i.ID); c >= i.MaxUses.Int64 {
return false
}
}
return true
}
func (i Invite) ExpiresFriendly() string {
return i.Expires.Format("January 2, 2006, 3:04 PM")
}
func handleViewUserInvites(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
// Don't show page if instance doesn't allow it
if !(app.cfg.App.UserInvites != "" && (u.IsAdmin() || app.cfg.App.UserInvites != "admin")) {
return impart.HTTPError{http.StatusNotFound, ""}
}
f, _ := getSessionFlashes(app, w, r, nil)
p := struct {
*UserPage
Invites *[]Invite
Silenced bool
}{
UserPage: NewUserPage(app, r, u, "Invite People", f),
}
var err error
p.Silenced, err = app.db.IsUserSilenced(u.ID)
if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view invites: %v", err)
}
p.Invites, err = app.db.GetUserInvites(u.ID)
if err != nil {
return err
}
for i := range *p.Invites {
(*p.Invites)[i].uses = app.db.GetUsersInvitedCount((*p.Invites)[i].ID)
}
showUserPage(w, "invite", p)
return nil
}
func handleCreateUserInvite(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
muVal := r.FormValue("uses")
expVal := r.FormValue("expires")
if u.IsSilenced() {
return ErrUserSilenced
}
var err error
var maxUses int
if muVal != "0" {
maxUses, err = strconv.Atoi(muVal)
if err != nil {
return impart.HTTPError{http.StatusBadRequest, "Invalid value for 'max_uses'"}
}
}
var expDate *time.Time
var expires int
if expVal != "0" {
expires, err = strconv.Atoi(expVal)
if err != nil {
return impart.HTTPError{http.StatusBadRequest, "Invalid value for 'expires'"}
}
ed := time.Now().Add(time.Duration(expires) * time.Minute)
expDate = &ed
}
inviteID := id.GenerateRandomString("0123456789BCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", 6)
err = app.db.CreateUserInvite(inviteID, u.ID, maxUses, expDate)
if err != nil {
return err
}
return impart.HTTPError{http.StatusFound, "/me/invites"}
}
func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error {
inviteCode := mux.Vars(r)["code"]
i, err := app.db.GetUserInvite(inviteCode)
if err != nil {
return err
}
expired := i.Expired()
if !expired && i.MaxUses.Valid && i.MaxUses.Int64 > 0 {
// Invite has a max-use number, so check if we're past that limit
i.uses = app.db.GetUsersInvitedCount(inviteCode)
expired = i.uses >= i.MaxUses.Int64
}
if u := getUserSession(app, r); u != nil {
// check if invite belongs to another user
// error can be ignored as not important in this case
if ownInvite, _ := app.db.IsUsersInvite(inviteCode, u.ID); !ownInvite {
addSessionFlash(app, w, r, "You're already registered and logged in.", nil)
// show homepage
return impart.HTTPError{http.StatusFound, "/me/settings"}
}
// show invite instructions
p := struct {
*UserPage
Invite *Invite
Expired bool
}{
UserPage: NewUserPage(app, r, u, "Invite to "+app.cfg.App.SiteName, nil),
Invite: i,
Expired: expired,
}
showUserPage(w, "invite-help", p)
return nil
}
p := struct {
page.StaticPage
*OAuthButtons
Error string
Flashes []template.HTML
Invite string
}{
StaticPage: pageForReq(app, r),
OAuthButtons: NewOAuthButtons(app.cfg),
Invite: inviteCode,
}
if expired {
p.Error = "This invite link has expired."
}
// Tell search engines not to index invite links
w.Header().Set("X-Robots-Tag", "noindex")
// Get error messages
session, err := app.sessionStore.Get(r, cookieName)
if err != nil {
// Ignore this
log.Error("Unable to get session in handleViewInvite; ignoring: %v", err)
}
flashes, _ := getSessionFlashes(app, w, r, session)
for _, flash := range flashes {
p.Flashes = append(p.Flashes, template.HTML(flash))
}
// Show landing page
return renderPage(w, "signup.tmpl", p)
}

72
jobs.go Normal file
View File

@ -0,0 +1,72 @@
package writefreely
import (
"github.com/writeas/web-core/log"
"time"
)
type PostJob struct {
ID int64
PostID string
Action string
Delay int64
}
func addJob(app *App, p *PublicPost, action string, delay int64) error {
j := &PostJob{
PostID: p.ID,
Action: action,
Delay: delay,
}
return app.db.InsertJob(j)
}
func startPublishJobsQueue(app *App) {
t := time.NewTicker(62 * time.Second)
for {
log.Info("[jobs] Done.")
<-t.C
log.Info("[jobs] Fetching email publish jobs...")
jobs, err := app.db.GetJobsToRun("email")
if err != nil {
log.Error("[jobs] %s - Skipping.", err)
continue
}
log.Info("[jobs] Running %d email publish jobs...", len(jobs))
err = runJobs(app, jobs, true)
if err != nil {
log.Error("[jobs] Failed: %s", err)
}
}
}
func runJobs(app *App, jobs []*PostJob, reqColl bool) error {
for _, j := range jobs {
p, err := app.db.GetPost(j.PostID, 0)
if err != nil {
log.Info("[job #%d] Unable to get post: %s", j.ID, err)
continue
}
if !p.CollectionID.Valid && reqColl {
log.Info("[job #%d] Post %s not part of a collection", j.ID, p.ID)
app.db.DeleteJob(j.ID)
continue
}
coll, err := app.db.GetCollectionByID(p.CollectionID.Int64)
if err != nil {
log.Info("[job #%d] Unable to get collection: %s", j.ID, err)
continue
}
coll.hostName = app.cfg.App.Host
coll.ForPublic()
p.Collection = &CollectionObj{Collection: *coll}
err = emailPost(app, p, p.Collection.ID)
if err != nil {
log.Error("[job #%d] Failed to email post %s", j.ID, p.ID)
continue
}
log.Info("[job #%d] Success for post %s.", j.ID, p.ID)
app.db.DeleteJob(j.ID)
}
return nil
}

69
key/key.go Normal file
View File

@ -0,0 +1,69 @@
/*
* Copyright © 2019, 2021 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 key holds application keys and utilities around generating them.
package key
import (
"crypto/rand"
)
const (
EncKeysBytes = 32
)
type Keychain struct {
EmailKey, CookieAuthKey, CookieKey, CSRFKey []byte
}
// GenerateKeys generates necessary keys for the app on the given Keychain,
// skipping any that already exist.
func (keys *Keychain) GenerateKeys() error {
// Generate keys only if they don't already exist
// TODO: use something like https://github.com/hashicorp/go-multierror to return errors
var err, keyErrs error
if len(keys.EmailKey) == 0 {
keys.EmailKey, err = GenerateBytes(EncKeysBytes)
if err != nil {
keyErrs = err
}
}
if len(keys.CookieAuthKey) == 0 {
keys.CookieAuthKey, err = GenerateBytes(EncKeysBytes)
if err != nil {
keyErrs = err
}
}
if len(keys.CookieKey) == 0 {
keys.CookieKey, err = GenerateBytes(EncKeysBytes)
if err != nil {
keyErrs = err
}
}
if len(keys.CSRFKey) == 0 {
keys.CSRFKey, err = GenerateBytes(EncKeysBytes)
if err != nil {
keyErrs = err
}
}
return keyErrs
}
// GenerateBytes returns securely generated random bytes.
func GenerateBytes(n int) ([]byte, error) {
b := make([]byte, n)
_, err := rand.Read(b)
if err != nil {
return nil, err
}
return b, nil
}

81
keys.go
View File

@ -1,31 +1,74 @@
/*
* Copyright © 2018-2019, 2021 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 (
"io/ioutil"
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/key"
"os"
"path/filepath"
)
type keychain struct {
emailKey, cookieAuthKey, cookieKey []byte
}
const (
keysDir = "keys"
)
func initKeys(app *app) error {
var err error
app.keys = &keychain{}
var (
emailKeyPath = filepath.Join(keysDir, "email.aes256")
cookieAuthKeyPath = filepath.Join(keysDir, "cookies_auth.aes256")
cookieKeyPath = filepath.Join(keysDir, "cookies_enc.aes256")
csrfKeyPath = filepath.Join(keysDir, "csrf.aes256")
)
app.keys.emailKey, err = ioutil.ReadFile("keys/email.aes256")
// InitKeys loads encryption keys into memory via the given Apper interface
func InitKeys(apper Apper) error {
log.Info("Loading encryption keys...")
err := apper.LoadKeys()
if err != nil {
return err
}
app.keys.cookieAuthKey, err = ioutil.ReadFile("keys/cookies_auth.aes256")
if err != nil {
return err
}
app.keys.cookieKey, err = ioutil.ReadFile("keys/cookies_enc.aes256")
if err != nil {
return err
}
return nil
}
func initKeyPaths(app *App) {
emailKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, emailKeyPath)
cookieAuthKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, cookieAuthKeyPath)
cookieKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, cookieKeyPath)
csrfKeyPath = filepath.Join(app.cfg.Server.KeysParentDir, csrfKeyPath)
}
// generateKey generates a key at the given path used for the encryption of
// certain user data. Because user data becomes unrecoverable without these
// keys, this won't overwrite any existing key, and instead outputs a message.
func generateKey(path string) error {
// Check if key file exists
if _, err := os.Stat(path); err == nil {
log.Info("%s already exists. rm the file if you understand the consequences.", path)
return nil
} else if !os.IsNotExist(err) {
log.Error("%s", err)
return err
}
log.Info("Generating %s.", path)
b, err := key.GenerateBytes(key.EncKeysBytes)
if err != nil {
log.Error("FAILED. %s. Run writefreely --gen-keys again.", err)
return err
}
err = os.WriteFile(path, b, 0600)
if err != nil {
log.Error("FAILED writing file: %s", err)
return err
}
log.Info("Success.")
return nil
}

25
keys.sh
View File

@ -1,25 +0,0 @@
#!/bin/bash
#
# keys.sh generates keys used for the encryption of certain user data. Because
# user data becomes unrecoverable without these keys, the script and won't
# overwrite any existing keys unless you explicitly delete them.
#
# Generate cookie encryption and authentication keys
if [[ ! -e "$(pwd)/keys/cookies_enc.aes256" ]]; then
dd of=$(pwd)/keys/cookies_enc.aes256 if=/dev/urandom bs=32 count=1
else
echo "cookies key already exists! rm keys/cookies_enc.aes256 if you understand the consquences."
fi
if [[ ! -e "$(pwd)/keys/cookies_auth.aes256" ]]; then
dd of=$(pwd)/keys/cookies_auth.aes256 if=/dev/urandom bs=32 count=1
else
echo "cookies authentication key already exists! rm keys/cookies_auth.aes256 if you understand the consquences."
fi
# Generate email encryption key
if [[ ! -e "$(pwd)/keys/email.aes256" ]]; then
dd of=$(pwd)/keys/email.aes256 if=/dev/urandom bs=32 count=1
else
echo "email key already exists! rm keys/email.aes256 if you understand the consquences."
fi

View File

@ -1,4 +1,4 @@
Keys
====
Contains keys for encrypting database and session data. Generate necessary keys by running (from the root of the project) `./keys.sh`.
Contains keys for encrypting database and session data. Generate necessary keys by running (from the root of the project) `writefreely --gen-keys`.

View File

@ -1,18 +1,13 @@
ifeq ($(shell which lessc),/usr/bin/lessc)
LESSC=/usr/bin/lessc
else
LESSC=node_modules/.bin/lessc
endif
export LESSC
CSSDIR=../static/css/
all :
$(LESSC) app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css
$(LESSC) fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css
$(LESSC) icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css
@command -v lessc >/dev/null 2>&1 || { echo >&2 "lessc is not installed, please run: make install or: less/install-less.sh"; exit 1; }
lessc app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css
lessc fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css
lessc icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css
lessc prose.less --clean-css="--s1 --advanced" $(CSSDIR)prose.css
install :
install :
./install-less.sh
$(MAKE) all

128
less/admin.less Normal file
View File

@ -0,0 +1,128 @@
.edit-page {
font-size: 1em;
min-height: 12em;
}
header.admin {
margin: 0;
h1 + a {
margin-left: 1em;
}
}
nav#admin {
display: block;
margin: 0.5em 0;
a {
margin-left: 0;
.rounded(.25em);
border: 0;
&.selected {
background: #dedede;
font-weight: bold;
.blip {
color: black;
}
}
}
.blip {
font-weight: bold;
}
}
.pager {
display: flex;
justify-content: center;
&:not(.pages) {
display: block;
margin: 0.5em 0;
a {
margin-left: 0;
.rounded(.25em);
&+a {
margin-left: 0.5em;
}
}
}
a {
color: #333;
font-family: @sansFont;
font-size: 0.86em;
padding: 0.5em 1em;
border: 1px solid #ccc;
&:hover {
text-decoration: none;
background: #efefef;
}
&.selected {
cursor: default;
background: #ccc;
}
}
&.sub {
margin: 1em 0 2em;
a:not(.toggle) {
border: 0;
border-bottom: 2px transparent solid;
.rounded(0);
padding: 0.5em;
margin-left: 0.5em;
margin-right: 0.5em;
&:hover {
color: @primary;
background: transparent;
}
&.selected {
color: @primary;
background: transparent;
border-bottom-color: @primary;
}
&+a {
margin-left: 1em;
}
}
a.toggle {
margin-top: -0.5em;
float: right;
}
}
}
.admin-actions {
.btn {
font-family: @sansFont;
font-size: 0.86em;
}
}
.features {
margin: 1em 0;
div {
&:first-child {
font-weight: bold;
}
&+div {
padding-left: 1em;
}
p {
font-weight: normal;
margin: 0.5rem 0;
font-size: 0.86em;
color: #666;
}
}
}
@media (max-width: 600px) {
div.row.features {
align-items: start;
}
.features div + div {
padding-left: 0;
}
}

View File

@ -4,6 +4,9 @@
@import "pad-theme";
@import "post-temp";
@import "effects";
@import "admin";
@import "login";
@import "pages/error";
@import "resources";
@import "lib/elements";
@import "lib/material";

View File

@ -1,20 +1,9 @@
@primary: rgb(114, 120, 191);
@secondary: rgb(114, 191, 133);
@subheaders: #444;
@headerTextColor: black;
@sansFont: 'Open Sans', 'Segoe UI', Tahoma, Arial, sans-serif;
@serifFont: Lora, 'Palatino Linotype', 'Book Antiqua', 'New York', 'DejaVu serif', serif;
@monoFont: Hack, consolas, Menlo-Regular, Menlo, Monaco, 'ubuntu mono', monospace, monospace;
@dangerCol: #e21d27;
@errUrgentCol: #ecc63c;
@proSelectedCol: #71D571;
@textLinkColor: rgb(0, 0, 238);
body {
font-family: @serifFont;
font-size-adjust: 0.5;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: white;
color: #111;
h1, header h2 {
@ -46,6 +35,11 @@ body {
}
}
blockquote {
p + p {
margin: -2em 0 0.5em;
}
}
article {
margin-bottom: 2em !important;
@ -55,18 +49,18 @@ body {
}
hr + p, ol, ul {
display: block;
margin-top: -1em;
margin-bottom: -1em;
margin-top: -1rem;
margin-bottom: -1rem;
}
ol, ul {
margin: 0.75em 0 -1em;
}
ul {
padding: 0 0 0 2em;
margin: 2rem 0 -1rem;
ol, ul {
margin: 1.25rem 0 -0.5rem;
}
}
li {
margin-top: -0.5em;
margin-bottom: -0.5em;
margin-top: -0.5rem;
margin-bottom: -0.5rem;
}
h2#title {
.article-title;
@ -75,7 +69,7 @@ body {
font-size: 1.5em;
}
h2 {
font-size: 1.17em;
font-size: 1.4em;
}
}
@ -102,9 +96,13 @@ body {
code {
.article-code;
}
img, video {
img, video, audio {
max-width: 100%;
}
audio {
width: 100%;
white-space: initial;
}
pre {
.code-block;
@ -212,6 +210,10 @@ body {
pre {
line-height: 1.5;
}
.flash {
text-align: center;
margin-bottom: 4em;
}
}
&#subpage {
#wrapper {
@ -247,6 +249,8 @@ body {
margin-bottom: 0.25em;
&+time {
display: block;
margin-top: 0.25em;
margin-bottom: 0.25em;
}
}
time {
@ -393,6 +397,39 @@ body {
}
}
img {
&.paid {
height: 0.86em;
vertical-align: middle;
margin-bottom: 0.1em;
}
}
nav#full-nav {
margin: 0;
.left-side {
display: inline-block;
a:first-child {
margin-left: 0;
}
}
.right-side {
float: right;
}
}
nav#full-nav a.simple-btn, .tool button {
font-family: @sansFont;
border: 1px solid #ccc !important;
padding: .5rem 1rem;
margin: 0;
.rounded(.25em);
text-decoration: none;
}
.post-title {
a {
&:link {
@ -479,13 +516,20 @@ abbr {
body#collection article p, body#subpage article p {
.article-p;
}
pre, body#post article, body#collection article, body#subpage article, body#subpage #wrapper h1 {
pre, body#post article, #post .alert, #subpage .alert, body#collection article, body#subpage article, body#subpage #wrapper h1 {
max-width: 40rem;
margin: 0 auto;
}
textarea, pre, body#post article, body#collection article p {
#collection header .alert, #post .alert, #subpage .alert {
margin-bottom: 1em;
p {
text-align: left;
line-height: 1.5;
}
}
textarea, input#title, pre, body#post article, body#collection article p {
&.norm, &.sans, &.wrap {
line-height: 1.4em;
line-height: 1.5;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
@ -493,7 +537,7 @@ textarea, pre, body#post article, body#collection article p {
word-wrap: break-word; /* Internet Explorer 5.5+ */
}
}
textarea, pre, body#post article, body#collection article, body#subpage article, span, .font {
textarea, input#title, pre, body#post article, body#collection article, body#subpage article, span, .font {
&.norm {
font-family: @serifFont;
}
@ -595,10 +639,30 @@ table.classy {
}
}
article table {
border-spacing: 0;
border-collapse: collapse;
width: 100%;
th {
border-width: 1px 1px 2px 1px;
border-style: solid;
border-color: #ccc;
}
td {
border-width: 0 1px 1px 1px;
border-style: solid;
border-color: #ccc;
padding: .25rem .5rem;
}
}
body#collection article, body#subpage article {
padding-top: 0;
padding-bottom: 0;
.book {
h2 {
font-size: 1.4em;
}
a.hidden.action {
color: #666;
float: right;
@ -635,20 +699,22 @@ table.downloads {
select.inputform, textarea.inputform {
border: 1px solid #999;
background: white;
}
input, button, select.inputform, textarea.inputform {
input, button, select.inputform, textarea.inputform, a.btn {
padding: 0.5em;
font-family: @serifFont;
font-size: 100%;
.rounded(.25em);
&[type=submit], &.submit {
&[type=submit], &.submit, &.cta {
border: 1px solid @primary;
background: @primary;
color: white;
.transition(0.2s);
&:hover {
background-color: lighten(@primary, 3%);
text-decoration: none;
}
&:disabled {
cursor: default;
@ -678,6 +744,31 @@ input, button, select.inputform, textarea.inputform {
}
}
.btn.pager {
border: 1px solid @lightNavBorder;
font-size: .86em;
padding: .5em 1em;
white-space: nowrap;
font-family: @sansFont;
&:hover {
text-decoration: none;
background: @lightNavBorder;
}
}
.btn.cta.secondary, input[type=submit].secondary {
background: transparent;
color: @primary;
&:hover {
background-color: #f9f9f9;
}
}
.btn.cta.disabled {
background-color: desaturate(@primary, 100%) !important;
border-color: desaturate(@primary, 100%) !important;
}
div.flat-select {
display: inline-block;
position: relative;
@ -740,15 +831,15 @@ input {
margin: 0 auto 3em;
font-size: 1.2em;
&.toosmall {
max-width: 25em;
}
&.tight {
max-width: 30em;
}
&.snug {
max-width: 40em;
}
&.regular {
font-size: 1em;
}
.app {
+ .app {
margin-top: 1.5em;
@ -765,7 +856,7 @@ input {
font-weight: normal;
}
p {
line-height: 1.4;
line-height: 1.5;
}
li {
margin: 0.3em 0;
@ -820,20 +911,6 @@ input {
text-align: center;
}
}
div.features {
margin-top: 1.5em;
text-align: center;
font-size: 0.86em;
ul {
text-align: left;
max-width: 26em;
margin-left: auto !important;
margin-right: auto !important;
li.soon, span.soon {
color: lighten(#111, 40%);
}
}
}
div.blurbs {
>h2 {
text-align: center;
@ -917,7 +994,12 @@ footer.contain-me {
}
ul {
&.collections {
padding-left: 0;
margin-left: 0;
h3 {
margin-top: 0;
font-weight: normal;
}
li {
&.collection {
a.title {
@ -959,7 +1041,7 @@ footer.contain-me {
}
li {
line-height: 1.4;
line-height: 1.5;
.item-desc, .prog-lang {
font-size: 0.6em;
@ -991,6 +1073,19 @@ li {
background-color: #dff0d8;
border-color: #d6e9c6;
}
&.danger {
border-color: #856404;
background-color: white;
h3 {
margin: 0 0 0.5em 0;
font-size: 1em;
font-weight: bold;
color: black !important;
}
h3 + p, button {
font-size: 0.86em;
}
}
p {
margin: 0;
@ -1047,7 +1142,8 @@ body#pad-sub #posts, .atoms {
}
.electron {
font-weight: normal;
margin-left: 0.5em;
font-size: 0.86em;
margin-left: 0.75rem;
}
}
h3, h4 {
@ -1197,7 +1293,7 @@ header {
}
}
&.singleuser {
margin: 0.5em 0.25em;
margin: 0.5em 1em 0.5em 0.25em;
nav#user-nav {
nav > ul > li:first-child {
img {
@ -1205,6 +1301,9 @@ header {
}
}
}
.right-side {
padding-top: 0.5em;
}
}
.dash-nav {
font-weight: bold;
@ -1270,6 +1369,24 @@ form {
font-size: 0.86em;
line-height: 2;
}
&.prominent {
margin: 1em 0;
label {
font-weight: bold;
}
input, select {
width: 100%;
}
select {
font-size: 1em;
padding: 0.5rem;
display: block;
border-radius: 0.25rem;
margin: 0.5rem 0;
}
}
}
div.row {
display: flex;
@ -1279,6 +1396,16 @@ div.row {
}
}
.check, .blip {
font-size: 1.125em;
color: #71D571;
}
.ex.failure {
font-weight: bold;
color: @dangerCol;
}
@media all and (max-width: 450px) {
body#post {
header {
@ -1345,7 +1472,7 @@ div.row {
}
@media all and (max-width: 600px) {
div.row {
div.row:not(.admin-actions) {
flex-direction: column;
}
.half {
@ -1430,6 +1557,11 @@ div.row {
margin-left: 0;
margin-top: 0;
}
article {
.hidden {
.opacity(1);
}
}
}
@media print {
@ -1471,3 +1603,38 @@ div.row {
pre.code-block {
overflow-x: auto;
}
#emailsub {
text-align: center;
}
p#emailsub {
display: inline-block !important;
width: 100%;
font-style: italic;
}
#subscribe-btn {
margin-left: 0.5em;
}
#org-nav {
font-family: @sansFont;
font-size: 1.1em;
color: #888;
em, strong {
color: #000;
}
&+h1 {
margin-top: 0.5em;
}
a:link, a:visited, a:hover {
color: @accent;
}
a:first-child {
margin-right: 0.25em;
}
a.coll-name {
font-weight: bold;
margin-left: 0.25em;
}
}

View File

@ -29,36 +29,33 @@
font-family: 'Lora';
font-style: normal;
font-weight: 400;
src: url('/fonts/lora-v9-latin-regular.eot'); /* IE9 Compat Modes */
src: url('/fonts/Lora-Regular.eot'); /* IE9 Compat Modes */
src: local('Lora'), local('Lora-Regular'),
url('/fonts/lora-v9-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/lora-v9-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/lora-v9-latin-regular.woff') format('woff'), /* Modern Browsers */
url('/fonts/lora-v9-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/lora-v9-latin-regular.svg#Lora') format('svg'); /* Legacy iOS */
url('/fonts/Lora-Regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/Lora-Regular.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/Lora-Regular.woff') format('woff'), /* Modern Browsers */
url('/fonts/Lora-Regular.ttf') format('truetype'); /* Safari, Android, iOS */
}
/* lora-700 - latin */
@font-face {
font-family: 'Lora';
font-style: normal;
font-weight: 700;
src: url('/fonts/lora-v9-latin-700.eot'); /* IE9 Compat Modes */
src: url('/fonts/Lora-Bold.eot'); /* IE9 Compat Modes */
src: local('Lora Bold'), local('Lora-Bold'),
url('/fonts/lora-v9-latin-700.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/lora-v9-latin-700.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/lora-v9-latin-700.woff') format('woff'), /* Modern Browsers */
url('/fonts/lora-v9-latin-700.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/lora-v9-latin-700.svg#Lora') format('svg'); /* Legacy iOS */
url('/fonts/Lora-Bold.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/Lora-Bold.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/Lora-Bold.woff') format('woff'), /* Modern Browsers */
url('/fonts/Lora-Bold.ttf') format('truetype'); /* Safari, Android, iOS */
}
@font-face {
font-family: 'Lora';
font-style: italic;
font-weight: 400;
src: url('/fonts/lora-v10-latin_latin-ext-italic.eot'); /* IE9 Compat Modes */
src: local('Lora Italic'), local('Lora-Italic'),
url('/fonts/lora-v10-latin-italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/lora-v10-latin-italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/lora-v10-latin-italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/lora-v10-latin-italic.ttf') format('truetype'), /* Safari, Android, iOS */
url('/fonts/lora-v10-latin-italic.svg#Lora') format('svg'); /* Legacy iOS */
font-family: 'Lora';
font-style: italic;
font-weight: 400;
src: url('/fonts/Lora-Italic.eot'); /* IE9 Compat Modes */
src: local('Lora Italic'), local('Lora-Italic'),
url('/fonts/Lora-Italic.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('/fonts/Lora-Italic.woff2') format('woff2'), /* Super Modern Browsers */
url('/fonts/Lora-Italic.woff') format('woff'), /* Modern Browsers */
url('/fonts/Lora-Italic.ttf') format('truetype'); /* Safari, Android, iOS */
}

View File

@ -2,7 +2,7 @@
# Install Less via npm
if [ ! -e "$(which lessc)" ]; then
sudo npm install -g less
sudo npm install -g less@3.5.3
sudo npm install -g less-plugin-clean-css
else
echo LESS $(npm view less version 2>&1 | grep -v WARN) is installed

91
less/login.less Normal file
View File

@ -0,0 +1,91 @@
/*
* Copyright © 2020 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.
*/
.row.signinbtns {
justify-content: center;
font-size: 1em;
margin-top: 2em;
margin-bottom: 1em;
flex-wrap: wrap;
.loginbtn {
height: 40px;
margin: 0.5em;
&.btn {
box-sizing: border-box;
font-size: 17px;
white-space: nowrap;
img {
height: 1.5em;
vertical-align: middle;
}
}
&#writeas-login, &#slack-login {
img {
margin-top: -0.2em;
}
}
&#gitlab-login {
background-color: #fc6d26;
border-color: #fc6d26;
&:hover {
background-color: darken(#fc6d26, 5%);
border-color: darken(#fc6d26, 5%);
}
}
&#gitea-login {
background-color: #2ecc71;
border-color: #2ecc71;
&:hover {
background-color: #2cc26b;
border-color: #2cc26b;
}
}
&#slack-login, &#gitlab-login, &#gitea-login, &#generic-oauth-login {
font-size: 0.86em;
font-family: @sansFont;
}
&#slack-login, &#generic-oauth-login {
color: @lightTextColor;
background-color: @lightNavBG;
border-color: @lightNavBorder;
&:hover {
background-color: @lightNavHoverBG;
}
}
}
}
.or {
text-align: center;
margin-bottom: 3.5em;
p {
display: inline-block;
background-color: white;
padding: 0 1em;
}
hr {
margin-top: -1.6em;
margin-bottom: 0;
}
hr.short {
max-width: 30rem;
}
}

View File

@ -1,4 +1,4 @@
@actionNavColor: #999;
@actionNavColor: #767676;
body {
margin: 0;
@ -58,7 +58,7 @@ header {
}
p {
&.description {
color: #666;
color: #444;
font-size: 1.1em;
margin-top: 0.5em;
line-height: 1.5;
@ -113,7 +113,7 @@ textarea {
ul {
margin: 0;
padding: 0 0 0 1em;
line-height: 1.4;
line-height: 1.5;
&.collections, &.posts, &.integrations {
list-style: none;
@ -127,7 +127,6 @@ textarea {
&.collection {
a.title {
font-size: 1.3em;
font-weight: bold;
}
}
}
@ -206,7 +205,7 @@ code, textarea#embed {
font-weight: normal;
}
p {
line-height: 1.4;
line-height: 1.5;
}
li {
margin: 0.3em 0;

View File

@ -63,7 +63,7 @@ body#pad, body#pad-sub {
}
}
#belt {
a {
a, button {
color: #000;
}
}
@ -100,7 +100,7 @@ body#pad, body#pad-sub {
}
}
#belt {
a {
a, button {
color: white;
}
}
@ -188,18 +188,18 @@ body#pad, body#pad-sub {
body#pad {
.pad-theme-transition;
textarea {
textarea, #title {
.pad-theme-transition;
}
&.dark {
textarea {
textarea, #title, #editor {
background-color: @darkBG;
color: @darkTextColor;
}
}
&.light {
textarea {
textarea, #title, #editor {
background-color: @lightBG;
color: @lightTextColor;
}

View File

@ -60,7 +60,7 @@
&:hover {
background: @lightNavHoverBG;
}
&:hover > ul {
&:hover > ul, &.open > ul {
display: block;
}
&.selected {
@ -222,6 +222,13 @@ body#pad, body#pad-sub {
font-style: italic;
}
}
button {
font-family: @sansFont;
background-color: transparent;
padding-top: 0.25rem;
padding-bottom: 0.25rem;
border: 0;
}
}
}
}
@ -249,7 +256,7 @@ body#pad {
border: 0;
outline: 0;
}
textarea {
textarea, #title {
position: fixed !important;
top: 3em;
right: 0;
@ -333,6 +340,15 @@ body#pad {
}
}
.body {
line-height: 1.5;
input[type=text].confirm {
width: 100%;
box-sizing: border-box;
}
}
.short {
text-align: center;
}
@ -354,12 +370,38 @@ body#pad {
z-index: 10;
}
body#pad .alert {
position: fixed;
bottom: 0.25em;
left: 2em;
right: 2em;
font-size: 1.1em;
&#edited-elsewhere {
&.hidden {
display: none;
}
a {
font-weight: bold;
}
}
}
@media all and (max-height: 500px) {
body#pad {
textarea {
top: 2.25em;
padding-top: 0.25em;
}
&.classic {
#editor {
top: 5.25em;
}
#title {
top: 3.5rem;
}
}
#tools {
padding-top: 0.5em;
padding-bottom: 0.5em;
@ -413,43 +455,63 @@ body#pad {
}
@media all and (min-width: 50em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 10%;
padding-right: 10%;
}
.alert {
left: 10%;
right: 10%;
}
}
}
@media all and (min-width: 60em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 15%;
padding-right: 15%;
}
.alert {
left: 15%;
right: 15%;
}
}
}
@media all and (min-width: 70em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 20%;
padding-right: 20%;
}
.alert {
left: 20%;
right: 20%;
}
}
}
@media all and (min-width: 85em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 25%;
padding-right: 25%;
}
.alert {
left: 25%;
right: 25%;
}
}
}
@media all and (min-width: 105em) {
body#pad {
textarea {
body#pad, body#pad.classic {
textarea, #title {
padding-left: 30%;
padding-right: 30%;
}
.alert {
left: 30%;
right: 30%;
}
}
}
@media (pointer: coarse) {

View File

@ -17,6 +17,16 @@ body {
font-size: 1.6em;
}
}
article {
h2#title.dated {
margin-bottom: 0.5em;
}
time.dt-published {
display: block;
color: #666;
margin-bottom: 1em;
}
}
}
}
@ -27,6 +37,25 @@ body#post article, pre, .hljs {
font-size: 1.2em;
}
p.split {
color: #6161FF;
font-style: italic;
font-size: 0.86em;
}
#readmore-sell {
padding: 1em 1em 2em;
background-color: #fafafa;
p.split {
color: black;
font-style: normal;
font-size: 1.4em;
}
.cta + .cta {
margin-left: 0.5em;
}
}
/* Post mixins */
.article-code() {
background-color: #f8f8f8;
@ -39,7 +68,7 @@ body#post article, pre, .hljs {
border-left: 4px solid #ddd;
padding: 0 1em;
margin: 0.5em;
color: #777;
color: #767676;
display: inline-block;
p {
@ -48,7 +77,7 @@ body#post article, pre, .hljs {
}
}
.article-p() {
line-height: 1.4em;
line-height: 1.5;
white-space: pre-wrap; /* CSS 3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */

490
less/prose-editor.less Normal file
View File

@ -0,0 +1,490 @@
@classicHorizMargin: 2rem;
body#pad.classic {
header {
display: flex;
justify-content: space-between;
align-items: center;
}
#editor {
top: 4em;
bottom: 1em;
}
#title {
top: 4.25rem;
bottom: unset;
height: auto;
font-weight: bold;
font-size: 2em;
padding: 0;
border: 0;
}
#tools {
#belt {
float: none;
}
}
#target {
ul {
a {
padding: 0 0.5em !important;
}
}
}
}
#title {
margin-left: @classicHorizMargin;
margin-right: @classicHorizMargin;
}
.ProseMirror {
position: relative;
height: calc(~"100% - 1.6em");
overflow-y: auto;
box-sizing: border-box;
-moz-box-sizing: border-box;
font-size: 1.2em;
word-wrap: break-word;
white-space: pre-wrap;
-webkit-font-variant-ligatures: none;
font-variant-ligatures: none;
padding: 0.5em @classicHorizMargin;
line-height: 1.5;
outline: none;
}
.ProseMirror pre {
white-space: pre-wrap;
}
.ProseMirror li {
position: relative;
}
.ProseMirror-hideselection *::selection {
background: transparent;
}
.ProseMirror-hideselection *::-moz-selection {
background: transparent;
}
.ProseMirror-hideselection {
caret-color: transparent;
}
.ProseMirror-selectednode {
outline: 2px solid #8cf;
}
/* Make sure li selections wrap around markers */
li.ProseMirror-selectednode {
outline: none;
}
li.ProseMirror-selectednode:after {
content: "";
position: absolute;
left: -32px;
right: -2px;
top: -2px;
bottom: -2px;
border: 2px solid #8cf;
pointer-events: none;
}
.ProseMirror-textblock-dropdown {
min-width: 3em;
}
.ProseMirror-menu {
margin: 0 -4px;
line-height: 1;
}
.ProseMirror-tooltip .ProseMirror-menu {
width: -webkit-fit-content;
width: fit-content;
white-space: pre;
}
.ProseMirror-menuitem {
margin-right: 3px;
display: inline-block;
div {
cursor: pointer;
}
}
.ProseMirror-menuseparator {
border-right: 1px solid #ddd;
margin-right: 3px;
}
.ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu {
font-size: 90%;
white-space: nowrap;
}
.ProseMirror-menu-dropdown {
vertical-align: 1px;
cursor: pointer;
position: relative;
padding-right: 15px;
}
.ProseMirror-menu-dropdown-wrap {
padding: 1px 0 1px 4px;
display: inline-block;
position: relative;
}
.ProseMirror-menu-dropdown:after {
content: "";
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 4px solid currentColor;
opacity: .6;
position: absolute;
right: 4px;
top: calc(50% - 2px);
}
.ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
position: absolute;
background: white;
color: #666;
border: 1px solid #aaa;
padding: 2px;
}
.ProseMirror-menu-dropdown-menu {
z-index: 15;
min-width: 6em;
}
.ProseMirror-menu-dropdown-item {
cursor: pointer;
padding: 2px 8px 2px 4px;
}
.ProseMirror-menu-dropdown-item:hover {
background: #f2f2f2;
}
.ProseMirror-menu-submenu-wrap {
position: relative;
margin-right: -4px;
}
.ProseMirror-menu-submenu-label:after {
content: "";
border-top: 4px solid transparent;
border-bottom: 4px solid transparent;
border-left: 4px solid currentColor;
opacity: .6;
position: absolute;
right: 4px;
top: calc(50% - 4px);
}
.ProseMirror-menu-submenu {
display: none;
min-width: 4em;
left: 100%;
top: -3px;
}
.ProseMirror-menu-active {
background: #eee;
border-radius: 4px;
}
.ProseMirror-menu-active {
background: #eee;
border-radius: 4px;
}
.ProseMirror-menu-disabled {
opacity: .3;
}
.ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
display: block;
}
.ProseMirror-menubar {
font-family: @sansFont;
position: relative;
min-height: 1em;
color: #666;
padding: 0.5em;
top: 0;
left: 0;
right: 0;
background: rgba(255, 255, 255, 0.8);
z-index: 10;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow: visible;
margin-left: @classicHorizMargin;
margin-right: @classicHorizMargin;
}
.ProseMirror-icon {
display: inline-block;
line-height: .8;
vertical-align: -2px; /* Compensate for padding */
padding: 2px 8px;
cursor: pointer;
}
.ProseMirror-menu-disabled.ProseMirror-icon {
cursor: default;
}
.ProseMirror-icon svg {
fill: currentColor;
height: 1em;
}
.ProseMirror-icon span {
vertical-align: text-top;
}
.ProseMirror-gapcursor {
display: none;
pointer-events: none;
position: absolute;
}
.ProseMirror-gapcursor:after {
content: "";
display: block;
position: absolute;
top: -2px;
width: 20px;
border-top: 1px solid black;
animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite;
}
@keyframes ProseMirror-cursor-blink {
to {
visibility: hidden;
}
}
.ProseMirror-focused .ProseMirror-gapcursor {
display: block;
}
/* Add space around the hr to make clicking it easier */
.ProseMirror-example-setup-style hr {
padding: 4px 10px;
border: none;
margin: 1em 0;
background: initial;
}
.ProseMirror-example-setup-style hr:after {
content: "";
display: block;
height: 1px;
background-color: #ccc;
line-height: 2px;
}
.ProseMirror ul, .ProseMirror ol {
padding-left: 30px;
}
.ProseMirror blockquote {
padding-left: 1em;
border-left: 4px solid #ddd;
color: #767676;
margin-left: 0;
margin-right: 0;
}
.ProseMirror-example-setup-style img {
cursor: default;
max-width: 100%;
}
.ProseMirror-prompt {
background: white;
padding: 1em;
border: 1px solid silver;
position: fixed;
border-radius: 0.25em;
z-index: 11;
box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);
}
.ProseMirror-prompt h5 {
margin: 0 0 0.75em;
font-family: @sansFont;
font-size: 100%;
color: #444;
}
.ProseMirror-prompt input[type="text"],
.ProseMirror-prompt textarea {
background: #eee;
border: none;
outline: none;
}
.ProseMirror-prompt input[type="text"] {
margin: 0.25em 0;
}
.ProseMirror-prompt-close {
position: absolute;
left: 2px;
top: 1px;
color: #666;
border: none;
background: transparent;
padding: 0;
}
.ProseMirror-prompt-close:after {
content: "✕";
font-size: 12px;
}
.ProseMirror-invalid {
background: #ffc;
border: 1px solid #cc7;
border-radius: 4px;
padding: 5px 10px;
position: absolute;
min-width: 10em;
}
.ProseMirror-prompt-buttons {
margin-top: 5px;
display: none;
}
#editor, .editor {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
color: black;
background-clip: padding-box;
padding: 5px 0;
margin: 4em auto 23px auto;
}
.dark #editor {
color: white;
}
.ProseMirror p:first-child,
.ProseMirror h1:first-child,
.ProseMirror h2:first-child,
.ProseMirror h3:first-child,
.ProseMirror h4:first-child,
.ProseMirror h5:first-child,
.ProseMirror h6:first-child {
margin-top: 10px;
}
.ProseMirror p {
margin-bottom: 1em;
}
textarea {
width: 100%;
height: 123px;
border: 1px solid silver;
box-sizing: border-box;
-moz-box-sizing: border-box;
padding: 3px 10px;
border: none;
outline: none;
font-family: inherit;
font-size: inherit;
}
.ProseMirror-menubar-wrapper {
height: 100%;
box-sizing: border-box;
}
.ProseMirror-menubar-wrapper, #markdown textarea {
display: block;
margin-bottom: 4px;
}
.editorreadmore {
color: @textLinkColor;
text-decoration: underline;
text-align: center;
width: 100%;
}
@media all and (min-width: 50em) {
#photo-upload label {
display: inline;
}
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 10%;
margin-right: 10%;
}
.ProseMirror {
padding-left: 10%;
padding-right: 10%;
}
}
@media all and (min-width: 60em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 15%;
margin-right: 15%;
}
.ProseMirror {
padding-left: 15%;
padding-right: 15%;
}
}
@media all and (min-width: 70em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 20%;
margin-right: 20%;
}
.ProseMirror {
padding-left: 20%;
padding-right: 20%;
}
}
@media all and (min-width: 85em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 25%;
margin-right: 25%;
}
.ProseMirror {
padding-left: 25%;
padding-right: 25%;
}
}
@media all and (min-width: 105em) {
.ProseMirror-menubar, #title, #photo-upload {
margin-left: 30%;
margin-right: 30%;
}
.ProseMirror {
padding-left: 30%;
padding-right: 30%;
}
}

4
less/prose.less Normal file
View File

@ -0,0 +1,4 @@
@import "prose-editor";
@import "pad-theme";
@import "resources";
@import "lib/elements";

13
less/resources.less Normal file
View File

@ -0,0 +1,13 @@
@primary: rgb(114, 120, 191);
@secondary: rgb(114, 191, 133);
@subheaders: #444;
@headerTextColor: black;
@sansFont: 'Open Sans', 'Segoe UI', Tahoma, Arial, sans-serif;
@serifFont: Lora, 'Palatino Linotype', 'Book Antiqua', 'New York', 'DejaVu serif', serif;
@monoFont: Hack, consolas, Menlo-Regular, Menlo, Monaco, 'ubuntu mono', monospace, monospace;
@dangerCol: #e21d27;
@errUrgentCol: #ecc63c;
@proSelectedCol: #71D571;
@textLinkColor: rgb(0, 0, 238);
@accent: #767676;

153
main_test.go Normal file
View File

@ -0,0 +1,153 @@
package writefreely
import (
"context"
"database/sql"
"encoding/gob"
"errors"
"fmt"
uuid "github.com/nu7hatch/gouuid"
"github.com/stretchr/testify/assert"
"math/rand"
"os"
"strings"
"testing"
"time"
)
var testDB *sql.DB
type ScopedTestBody func(*sql.DB)
// TestMain provides testing infrastructure within this package.
func TestMain(m *testing.M) {
rand.Seed(time.Now().UTC().UnixNano())
gob.Register(&User{})
if runMySQLTests() {
var err error
testDB, err = initMySQL(os.Getenv("WF_USER"), os.Getenv("WF_PASSWORD"), os.Getenv("WF_DB"), os.Getenv("WF_HOST"))
if err != nil {
fmt.Println(err)
return
}
}
code := m.Run()
if runMySQLTests() {
if closeErr := testDB.Close(); closeErr != nil {
fmt.Println(closeErr)
}
}
os.Exit(code)
}
func runMySQLTests() bool {
return len(os.Getenv("TEST_MYSQL")) > 0
}
func initMySQL(dbUser, dbPassword, dbName, dbHost string) (*sql.DB, error) {
if dbUser == "" || dbPassword == "" {
return nil, errors.New("database user or password not set")
}
if dbHost == "" {
dbHost = "localhost"
}
if dbName == "" {
dbName = "writefreely"
}
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8mb4&parseTime=true", dbUser, dbPassword, dbHost, dbName)
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
if err := ensureMySQL(db); err != nil {
return nil, err
}
return db, nil
}
func ensureMySQL(db *sql.DB) error {
if err := db.Ping(); err != nil {
return err
}
db.SetMaxOpenConns(250)
return nil
}
// withTestDB provides a scoped database connection.
func withTestDB(t *testing.T, testBody ScopedTestBody) {
db, cleanup, err := newTestDatabase(testDB,
os.Getenv("WF_USER"),
os.Getenv("WF_PASSWORD"),
os.Getenv("WF_DB"),
os.Getenv("WF_HOST"),
)
assert.NoError(t, err)
defer func() {
assert.NoError(t, cleanup())
}()
testBody(db)
}
// newTestDatabase creates a new temporary test database. When a test
// database connection is returned, it will have created a new database and
// initialized it with tables from a reference database.
func newTestDatabase(base *sql.DB, dbUser, dbPassword, dbName, dbHost string) (*sql.DB, func() error, error) {
var err error
var baseName = dbName
if baseName == "" {
row := base.QueryRow("SELECT DATABASE()")
err := row.Scan(&baseName)
if err != nil {
return nil, nil, err
}
}
tUUID, _ := uuid.NewV4()
suffix := strings.Replace(tUUID.String(), "-", "_", -1)
newDBName := baseName + suffix
_, err = base.Exec("CREATE DATABASE " + newDBName)
if err != nil {
return nil, nil, err
}
newDB, err := initMySQL(dbUser, dbPassword, newDBName, dbHost)
if err != nil {
return nil, nil, err
}
rows, err := base.Query("SHOW TABLES IN " + baseName)
if err != nil {
return nil, nil, err
}
for rows.Next() {
var tableName string
if err := rows.Scan(&tableName); err != nil {
return nil, nil, err
}
query := fmt.Sprintf("CREATE TABLE %s LIKE %s.%s", tableName, baseName, tableName)
if _, err := newDB.Exec(query); err != nil {
return nil, nil, err
}
}
cleanup := func() error {
if closeErr := newDB.Close(); closeErr != nil {
fmt.Println(closeErr)
}
_, err = base.Exec("DROP DATABASE " + newDBName)
return err
}
return newDB, cleanup, nil
}
func countRows(t *testing.T, ctx context.Context, db *sql.DB, count int, query string, args ...interface{}) {
var returned int
err := db.QueryRowContext(ctx, query, args...).Scan(&returned)
assert.NoError(t, err, "error executing query %s and args %s", query, args)
assert.Equal(t, count, returned, "unexpected return count %d, expected %d from %s and args %s", returned, count, query, args)
}

110
migrations/drivers.go Normal file
View File

@ -0,0 +1,110 @@
/*
* Copyright © 2019 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
import (
"fmt"
)
// TODO: use now() from writefreely pkg
func (db *datastore) now() string {
if db.driverName == driverSQLite {
return "strftime('%Y-%m-%d %H:%M:%S','now')"
}
return "NOW()"
}
func (db *datastore) typeInt() string {
if db.driverName == driverSQLite {
return "INTEGER"
}
return "INT"
}
func (db *datastore) typeSmallInt() string {
if db.driverName == driverSQLite {
return "INTEGER"
}
return "SMALLINT"
}
func (db *datastore) typeTinyInt() string {
if db.driverName == driverSQLite {
return "INTEGER"
}
return "TINYINT"
}
func (db *datastore) typeText() string {
return "TEXT"
}
func (db *datastore) typeChar(l int) string {
if db.driverName == driverSQLite {
return "TEXT"
}
return fmt.Sprintf("CHAR(%d)", l)
}
func (db *datastore) typeVarChar(l int) string {
if db.driverName == driverSQLite {
return "TEXT"
}
return fmt.Sprintf("VARCHAR(%d)", l)
}
func (db *datastore) typeVarBinary(l int) string {
if db.driverName == driverSQLite {
return "BLOB"
}
return fmt.Sprintf("VARBINARY(%d)", l)
}
func (db *datastore) typeBool() string {
if db.driverName == driverSQLite {
return "INTEGER"
}
return "TINYINT(1)"
}
func (db *datastore) typeDateTime() string {
return "DATETIME"
}
func (db *datastore) typeIntPrimaryKey() string {
if db.driverName == driverSQLite {
// From docs: "In SQLite, a column with type INTEGER PRIMARY KEY is an alias for the ROWID (except in WITHOUT
// ROWID tables) which is always a 64-bit signed integer."
return "INTEGER PRIMARY KEY"
}
return "INT AUTO_INCREMENT PRIMARY KEY"
}
func (db *datastore) collateMultiByte() string {
if db.driverName == driverSQLite {
return ""
}
return " COLLATE utf8_bin"
}
func (db *datastore) engine() string {
if db.driverName == driverSQLite {
return ""
}
return " ENGINE = InnoDB"
}
func (db *datastore) after(colName string) string {
if db.driverName == driverSQLite {
return ""
}
return " AFTER " + colName
}

150
migrations/migrations.go Normal file
View File

@ -0,0 +1,150 @@
/*
* Copyright © 2019 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 contains database migrations for WriteFreely
package migrations
import (
"database/sql"
"github.com/writeas/web-core/log"
)
// TODO: refactor to use the datastore struct from writefreely pkg
type datastore struct {
*sql.DB
driverName string
}
func NewDatastore(db *sql.DB, dn string) *datastore {
return &datastore{db, dn}
}
// TODO: use these consts from writefreely pkg
const (
driverMySQL = "mysql"
driverSQLite = "sqlite3"
)
type Migration interface {
Description() string
Migrate(db *datastore) error
}
type migration struct {
description string
migrate func(db *datastore) error
}
func New(d string, fn func(db *datastore) error) Migration {
return &migration{d, fn}
}
func (m *migration) Description() string {
return m.description
}
func (m *migration) Migrate(db *datastore) error {
return m.migrate(db)
}
var migrations = []Migration{
New("support user invites", supportUserInvites), // -> V1 (v0.8.0)
New("support dynamic instance pages", supportInstancePages), // V1 -> V2 (v0.9.0)
New("support users suspension", supportUserStatus), // V2 -> V3 (v0.11.0)
New("support oauth", oauth), // V3 -> V4
New("support slack oauth", oauthSlack), // V4 -> v5
New("support ActivityPub mentions", supportActivityPubMentions), // V5 -> V6
New("support oauth attach", oauthAttach), // V6 -> V7
New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0)
New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9
New("support post signatures", supportPostSignatures), // V9 -> V10 (v0.13.0)
New("Widen oauth_users.access_token", widenOauthAcceesToken), // V10 -> V11
New("support verifying fedi profile", fediverseVerifyProfile), // V11 -> V12 (v0.14.0)
New("support newsletters", supportLetters), // V12 -> V13
New("support password resetting", supportPassReset), // V13 -> V14
New("speed up blog post retrieval", addPostRetrievalIndex), // V14 -> V15
}
// CurrentVer returns the current migration version the application is on
func CurrentVer() int {
return len(migrations)
}
func SetInitialMigrations(db *datastore) error {
// Included schema files represent changes up to V1, so note that in the database
_, err := db.Exec("INSERT INTO appmigrations (version, migrated, result) VALUES (?, "+db.now()+", ?)", 1, "")
if err != nil {
return err
}
return nil
}
func Migrate(db *datastore) error {
var version int
var err error
if db.tableExists("appmigrations") {
err = db.QueryRow("SELECT MAX(version) FROM appmigrations").Scan(&version)
if err != nil {
return err
}
} else {
log.Info("Initializing appmigrations table...")
version = 0
_, err = db.Exec(`CREATE TABLE appmigrations (
version ` + db.typeInt() + ` NOT NULL,
migrated ` + db.typeDateTime() + ` NOT NULL,
result ` + db.typeText() + ` NOT NULL
) ` + db.engine() + `;`)
if err != nil {
return err
}
}
if len(migrations[version:]) > 0 {
for i, m := range migrations[version:] {
curVer := version + i + 1
log.Info("Migrating to V%d: %s", curVer, m.Description())
err = m.Migrate(db)
if err != nil {
return err
}
// Update migrations table
_, err = db.Exec("INSERT INTO appmigrations (version, migrated, result) VALUES (?, "+db.now()+", ?)", curVer, "")
if err != nil {
return err
}
}
} else {
log.Info("Database up-to-date. No migrations to run.")
}
return nil
}
func (db *datastore) tableExists(t string) bool {
var dummy string
var err error
if db.driverName == driverSQLite {
err = db.QueryRow("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", t).Scan(&dummy)
} else {
err = db.QueryRow("SHOW TABLES LIKE '" + t + "'").Scan(&dummy)
}
switch {
case err == sql.ErrNoRows:
return false
case err != nil:
log.Error("Couldn't SHOW TABLES: %v", err)
return false
}
return true
}

49
migrations/v1.go Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright © 2019 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 supportUserInvites(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`CREATE TABLE userinvites (
id ` + db.typeChar(6) + ` NOT NULL ,
owner_id ` + db.typeInt() + ` NOT NULL ,
max_uses ` + db.typeSmallInt() + ` NULL ,
created ` + db.typeDateTime() + ` NOT NULL ,
expires ` + db.typeDateTime() + ` NULL ,
inactive ` + db.typeBool() + ` NOT NULL ,
PRIMARY KEY (id)
) ` + db.engine() + `;`)
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`CREATE TABLE usersinvited (
invite_id ` + db.typeChar(6) + ` NOT NULL ,
user_id ` + db.typeInt() + ` NOT NULL ,
PRIMARY KEY (invite_id, user_id)
) ` + db.engine() + `;`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

33
migrations/v10.go Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright © 2020 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 supportPostSignatures(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE collections ADD COLUMN post_signature ` + db.typeText() + db.collateMultiByte() + ` NULL` + db.after("script"))
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

38
migrations/v11.go Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright © 2020 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
/**
* Widen `oauth_users.access_token`, necessary only for mysql
*/
func widenOauthAcceesToken(db *datastore) error {
if db.driverName == driverMySQL {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE oauth_users MODIFY COLUMN access_token ` + db.typeText() + db.collateMultiByte() + ` NULL`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
}
return nil
}

33
migrations/v12.go Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright © 2023 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 fediverseVerifyProfile(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE remoteusers ADD COLUMN url ` + db.typeVarChar(255) + ` NULL` + db.after("shared_inbox"))
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

58
migrations/v13.go Normal file
View File

@ -0,0 +1,58 @@
/*
* Copyright © 2021 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 supportLetters(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`CREATE TABLE publishjobs (
id ` + db.typeIntPrimaryKey() + `,
post_id ` + db.typeVarChar(16) + ` not null,
action ` + db.typeVarChar(16) + ` not null,
delay ` + db.typeTinyInt() + ` not null
)`)
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`CREATE TABLE emailsubscribers (
id ` + db.typeChar(8) + ` not null,
collection_id ` + db.typeInt() + ` not null,
user_id ` + db.typeInt() + ` null,
email ` + db.typeVarChar(255) + ` null,
subscribed ` + db.typeDateTime() + ` not null,
token ` + db.typeChar(16) + ` not null,
confirmed ` + db.typeBool() + ` default 0 not null,
allow_export ` + db.typeBool() + ` default 0 not null,
constraint eu_coll_email
unique (collection_id, email),
constraint eu_coll_user
unique (collection_id, user_id),
PRIMARY KEY (id)
)`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

37
migrations/v14.go Normal file
View File

@ -0,0 +1,37 @@
/*
* Copyright © 2023 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 supportPassReset(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`CREATE TABLE password_resets (
user_id ` + db.typeInt() + ` not null,
token ` + db.typeChar(32) + ` not null primary key,
used ` + db.typeBool() + ` default 0 not null,
created ` + db.typeDateTime() + ` not null
)`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

33
migrations/v15.go Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright © 2023 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 addPostRetrievalIndex(db *datastore) error {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec("CREATE INDEX posts_get_collection_index ON posts (`collection_id`, `pinned_position`, `created`)")
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

38
migrations/v2.go Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright © 2019 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 supportInstancePages(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`ALTER TABLE appcontent ADD COLUMN title ` + db.typeVarChar(255) + db.collateMultiByte() + ` NULL`)
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE appcontent ADD COLUMN content_type ` + db.typeVarChar(36) + ` DEFAULT 'page' NOT NULL`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

32
migrations/v3.go Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright © 2019 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 supportUserStatus(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`ALTER TABLE users ADD COLUMN status ` + db.typeInt() + ` DEFAULT '0' NOT NULL`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

54
migrations/v4.go Normal file
View File

@ -0,0 +1,54 @@
/*
* Copyright © 2019-2021 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
import (
"context"
"database/sql"
wf_db "github.com/writefreely/writefreely/db"
)
func oauth(db *datastore) error {
dialect := wf_db.DialectMySQL
if db.driverName == driverSQLite {
dialect = wf_db.DialectSQLite
}
return wf_db.RunTransactionWithOptions(context.Background(), db.DB, &sql.TxOptions{}, func(ctx context.Context, tx *sql.Tx) error {
createTableUsersOauth, err := dialect.
Table("oauth_users").
SetIfNotExists(false).
Column(dialect.Column("user_id", wf_db.ColumnTypeInteger, wf_db.UnsetSize)).
Column(dialect.Column("remote_user_id", wf_db.ColumnTypeInteger, wf_db.UnsetSize)).
ToSQL()
if err != nil {
return err
}
createTableOauthClientState, err := dialect.
Table("oauth_client_states").
SetIfNotExists(false).
Column(dialect.Column("state", wf_db.ColumnTypeVarChar, wf_db.OptionalInt{Set: true, Value: 255})).
Column(dialect.Column("used", wf_db.ColumnTypeBool, wf_db.UnsetSize)).
Column(dialect.Column("created_at", wf_db.ColumnTypeDateTime, wf_db.UnsetSize).SetDefaultCurrentTimestamp()).
UniqueConstraint("state").
ToSQL()
if err != nil {
return err
}
for _, table := range []string{createTableUsersOauth, createTableOauthClientState} {
if _, err := tx.ExecContext(ctx, table); err != nil {
return err
}
}
return nil
})
}

88
migrations/v5.go Normal file
View File

@ -0,0 +1,88 @@
/*
* Copyright © 2019-2021 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
import (
"context"
"database/sql"
wf_db "github.com/writefreely/writefreely/db"
)
func oauthSlack(db *datastore) error {
dialect := wf_db.DialectMySQL
if db.driverName == driverSQLite {
dialect = wf_db.DialectSQLite
}
return wf_db.RunTransactionWithOptions(context.Background(), db.DB, &sql.TxOptions{}, func(ctx context.Context, tx *sql.Tx) error {
builders := []wf_db.SQLBuilder{
dialect.
AlterTable("oauth_client_states").
AddColumn(dialect.
Column(
"provider",
wf_db.ColumnTypeVarChar,
wf_db.OptionalInt{Set: true, Value: 24}).SetDefault("")),
dialect.
AlterTable("oauth_client_states").
AddColumn(dialect.
Column(
"client_id",
wf_db.ColumnTypeVarChar,
wf_db.OptionalInt{Set: true, Value: 128}).SetDefault("")),
dialect.
AlterTable("oauth_users").
AddColumn(dialect.
Column(
"provider",
wf_db.ColumnTypeVarChar,
wf_db.OptionalInt{Set: true, Value: 24}).SetDefault("")),
dialect.
AlterTable("oauth_users").
AddColumn(dialect.
Column(
"client_id",
wf_db.ColumnTypeVarChar,
wf_db.OptionalInt{Set: true, Value: 128}).SetDefault("")),
dialect.
AlterTable("oauth_users").
AddColumn(dialect.
Column(
"access_token",
wf_db.ColumnTypeVarChar,
wf_db.OptionalInt{Set: true, Value: 512}).SetDefault("")),
dialect.CreateUniqueIndex("oauth_users_uk", "oauth_users", "user_id", "provider", "client_id"),
}
if dialect != wf_db.DialectSQLite {
// This updates the length of the `remote_user_id` column. It isn't needed for SQLite databases.
builders = append(builders, dialect.
AlterTable("oauth_users").
ChangeColumn("remote_user_id",
dialect.
Column(
"remote_user_id",
wf_db.ColumnTypeVarChar,
wf_db.OptionalInt{Set: true, Value: 128})))
}
for _, builder := range builders {
query, err := builder.ToSQL()
if err != nil {
return err
}
if _, err := tx.ExecContext(ctx, query); err != nil {
return err
}
}
return nil
})
}

32
migrations/v6.go Normal file
View File

@ -0,0 +1,32 @@
/*
* Copyright © 2019-2020 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 supportActivityPubMentions(db *datastore) error {
t, err := db.Begin()
if err != nil {
return err
}
_, err = t.Exec(`ALTER TABLE remoteusers ADD COLUMN handle ` + db.typeVarChar(255) + ` NULL`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
return nil
}

46
migrations/v7.go Normal file
View File

@ -0,0 +1,46 @@
/*
* Copyright © 2020-2021 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
import (
"context"
"database/sql"
wf_db "github.com/writefreely/writefreely/db"
)
func oauthAttach(db *datastore) error {
dialect := wf_db.DialectMySQL
if db.driverName == driverSQLite {
dialect = wf_db.DialectSQLite
}
return wf_db.RunTransactionWithOptions(context.Background(), db.DB, &sql.TxOptions{}, func(ctx context.Context, tx *sql.Tx) error {
builders := []wf_db.SQLBuilder{
dialect.
AlterTable("oauth_client_states").
AddColumn(dialect.
Column(
"attach_user_id",
wf_db.ColumnTypeInteger,
wf_db.OptionalInt{Set: true, Value: 24}).SetNullable(true)),
}
for _, builder := range builders {
query, err := builder.ToSQL()
if err != nil {
return err
}
if _, err := tx.ExecContext(ctx, query); err != nil {
return err
}
}
return nil
})
}

Some files were not shown because too many files have changed in this diff Show More