Compare commits

..

269 commits

Author SHA1 Message Date
Nutomic
e82f72d3c8
Avoid breaking changes, keep response fields as deprecated (#5058) 2024-09-27 09:23:19 -04:00
Joseph Silva
50ce7961d1
Apply scheduled post limit to future posts instead of past posts, and verify this in test (#5054)
* test scheduled_post_count

* fix syntax error

* fix formatting

* fix argument order

* fix user_scheduled_post_count function
2024-09-27 08:51:10 -04:00
SleeplessOne1917
33cbd95b7e
Add skip_serialize_none to OAuth structs with option fields (#5046)
* Add skip_serialize_none to OAuth structs with option fields

* PR feedback

* Remove serde and ts export from SSO db-only structs
2024-09-26 10:24:51 +02:00
Nutomic
f6a24e133a
Replace clippy allow annotation with expect (fixes #5012) (#5048) 2024-09-24 13:29:02 -04:00
Nutomic
61a02482ff
Cleanup remaining use of Result<bool, Error> (fixes #4862) (#5047) 2024-09-24 13:25:33 -04:00
Dessalines
0fab5bed24
Add ability to search for Community by its description (or title only). (#5044)
- This changes the post_title_only for Search to title_only, since its
  also used in the community query now.
- Fixes #4785
2024-09-24 13:24:28 -04:00
Dessalines
a65be776e3
Remove redundant local_user.auto_expand setting. (#5041)
- Fixes #4643

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-09-24 08:55:09 -04:00
Nutomic
9eee61dd06
Post scheduling (fixes #234) (#5025)
* Post scheduling (fixes #234)

* clippy

* replace map_err with inspect_err

* ignore unpublished posts in read queries

* add api test

* fmt

* add some checks

* address some review comments

* allow updating schedule time

* rewrite scheduled task

* fmt

* machete

* compare date in sql, more filters

* check for community ban in sql

* remove api test (scheduled task only runs every 10 mins)

* remove mut

* add index

* remove Post::read impl

* fmt

* fix

* correctly handle changes to schedule time

* normal users can only schedule up to 10 posts
2024-09-24 05:39:40 -04:00
Nutomic
bab5c93062
Conditionally hide comments on nsfw posts (fixes #4237) (#5028)
* Conditionally hide comments on nsfw posts (fixes #4237)

* fix test
2024-09-24 10:33:53 +02:00
Dessalines
d476d32200
Removing a few more Result<bool> . (#4977)
* Removing a few more Result<bool> .

* Running taplo fmt.

* Running fmt.

* Adding email taken test.

* Fixing tests.

* Adding back in missing admin check.

* Rename check_has_local_followers function.
2024-09-23 20:55:35 -04:00
Dessalines
62e1790ae7
Adding saved_only, liked_only, and disliked_only filters to search. (#5034)
* Adding saved_only, liked_only, and disliked_only filters to search.

- Fixes #4547

* Removing duplicate Url return type for search (was actually post).

- This now works like the post_title_only filter.

* Address PR comments.

* Add saved_only post_view test.
2024-09-23 11:27:06 -04:00
Nutomic
a8843335a6
Simplify handling of NotFound SQL errors (fixes #4633) (#5031)
* Simplify handling of NotFound SQL errors (fixes #4633)

* fmt

* wip

* compiling

* clippy

* api tests

* fix
2024-09-23 11:26:50 -04:00
Dessalines
458bb60144
Get rid of a lot of pointless mut form initializations. (#5037)
* Get rid of a lot of pointless mut form initializations.

- Fixes #5036

* Fix clippy.
2024-09-23 12:05:18 +02:00
Sander Saarend
25df9d255b
Always save remote image data (#4875)
* Always save remote image data

* cleanup

---------

Co-authored-by: Felix Ableitner <me@nutomic.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-09-23 09:58:49 +02:00
Nutomic
8cdfc148d7
Ignore zero values when setting rate limits (fixes #4280) (#5029)
* Ignore zero values when setting rate limits (fixes #4280)

Havent bothered to add an error message for such an uncommon case.

* fmt

* reorder, add test
2024-09-20 14:43:04 +02:00
Nutomic
ad75192bae
Remove TypedBuilder in favor of derive_new (fixes #4863) (#5020)
* Remove TypedBuilder in favor of derive_new (fixes #4863)

* fix

* fix
2024-09-20 08:15:25 -04:00
Dessalines
5a722146b5
Upgrading to rust 1.81 (#5032) 2024-09-19 17:00:20 -04:00
leoseg
dbb8f9553a
Unittest for Search by title only (#5033)
* added test for search by title only

* formatted rust files
2024-09-19 17:00:07 -04:00
Nutomic
89745bb37d
Add category to RSS feeds (fixes #3446) (#5030) 2024-09-19 09:43:58 -04:00
Freakazoid182
43f20881cb
Feature/custom emoji and tagline views (#4580)
* Add custom_emoji list route

* Add tagline list route

* Apply linting

* Remove unecessary TaglineView

* Add category filter for custom emoji

* Add create tagline endpoint

* Add update tagline endpoint

* Add delete tagline endpoint

* Format through lint.sh

* Remove custom_emojis and taglines from site resource

* Get random tagline on site requets

* Impl Crud for Tagline

Remove superfluous properties

* Move tagline endpoints under /admin

* Impl Crud for CustomEmoji

* Remove delete from tagline and custom emoji impls

* Check  markdown for tagline

* Validate markdown on tagline

* Make content fields non optional

Add error types for tagline validation

* Use process_markdown instead of process_markdown_opt

* Consolidate Tagline error types

* Remove unecessary clone

* Updat misleading comments

* Remove local_site_id from tagline and custom_emoji

* Update TaglineInserForm and TaglineUpdateForm

* Add ignore_page_limits for custom emojis

EmojiPicker needs to be able to retrieve all emojis in 1 call

* Update custom_emoji_view

Only keep get_all als helper function calling list with paging ignored

Only order on category when filtering on category

* Removing pointless get_all fn.

* remove tagline length checks

* make fields of TaglineInsertForm and TaglineUpdateForm mandatory

* move emoji order statement

* add comment for GetSiteResponse.tagline

---------

Co-authored-by: Freakazoid182 <>
Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: Felix Ableitner <me@nutomic.com>
2024-09-19 05:15:04 -04:00
Nutomic
026e23cf32
Simplify tests using default (#5026) 2024-09-19 04:43:27 -04:00
Dessalines
6b6457cc54
Adding a default_comment_sort_type column for local_site and local_user. (#4469)
* Adding a default_comment_sort_type column for local_site and local_user.

- Renamed SortType to PostSortType in the DB and code.
- Renamed references to default_sort_type to default_post_sort_type.
- Fixes #4128

* Renaming migration to current date.

* Simplifying PostSortType.
2024-09-19 10:03:58 +02:00
Dessalines
2b3fd70afd
Adding ability to restore content on user unban. (#4845)
* Adding ability to restore content on user unban.

- Fixes #4721

* Fixing api tests.

* Fix package.json

* Fixing lemmy-js-client dep.

* Adding API test for restoring content.
2024-09-18 09:11:42 -04:00
privacyguard
b26aaac523
SSO Support (#4881)
* Added OAUTH2 OIDC support

* Fixes and improvements based on review feedback

* use derive_new::new instead of TypedBuilder

* merge migrations into a single file

* fixes based on review feedback

* remove unnecessary hostname_ui config

* improvement based on review feedback

* improvements based on review feedback

* delete user oauth accounts at account deletion

* fixes and improvements based on review feedback

* removed auto_approve_application

* support registration application with sso

* improvements based on review feedback

* making the TokenResponse an internal struct as it should be

* remove duplicate struct

* prevent oauth linking to unverified accounts

* switched to manually entered username and removed the oauth name claim

* fix cargo fmt

* fix compile error

* improvements based on review feedback

* fixes and improvements based on review feedback

---------

Co-authored-by: privacyguard <privacyguard@users.noreply.github.com>
2024-09-18 14:52:33 +02:00
Nutomic
6454a4d43d
Remove enable nsfw (#5017)
* Remove `local_site.enable_nsfw` in favor of `site.content_warning` (fixes #4627)

* cleanup usage of SiteView::read_local

* test

* uppercase
2024-09-16 11:18:16 -04:00
Dessalines
5febf2b8fb
Adding clearurls crate to clean tracking params from links and markdown. (#5018)
* Adding clearurls crate to clean tracking params from links and markdown.

- Thanks to @jenrdikw for creating this
- Fixes #4905

* Upgrading to new version of clearurls

* Fix clippy
2024-09-16 11:15:41 -04:00
Dessalines
ff939e04fd
Removing embedded pict-rs. (#5023)
Some reasons for removing this:

- Even as an optional dependency, it locks us to many specific versions
  of rust deps.
- Pict-rs is a large app that can and should be run in on its own.
- Violates the philosophy of separation of concerns.
2024-09-16 11:08:18 -04:00
Dessalines
987e3f8026
Upgrading webmention to 0.6.0, removes native-tls (#4976) 2024-09-16 10:11:02 +02:00
Carlos Cabello
fa192f16bd
Add option to search exclusively by post title (#5015)
* Add option to search exclusively by post title

* Address format issues

* Remove duplicated 'removed' filter

* Replace url_search with search_term

* Build generic PostQuery before search match

* Create default queries. Move title_only to Search struct. Rename Url to PostURL

* Revert PostUrl to Url
2024-09-16 09:59:09 +02:00
Nutomic
dea6ee462c
Upgrade http crate (#5006)
* Remove opentelemetry

* remove unused deps, use backtrace

* always print db migration messages regardless of log level (fixes #4725)

* fix ci

* Remove useless root span builder

* Upgrade http and opentelemetry crates

* more fixes

* cleanup

* use release

* upgrade more deps
2024-09-12 05:49:09 -04:00
Dessalines
a6220537b5
Increase bio max length from 300 to 1000. (#5014)
- Fixes #4972
2024-09-11 15:27:16 +00:00
Sander Saarend
fbb3960c36
Image proxy fixes (#4871)
* Fix remote image urls

* Remove unnecessary URL decode

* Removing the proxy changes.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-09-11 10:11:25 -04:00
Nutomic
fc13009611
In debug mode allow resolving objects without auth (#5013) 2024-09-11 08:53:24 -04:00
Dessalines
ef5e2d96cd
Fixing woodpecker warnings 1. (#5011) 2024-09-10 17:42:20 -04:00
Dessalines
d1866cbd04
Remove pointless block_views. (#4841)
- Fixes #4793
2024-09-10 14:32:12 -04:00
Dessalines
84794714da
Removing local_user.show_scores column, since its now on the (#4497)
local_user_vote_display_mode table.

- See https://github.com/LemmyNet/lemmy/pull/4450
2024-09-10 14:27:17 -04:00
Nutomic
c90ee3094d
Remove opentelemetry (#4741)
* Remove opentelemetry

* remove unused deps, use backtrace

* always print db migration messages regardless of log level (fixes #4725)

* fix ci

* Remove useless root span builder

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-09-10 13:36:03 -04:00
Dessalines
ef49a0eb8d
Changing list_logins to return a ListLoginsResponse object. (#4888)
- Fixes #4873
2024-09-10 12:41:30 +02:00
Dessalines
c8ad0f2d09
Remove pointless local_user_id from LocalUserVoteDisplayMode (#4890)
- Fixes #4866
2024-09-10 12:39:45 +02:00
Nutomic
234f5c2060
Remove nonstandard field expires from apub block activity (ref #2316) (#4542)
* Migrate apub block activity to standard `endTime` property (fixes #2316)

* Remove nonstandard apub field `expires` (ref #2316)
2024-09-10 12:35:58 +02:00
renovate[bot]
f98b15511a
chore(deps): update rust crate derive-new to 0.7.0 (#4995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 16:47:16 +00:00
renovate[bot]
5683d5ed7a
chore(deps): update pnpm to v9.9.0 (#4994)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 12:30:05 +00:00
renovate[bot]
b1afd98d29
chore(deps): update dependency @types/node to v22.5.1 (#4991)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 09:40:23 +00:00
renovate[bot]
f617227f0d
chore(deps): update dependency ts-jest to v29.2.5 (#4990)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 06:41:42 +00:00
renovate[bot]
fa94a5869e
chore(deps): update dependency eslint to v9.9.1 (#4992)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-01 03:11:01 +00:00
Joseph Silva
ae3df4db72
Replace "post" with "comment" in comment_report_view.rs (#4989) 2024-08-31 22:36:47 -04:00
Dessalines
d479bb96c6 Version 0.19.6-beta.7 2024-08-27 16:32:08 -04:00
dullbananas
101476df87
Make scripts work in path that contains spaces (#4986) 2024-08-22 17:33:00 +02:00
flamingos-cant
bd1b7aa6ef
Let federation modify groups (#4937)
* Don't check if group is local

* Add API test for mods modifying communities
2024-08-20 16:16:14 +02:00
Dessalines
2913cdf556 Version 0.19.6-beta.6 2024-08-19 19:44:13 -04:00
Dessalines
6b9e5ecb48 Version 0.19.6-beta.5 2024-08-15 08:50:27 -04:00
Richard Schwab
76a2c6e79b
Fix scheduled task to delete users with denied applications (#4907)
After an admin interacted with an application it can only be accepted or denied.
Denial reasons are not required by Lemmys backend, even though they're mandatory in Lemmy-UI, and therefore they are not a reliable indicator of an application being denied.
This aligns the cleanup logic  with the logic used for the unread application count.
2024-08-13 18:54:56 -04:00
Dessalines
254ef6dab3
Make site metadata fetch endpoint require auth. (#4968)
* Make site metadata fetch require auth.

* Update crates/api/src/post/get_link_metadata.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

---------

Co-authored-by: dullbananas <dull.bananas0@gmail.com>
2024-08-13 16:53:05 -04:00
Richard Schwab
63a686d390
Approve applications in transaction (#4970)
* Implement tests for registration application count and list api

* Use transaction when approving applications to ensure consistent approval state
2024-08-13 16:18:26 -04:00
flamingos-cant
ea18d462b0
Throw error when non-mod posts to mod-only comm (#4966) 2024-08-13 14:56:26 -04:00
Dessalines
128e78f7c2 Version 0.19.6-beta.4 2024-08-08 20:01:58 -04:00
phiresky
606545ccaf
fix: Run extract_opengraph_data only on first 64kB of data and if Content-Type html (#4957)
* fix: Run extract_opengraph_data only on first 64kB of data and if data is not binary.

* use mime type for determination

* chore: simplify collect function
2024-08-07 10:35:08 -04:00
Dessalines
88fbcea246 Version 0.19.6-beta.3 2024-08-06 10:32:41 -04:00
Dessalines
eff87b2764
Upgrading arm image. (#4962) 2024-08-05 22:25:18 -04:00
Dessalines
33fd31754a
Adding a URL max length lemmy error. (#4960)
* Adding a URL max length error.

- Also increasing the post.url max length to 2000 (seems standard)
- I ran into this when fixing torrent support, which often use longer
  urls.

* Fixing sql_format.
2024-08-04 09:45:53 -04:00
Dessalines
f8c7375731
Upgrading deps. (#4955) 2024-08-01 17:04:28 -04:00
renovate[bot]
c6cbc2f61d
chore(deps): update dependency typescript to v5.5.4 (#4942)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 14:46:59 -04:00
renovate[bot]
4729255514
chore(deps): update typescript-eslint monorepo to v8 (#4952)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 14:24:16 -04:00
renovate[bot]
b25f9c3e66
chore(deps): update typescript-eslint monorepo to v7.18.0 (#4950)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 13:59:55 -04:00
renovate[bot]
c0f3dff5cf
chore(deps): update rust crate mockall to 0.13.0 (#4946)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 13:59:40 -04:00
renovate[bot]
79fddc965f
chore(deps): update rust crate typed-builder to 0.19.0 (#4949)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 16:22:55 +00:00
renovate[bot]
2cab7935db
chore(deps): update docker/dockerfile docker tag to v1.9 (#4938)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 10:53:41 -04:00
renovate[bot]
2318473456
fix(deps): update rust crate console-subscriber to 0.4.0 (#4944)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 10:52:40 -04:00
renovate[bot]
75d8d704e7
chore(deps): update pnpm to v9.6.0 (#4939)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 10:52:24 -04:00
renovate[bot]
4e7e92da94
chore(deps): update dependency eslint-plugin-prettier to v5.2.1 (#4940)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 10:51:37 -04:00
renovate[bot]
fa836bb35a
chore(deps): update dependency ts-jest to v29.2.4 (#4941)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 10:51:26 -04:00
renovate[bot]
4e48054b17
chore(deps): update rust crate tokio to v1.39.2 (#4935)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 14:50:52 +00:00
Dessalines
6c11c013b0 Version 0.19.6-beta.2 2024-08-01 08:29:22 -04:00
renovate[bot]
0bd0c0f11b
chore(deps): update rust crate clap to v4.5.13 (#4932)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-01 11:38:06 +00:00
renovate[bot]
c6db2ad9b7
chore(deps): update rust crate serde_json to v1.0.121 (#4933)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-31 21:46:20 -04:00
Dessalines
60a7829638
Adding deny unimplemented to clippy lints. (#4922)
* Adding deny unimplemented to clippy lints.

- Context: #4782

* Update crates/apub/src/fetcher/site_or_community_or_user.rs

Thanks, I like that better.

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Update crates/apub/src/fetcher/search.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Running fmt.

* Adding debug_assert(false)

* Removing some commands.

* Format.

* Remove todo.

---------

Co-authored-by: dullbananas <dull.bananas0@gmail.com>
2024-07-31 20:28:41 -04:00
renovate[bot]
cbb37fa2f1
chore(deps): update dependency @types/node to v20.14.13 (#4928)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-31 19:28:17 -04:00
Dessalines
0a6186d7aa
Fixing apt-key deprecation, and using apt-get. (#4931)
* Fixing apt-key deprecation, and using apt-get.

* Try 2

* Try 3

* Try 4

* Try 5

* Try 6

* Try 7

* Try 8

* Try 9

* Try 10

* Try 11

* Try 12

* Try 13

* Try 14

* Try 15

* Try 16

* Try 17
2024-07-31 18:54:44 -04:00
renovate[bot]
5421e9a3c7
chore(deps): update dependency prettier to v3.3.3 (#4929)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-30 15:38:17 +00:00
SleeplessOne1917
1ef375e5c3
Replace 3rd party once_cell crate with recently stabilized standard library API (#4919)
* Replace 3rd party once_cell crate with recently stabilized standard library API

* Bump CI rust version
2024-07-30 10:11:39 -04:00
Dessalines
069a9509d5
Adding renovate automerge. (#4927) 2024-07-30 09:41:06 -04:00
abdel-m
2ccb46b66d
pass local user to send local notifs (#4920)
* pass local user to send local notif

* pass local user to all crud calls

* execution of command "cargo +nightly fmt"

* Formatting and mode fixes.

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-07-30 08:34:58 -04:00
Nutomic
fb911679d0
Fix admin notification for new user registration (fixes #4916) (#4925) 2024-07-30 08:34:17 -04:00
John Spurlock
7321a63005
Fix instance AP outbox url (#4917)
//site_outbox to /site_outbox
2024-07-24 12:18:28 +02:00
Dessalines
9738d87f38
Add the ability to fetch a registration application by person_id. (#4913)
* Add the ability to fetch a registration application by person_id.

- Fixes #4908

* Cleaning up PR.
2024-07-23 19:01:24 -04:00
Dessalines
32b73193df
Make sure you can view your moderated deleted and removed communities. (#4912)
* Make sure you can view your moderated deleted and removed communities.

- The front end checks to see whether you are a mod, in order to be
  able to restore deleted / removed communities. This removes a filter
  which prevents that.
- Fixes #4911

* Only show deleted communities to creator, and removed to admins.

* Addressing PR comments.
2024-07-23 12:38:54 -04:00
SleeplessOne1917
db390a2f3a
Make eligible enums convertable to static strs (#4915)
* Make eligible enums convertable to static strs

* Run cargo fmt

* Remove unnecessary derives
2024-07-23 11:05:19 -04:00
Nutomic
572a42d880
Change type of concurrent sends in config (#4914) 2024-07-22 09:58:50 -04:00
Darren M
3d80ac2ebb
Fix tls pool (#4910)
* Cargo: add rustls as a dependency

* install tls provider in main

* Cargo: re-define rustls dependency
2024-07-21 22:33:42 -04:00
phiresky
a08642f813
federation: parallel sending per instance (#4623)
* federation: parallel sending

* federation: some comments

* lint and set force_write true when a request fails

* inbox_urls return vec

* split inbox functions into separate file

* cleanup

* extract sending task code to separate file

* move federation concurrent config to config file

* off by one issue

* improve msg

* fix both permanent stopping of federation queues and multiple creation of the same federation queues

* fix after merge

* lint fix

* Update crates/federate/src/send.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* comment about reverse ordering

* remove crashable, comment

* comment

* move comment

* run federation tests twice

* fix test run

* prettier

* fix config default

* upgrade rust to 1.78 to fix diesel cli

* fix clippy

* delay

* add debug to make localhost urls not valid in ap crate, add some debug logs

* federation tests: ensure server stop after test and random activity id

* ci fix

* add test to federate 100 events

* fix send 100 test

* different data every time so activities are distinguishable

* allow out of order receives in test

* lint

* comment about https://github.com/LemmyNet/lemmy/pull/4623#discussion_r1565437391

* move sender for clarity, add comment

* move more things to members

* update test todo comment, use same env var as worker test but default to 1

* remove else below continue

* some more cleanup

* handle todo about smooth exit

* add federate inboxes collector tests

* lint

* actor max length

* don't reset fail count if activity skipped

* fix some comments

* reuse vars

* format

* Update .woodpecker.yml

* fix recheck time

* fix inboxes tests under fast mode

* format

* make i32 and ugly casts

* clippy

---------

Co-authored-by: dullbananas <dull.bananas0@gmail.com>
2024-07-21 11:50:50 -04:00
Dessalines
073ff44676
Reverting webmention git dep to not break publish. (#4904)
- Context: #4901
2024-07-19 10:18:45 +02:00
藍+85CD
847c01f348
refactor!: use rustls instead of native-tls (#4901)
* refactor(utils): remove apub

* refactor(utils): remove apub

* refactor(utils): remove openssl

* refactor(utils): remove openssl

* Use rustls instead of native-tls.

* refactor(utils): remove apub

* refactor(utils): remove apub

* refactor(utils): remove openssl

* refactor(utils): remove openssl

* Use rustls instead of native-tls.

* Upping activitypub_federation dep

* Using git dep of webmention.

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-07-18 08:40:43 -04:00
Daniel Lo Nigro
8abbd56400
Switch PostgreSQL container to use pgautoupgrade (#4892)
* Switch PostgreSQL container to use pgautoupgrade

This handles automatically upgrading the data files to newer versions of PostgreSQL.

* Fixing other uses of postgres:16-alpine image.

* Simplifying upgrade scripts.

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-07-17 09:59:21 +02:00
Dessalines
ba044c7d98
Adding checks for higher admin and mod. (#4860)
* Adding checks for higher admin and mod.

* Adding admin/mod checks for ban and comment removal.

* Combining mod or admin check with an SQL union.

* Making community ban or add mod also allow higher admins.

* Making sure remove post also checks higher mods or admins.

* Add unit test for is_higher_mod_or_admin_check

* Fixing comment.

* Addressing PR comments.

* Get rid of pointless wrapper functions, return lemmyresult directly.
2024-07-16 12:22:47 +02:00
Dessalines
6ff128341c
Do pictrs transformations for proxied image urls. (#4895)
- Fixes #4893
2024-07-11 17:31:31 +02:00
Dessalines
53a226b944
Add show_nsfw override filter to GetPosts. (#4889)
- Fixes #4124
2024-07-09 13:44:23 -04:00
Dessalines
f229f09f92
Changing nodeinfo test from voyager.lemmy.ml to lemmy.ml (#4891)
- Fixes #4870
2024-07-07 19:01:03 -04:00
Dessalines
32cee9cbca
Fixing not being able to create comments on local community posts. (#4854)
* Fixing not being able to create comments on local community posts.

- This was caused by not passing my_person_id into various
  `CommentView::read` functions.
- Fixes #4853

* Refactoring views to use local_user, rather than person

* Addressing PR comments.

* Fixing API tests.
2024-07-07 12:28:42 -04:00
dullbananas
78702b59fd
Use trigger to generate apub URL in insert instead of update, and fix query planner options not being set when TLS is disabled (#4797)
* Update create.rs

* Update utils.rs

* Update utils.sql

* Update triggers.sql

* Update utils.sql

* Update create.rs

* Update create.rs

* Update create.rs

* Update create.rs

* Update create.rs

* Update create.rs

* Update create.rs

* Update create.rs

* Create up.sql

* Update up.sql

* Update triggers.sql

* Update utils.rs

* stuff

* stuff

* revert some changed files

* Revert "revert some changed files"

This reverts commit 028eabb4bd.

* revert the correct files

* partial reverts

* migration, tests, fix establish_connection

* lint

* pg_format
2024-07-02 11:23:21 -04:00
renovate[bot]
117a8b42c8
Update Rust crate serde_json to v1.0.120 (#4877)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 10:02:32 -04:00
dullbananas
fd58b4f809
Exponential controversy rank (#4872)
* Update utils.sql

* add migration
2024-07-02 09:40:18 -04:00
Dessalines
a7c39226e2
Remove unused PersonBlockId. (#4880)
- Fixes #4879
2024-07-02 09:39:37 -04:00
renovate[bot]
d90e8f8550
Update Rust crate clap to v4.5.8 (#4876)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-07-02 09:37:37 -04:00
Richard Schwab
2c57f42022
Relax timeout for sending activities (#4864)
* Relax timeout for sending activities

Lemmy considers timeouts during activity sending as retryable errors.
While it is frequently enough to retry sending the same activity again after
the original submission attempt resulted in a timeout, allowing the receiving
side to use more time for synchronous processing should reduce the number of
retries needed overall and improve overall compatibility.

Some ActivityPub software, such as Mastodon, implements a queue for processing
received activities asynchronously, which allows immediately returning a
response for activity submissions. Other software, such as Lemmy or Hubzilla
implement synchronous processing of activities before returning a response.

ActivityPub does not specify specific timeouts to be used:
https://github.com/w3c/activitypub/issues/365

* Simplify usage of federation_sender_config Option
2024-07-02 09:30:13 -04:00
dullbananas
9120207314
Format replaceable_schema files in lint.sh (#4868) 2024-06-26 10:47:09 +02:00
Dessalines
d09854a722
Adding a show_read override to GetPosts. (#4846)
* Adding a show_read override to GetPosts.

- If show_read is true, it overrides the local user show_read
  setting.
- Fixes #4124

* Addressing PR comments.

* Update crates/db_views/src/post_view.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Fixing formatting.

---------

Co-authored-by: dullbananas <dull.bananas0@gmail.com>
2024-06-21 17:39:40 -04:00
Dessalines
c8d155102a
Removing renovate from git cliff (#4858)
* Removing renovate from git cliff

* Formatting.
2024-06-21 17:38:44 -04:00
dullbananas
36e6f7ec78
Fix order in CommunityModeratorView::get_community_first_mods (#4859)
* Fix order in `CommunityModeratorView::get_community_first_mods`

* Update community_moderator_view.rs

* Update community_moderator_view.rs
2024-06-21 13:44:55 -04:00
Dessalines
6d8d23130d
Adding an image_details table to store image dimensions. (#4704)
* Adding an image_details table to store image dimensions.

- Adds an image_details table, which stores the height,
  width, and content_type for local and remote images.
- For LocalImages, this information already comes back with
  the upload.
- For RemoteImages, it calls the pictrs details endpoint.
- Fixed some issues with proxying non-image urls.
- Fixes #3328
- Also fixes #4703

* Running sql format.

* Running fmt.

* Don't fetch metadata in background for local API requests.

* Dont export remote_image table to typescript.

* Cleaning up validate.

* Dont proxy url.

* Fixing tests, fixing issue with federated thumbnails.

* Fix tests.

* Updating corepack, fixing issue.

* Refactoring image inserts to use transactions.

* Use select exists again.

* Fixing imports.

* Fix test.

* Removing pointless backgrounded metadata generation version.

* Removing public pictrs details route.

* Fixing clippy.

* Running prettier.

* A few more fixes.

* Moving diesel schema check back down.

* Addressing PR comments.

* Changing back request head to get.

* Fixing lockfile.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-06-20 12:44:06 +02:00
Dessalines
9cf6da1b9e Version 0.19.5 2024-06-19 08:17:45 -04:00
Dessalines
59f274e531
Revert "Removing renovate schedule. (#4808)" (#4847)
This reverts commit 65620913fc.
2024-06-18 16:55:06 -04:00
Dessalines
fa143f72eb Version 0.19.5-alpha.3 2024-06-18 11:49:02 -04:00
Dessalines
63a824a2ed
Fixing TLS connection by installing provider. (#4844)
- Fixes #4795
2024-06-18 09:59:24 -04:00
renovate[bot]
b9dc7612a8
Update dependency @types/node to v20.14.5 (#4843)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-18 10:11:20 +00:00
renovate[bot]
393f65db8d
Update dependency @types/node to v20.14.4 (#4842)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 20:59:40 -04:00
Dessalines
bfefdfd15d Version 0.19.5-alpha.2 2024-06-17 20:26:44 -04:00
renovate[bot]
41d1b054fe
Update dependency @types/node to v20.14.3 (#4840)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 18:07:36 -04:00
dullbananas
42a6d8ab0f
Fix not-equals check in post aggregates update trigger (#4837)
* Fix not-equals check in post aggregates update trigger

Should fix #4836

* Create up.sql

* Create down.sql

* Update down.sql
2024-06-17 15:25:54 -04:00
renovate[bot]
f080400826
Update typescript-eslint monorepo to v7.13.1 (#4838)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 15:04:24 -04:00
renovate[bot]
c55636b0d0
Update pnpm to v9.4.0 (#4839)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-17 15:03:43 -04:00
renovate[bot]
32b7ee76e3
Update dependency ts-jest to v29.1.5 (#4834)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-16 19:30:34 +00:00
Dessalines
5cc798a146 Version 0.19.5-alpha.1 2024-06-15 15:12:10 -04:00
renovate[bot]
4974dbb1dd
Update Rust crate console-subscriber to 0.3.0 (#4817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-06-15 08:17:01 -04:00
renovate[bot]
d7a453dd68
Update Rust crate regex to v1.10.5 (#4811)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 07:29:41 -04:00
renovate[bot]
393b221be0
Update typescript-eslint monorepo to v7.13.0 (#4827)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:56:47 -04:00
renovate[bot]
966d949f73
Update dependency eslint to v9 (#4830)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:55:59 -04:00
renovate[bot]
b245bf48c0
Update pnpm to v9.3.0 (#4826)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:55:06 -04:00
renovate[bot]
b569c7df17
Update docker/dockerfile Docker tag to v1.8 (#4824)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:54:51 -04:00
renovate[bot]
0f6bd94407
Update dependency prettier to v3.3.2 (#4823)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:54:31 -04:00
renovate[bot]
5cf1593c9f
Update dependency @types/node to v20.14.2 (#4822)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:54:17 -04:00
renovate[bot]
04400ceac3
Update Rust crate rustls to v0.23.10 (#4816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:54:00 -04:00
renovate[bot]
dede17bf24
Update Rust crate pict-rs to v0.5.16 (#4815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:53:44 -04:00
renovate[bot]
b0e2a14d04
Update Rust crate actix-web to v4.7.0 (#4814)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:53:28 -04:00
renovate[bot]
027017b0a8
Update asonix/pictrs Docker tag to v0.5.16 (#4813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:53:11 -04:00
renovate[bot]
b27b38b9a9
Update Rust crate url to v2.5.1 (#4812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:52:56 -04:00
renovate[bot]
a7771ff385
Update Rust crate clap to v4.5.7 (#4810)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:52:40 -04:00
renovate[bot]
27e7aa1e04
Update Rust crate actix-web-httpauth to v0.8.2 (#4809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-06-15 06:52:21 -04:00
dullbananas
6497ec519e
Refactor LocalUser settings conditions in database views (#4746)
* Create viewer.rs

* Rename viewer.rs to viewer.rs

* Update viewer.rs

* Update post_view.rs

* Update distinguish.rs

* Update like.rs

* Update viewer.rs

* Update list_comment_likes.rs

* Update like.rs

* Update save.rs

* Update like.rs

* revert changes in api crate

* Update post_view.rs

* Update post_view.rs

* Update comment_view.rs

* Update post_view.rs

* Update community_view.rs

* Update comment_view.rs

* Update post_view.rs

* Update viewer.rs

* Update post_view.rs

* Update community_view.rs

* Update local_user_view.rs

* Update viewer.rs

* Update community_view.rs

* Update viewer.rs

* Update lib.rs

* Update comment_view.rs

* Update post_view.rs

* Update viewer.rs

* Update viewer.rs

* Update viewer.rs

* Update viewer.rs

* Update local_user_view.rs

* Update viewer.rs

* Update viewer.rs

* Update local_user_view.rs

* Update community_view.rs

* Update viewer.rs

* Update crates/db_schema/src/viewer.rs

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>

* Update viewer.rs

* Update viewer.rs

* Update viewer.rs

* Update viewer.rs

* Update post_view.rs

* Update community_view.rs

* Update comment_view.rs

* Update viewer.rs

* Update post_view.rs

* Update save.rs

* Update resolve_object.rs

* Update viewer.rs

* Update save.rs

* Update resolve_object.rs

* Update comment_view.rs

* Update post_view.rs

* Update community_view.rs

* Update local_user_view.rs

* Update post_view.rs

* Update viewer.rs

* Update comment_view.rs

* Update post_view.rs

* Update community_view.rs

* Update viewer.rs

* Update viewer.rs

* Update viewer.rs

* Update viewer.rs

* Update viewer.rs

* Some additions to localuser DB view helpers. (#39)

* Some additions to localuser DB view helpers.

- Getting rid of generics.
- Passing in only LocalUser to views.

* Formatting fixes.

* Getting rid of unecessary as_refs

* Fixing clippy.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
2024-06-14 21:51:24 -04:00
Dessalines
65620913fc
Removing renovate schedule. (#4808) 2024-06-14 21:51:10 -04:00
Nutomic
a3c8761bed
Revert "Remove unneeded error "last successful id is higher than latest id" (fixes #4363) (#4486)" (#4806)
This reverts commit c895e57086.
2024-06-14 08:15:12 -04:00
dullbananas
99160228ae
Remove unimplemented in <Comment as Crud>::create (#4796)
* Remove `unimplemented` in `<Comment as Crud>::create`

* Update comment.rs
2024-06-14 10:40:57 +02:00
Dessalines
fc6f46c1ac
Fix issue with GetPost not returning bot cross_posts. (#4804)
- Fixes #4803
2024-06-13 14:32:03 -04:00
dullbananas
046375171e
Don't change encoding style in clean_url_params (#4802)
* Don't change encoding style in `clean_url_params`

Fixes #4801

* fmt

* fix
2024-06-12 20:35:27 -04:00
Dessalines
b2a480f55c
Fixing sed command for postgres upgrade. (#4791)
- Context: https://github.com/LemmyNet/lemmy-ansible/issues/245
2024-06-07 12:39:23 -04:00
Dessalines
9236cf7d21
Remove ansible tagging lines. (#4790) 2024-06-07 11:26:43 -04:00
dullbananas
b559e0206b
Replace wav with hound (#4788)
* Update lib.rs

* Update Cargo.toml

* Update lib.rs

* cargo.lock

* fix simultaneous mutable references
2024-06-07 10:27:49 -04:00
Dessalines
f5f2b5ffc6 Version 0.19.4 2024-06-07 07:51:56 -04:00
dullbananas
1e11faf741
Improve comment in triggers.sql (#4789)
* Clarified existing info
* Added prohibition of inconsistent update order
2024-06-07 07:42:34 -04:00
Dessalines
5d31f0d516 Version 0.19.4-rc.11 2024-06-06 23:02:38 -04:00
Dessalines
844b84a01a Version 0.19.4-rc.10 2024-06-06 20:46:03 -04:00
Dessalines
b0447ad94d
Upgrading lemmy-js-client version to 0.19.4 (#4787)
* Upgrading lemmy-js-client version to 0.19.4

* Upgrading deps before renovate.
2024-06-06 20:44:36 -04:00
Dessalines
3d25322089 Version 0.19.4-rc.9 2024-06-06 10:43:40 -04:00
Dessalines
16a82862b8
Allow empty string to clear URL-type DB fields. (#4780)
* Allow empty string to clear URL-type DB fields.

- To address difficulties with clearing URL-type fields like
  avatars, banners, site icons, this PR turns the URL type form
  fields into strings.
- This allows an empty string to be used as a "clear data", as
  in the case with the regular text form fields.
- Also includes various cleanups.
- Fixes #4777
- Context: #2287

* Fixing comment.

* Use Option<&str> and deref.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-06-06 09:55:08 -04:00
dullbananas
79e6dbf0de
Remove PersonInsertForm builder (#4779)
* Update session_middleware.rs

* Update private_message_report_view.rs

* Update session_middleware.rs

* Update private_message_view.rs

* Update private_message.rs

* Update registration_application_view.rs

* Update actor_language.rs

* Update vote_view.rs

* Update code_migrations.rs

* Update comment_aggregates.rs

* Update person_view.rs

* Update user_settings_backup.rs

* Update person.rs

* Update create.rs

* Update comment_view.rs

* Update moderator.rs

* Update site_aggregates.rs

* Update claims.rs

* Update community_aggregates.rs

* Update post_report.rs

* Update person_mention_view.rs

* Update community_view.rs

* Update comment_report_view.rs

* Update post_report_view.rs

* Update community_moderators.rs

* Update comment.rs

* Update person_aggregates.rs

* Update comment_reply_view.rs

* Update password_reset_request.rs

* Update post_aggregates.rs

* Update community.rs

* Update main.rs

* Update post.rs

* Update person.rs

* Update person.rs

* Update claims.rs

* Update person.rs

* Update create.rs

* Update user_settings_backup.rs

* Update community_moderators.rs

* Update main.rs

* Update comment_aggregates.rs

* Update community_aggregates.rs

* Update person.rs

* Update Cargo.toml

* Update Cargo.toml

* Update person.rs

* fix

* Update code_migrations.rs

* fix submodule

* Update person.rs
2024-06-06 08:29:18 -04:00
Dessalines
fda5ce4482 Version 0.19.4-rc.8 2024-06-05 19:01:37 -04:00
Dessalines
e8cfb5665f
When banning from local communities, make sure they aren't deleted or removed. (#4784)
- This is causing some federation issues.
- Context: #4782
2024-06-05 18:59:46 -04:00
Nutomic
bb94fb1c79
Revert apub library 0.5.7 (#4783)
Wasnt necessary after all
2024-06-05 18:04:02 -04:00
Dessalines
92214a9364 Version 0.19.4-rc.7 2024-06-05 17:30:43 -04:00
Nutomic
78ae874b89
Apub library 0.5.7 (#4781) 2024-06-05 17:28:33 -04:00
Dessalines
a947474c64 Version 0.19.4-rc.6 2024-06-04 08:32:08 -04:00
Dessalines
8bf17946bd
Fix issue with avatar / icon deletion when saving settings. (#4774)
* Fix issue with avatar / icon deletion when saving settings.

- Fixes #4763

* Update crates/api_common/src/request.rs

Co-authored-by: dullbananas <dull.bananas0@gmail.com>

* Fixing an existing test, and adding another for replace images.

---------

Co-authored-by: dullbananas <dull.bananas0@gmail.com>
2024-06-04 08:28:22 -04:00
dullbananas
9ceb5b6386
Clean up build_update_instance_form in scheduled_tasks.rs (#4775)
* Clean up build_update_instance_form in scheduled_tasks.rs

* remove unused import
2024-06-04 08:04:16 -04:00
Dessalines
aefb41b551
Remove .json from nodeinfo urls, according to spec. (#4773) 2024-06-03 17:30:16 -04:00
Dessalines
4195a9b5a1
Fetch nodeinfo href from .well-known/nodeinfo . Fixes #4757 (#4765)
* Fetch nodeinfo href from .well-known/nodeinfo . Fixes #4757

* Addressing PR comments.

* Fixing clippy.

* Adding tests.
2024-06-03 17:30:00 -04:00
Dessalines
69b4c6647b Version 0.19.4-rc.5 2024-06-01 13:30:00 -04:00
renovate[bot]
f7fe0d46fc
Update Rust crate console-subscriber to 0.2.0 (#4771)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 22:35:34 -04:00
renovate[bot]
609a6411a7
Update pnpm to v9.1.4 (#4770)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 22:24:17 -04:00
renovate[bot]
44666a34a2
Update dependency ts-jest to v29.1.4 (#4768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 22:04:44 -04:00
renovate[bot]
6db878f761
Update dependency typescript to v5.4.5 (#4769)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 21:34:29 -04:00
renovate[bot]
6031709fcf
Update Rust crate serde to v1.0.203 (#4766)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 21:14:05 -04:00
renovate[bot]
4d9e38d875
Update asonix/pictrs Docker tag to v0.5.14 (#4767)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-31 21:13:39 -04:00
Dessalines
6a6c915014
Changing NodeInfo metadata to HashMap from vector. Fixes #4762 (#4764) 2024-05-31 16:38:46 -04:00
phiresky
96b7afc0b1
upgrade rust to 1.78 to fix diesel cli (#4761) 2024-05-31 08:39:45 -04:00
Felix Ableitner
d2083f79d9 Version 0.19.4-rc.4 2024-05-30 11:55:34 +02:00
phiresky
e8a7bb07a3
fix both permanent stopping of federation queues and multiple creation of the same federation queues (#4754)
Co-authored-by: Nutomic <me@nutomic.com>
2024-05-30 05:08:27 -04:00
Richard Schwab
91e57ff954
Prevent bot replies from increasing unread reply count when bot accounts are not shown (#4747)
* Prevent bot replies from increasing unread reply count when bot accounts are not shown

* Pass LocalUser for unread replies count query

* Prevent bot mentions from increasing unread reply count when bot accounts are not shown
2024-05-29 17:55:15 -04:00
phiresky
7d80a3c7d6
replace instanceid with domain (#4753) 2024-05-29 23:10:25 +02:00
Dessalines
abcfa266af
Fixing slowness in saved post fetching. #4756 (#4758)
* Fixing slowness in saved post fetching. #4756

* Also fix comment_view.rs
2024-05-29 17:03:42 -04:00
SleeplessOne1917
51970ffc81
Update dependencies to alleviate cargo audit peer dependency vulnerability (#4750) 2024-05-28 17:47:21 -07:00
Dessalines
fd6a1283a5 Version 0.19.4-rc.3 2024-05-27 09:37:58 -04:00
Nutomic
af034f3b5e
Unit tests and cleanup for outgoing federation code (#4733)
* test setup

* code cleanup

* cleanup

* move stats to own file

* basic test working

* cleanup

* processes test

* more test cases

* fmt

* add file

* add assert

* error handling

* fmt

* use instance id instead of domain for stats channel
2024-05-27 09:34:58 -04:00
Dessalines
0d5db29bc9
After creating a comment, update the unread comments for the post. (#4742)
* After creating a comment, update the unread comments for the post.

- Fixes #3863

* Addressing PR comments.

* Add comment.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-05-27 12:55:44 +02:00
dullbananas
ec77c00ef8
Fix lost separation caused by comment width change (#4739)
* Update post_view.rs

* Update structs.rs

* Update worker.rs

* Update worker.rs
2024-05-23 14:05:35 -04:00
Dessalines
69bdcb3069 Version 0.19.4-rc.2 2024-05-23 12:10:33 -04:00
Dessalines
6a6108ac55
Fixing proxied images for federated posts. (#4737)
* Fixing proxied images for federated posts.

- Also added test.
- Fixes #4736

* Address PR comments.
2024-05-23 11:11:25 -04:00
Nutomic
b2c1a14234
Correct url for nodeinfo version (#4734)
* Correct url for nodeinfo version

* add compat redirect

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-05-23 10:59:56 -04:00
Nutomic
d8dc38eb06
Upgrade dependencies (#4740) 2024-05-23 10:55:20 -04:00
Nutomic
c96017c009
Configure max comment width in clippy (#4738)
* Configure max comment width in clippy

* update default config
2024-05-23 08:46:26 -04:00
Dessalines
9aa565b559 Version 0.19.4-rc.1 2024-05-22 08:58:31 -04:00
Dessalines
7d7cd8ded4
Dont show replies / mentions from blocked users. Fixes #4227 (#4727)
* Dont show replies / mentions from blocked users. Fixes #4227

* Adding unit tests for reply and mention views.

- Also cleaned up some unwraps in the tests.

* Add allow deprecated to pass clippy for deprecated wav crate.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-05-22 08:50:26 -04:00
Nutomic
943c31cc72
Allow passing command line params via environment (fixes #4603) (#4729)
* Allow passing command line params via environment (fixes #4603)

* add prefix

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-05-22 08:39:01 -04:00
Nutomic
973f39601c
Dont allow removing comment which was deleted (fixes #4731) (#4732) 2024-05-22 08:29:01 -04:00
Felix Ableitner
a39c19c9db Version 0.19.4-beta.8 2024-05-22 10:30:38 +02:00
Dessalines
55f84dd38a
Fixing proxy images (#4722)
* Adding an image_details table to store image dimensions.

- Adds an image_details table, which stores the height,
  width, and content_type for local and remote images.
- For LocalImages, this information already comes back with
  the upload.
- For RemoteImages, it calls the pictrs details endpoint.
- Fixed some issues with proxying non-image urls.
- Fixes #3328
- Also fixes #4703

* Running sql format.

* Running fmt.

* Don't fetch metadata in background for local API requests.

* Dont export remote_image table to typescript.

* Cleaning up validate.

* Dont proxy url.

* Fixing tests, fixing issue with federated thumbnails.

* Fix tests.

* Updating corepack, fixing issue.

* Refactoring image inserts to use transactions.

* Use select exists again.

* Fixing imports.

* Fix test.

* Removing pointless backgrounded metadata generation version.

* Removing public pictrs details route.

* Fixing clippy.

* Fixing proxy image fetching. Fixes #4703

- This extracts only the proxy image fixes from #4704, leaving off
  thumbnails.

* Fix test.

* Addressing PR comments.

* Address PR comments 2.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-05-22 10:28:47 +02:00
Nutomic
6b46a70535
Extra logging to debug duplicate activities (ref #4609) (#4726)
* Extra logging to debug duplicate activities (ref #4609)

* Fix logging for api tests

* fmt
2024-05-21 14:47:06 -04:00
Nutomic
4ffaa93431
Dont allow reusing password reset token, use normal rate limit (#4719)
* Dont allow reusing password reset token, use normal rate limit

* fix
2024-05-21 14:46:49 -04:00
flamingos-cant
a0ad7806cb
Increase alt_text size to 1500 (#4724) 2024-05-17 13:03:19 -04:00
Nutomic
99aac07714
Mark database fields as sensitive so they dont show up in logs (#4720)
* Mark database fields as sensitive so they dont show up in logs

* add file

* fix test

* Update crates/apub/src/objects/person.rs

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>

* Update crates/apub/src/objects/community.rs

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>

* Update crates/apub/src/objects/instance.rs

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-05-16 20:41:57 -04:00
Dessalines
1a4aa3eaba
Stop using a diesel_cli docker image, use cargo-install in woodpecker. (#4723)
* Stop using a diesel_cli docker image, use cargo-binstall in woodpecker.

- The diesel_cli image is 500MB, and rebuilt daily. Much easier to use
  binstall to install it.

* Trying out a multiline var.

* Try sequence merges 1

* Try removing features.

* Try path fix.

* Abstracting diesel cli install.

* Try installing mysql and postgres.

* Try installing mysql and postgres 2.

* Try installing mysql and postgres 3.

* Try installing mysql and postgres 4.

* Try installing mysql and postgres 5.

* Try installing mysql and postgres 6.

* Try installing mysql and postgres 7.

* Try installing mysql and postgres 8.

* Try installing mysql and postgres 9.

* Try installing mysql and postgres 10.

* Try installing mysql and postgres 11.

* Try installing mysql and postgres 12.

* Try installing mysql and postgres 13.

* Add logging line.

* Add logging line 2.

* Add logging line 3.

* Add logging line 4.

* Removing binstall.

* Extract taplo into its own image, ignore binstall.

* Use a smaller taplo.

* taplo is the same image.
2024-05-16 16:41:36 -04:00
Nutomic
93c9a5f2b1
Dont federate post locking via Update activity (#4717)
* Dont federate post locking via Update activity

* cleanup

* add missing mod log entries

* update assets
2024-05-15 07:36:00 -04:00
Nutomic
9a9d518153
Fix import blocked objects (#4712)
* Allow importing partial backup (fixes #4672)

* Fetch blocked objects if not known locally (fixes #4669)

* extract helper fn

* add comment

* cleanup

* remove test

* fmt

* remove .ok()
2024-05-14 23:03:43 -04:00
Nutomic
7fb03c502e
Add test to ensure reports are sent to user's home instance (ref #4701) (#4711)
* Add test to ensure reports are sent to user's home instance (ref #4701)

* enable all tests

* set package-manager-strict=false
2024-05-14 22:48:24 -04:00
Nutomic
49bb17b583
Stricter rate limit for login (#4718) 2024-05-14 22:43:43 -04:00
Nutomic
723cb549d4
Allow importing partial backup (fixes #4672) (#4705)
* Allow importing partial backup (fixes #4672)

* Dont throw error on empty LocalUser::update

* fix tests
2024-05-14 22:37:30 -04:00
Nutomic
8b6a4c060e
Make nodeinfo standard compliant, upgrade to nodeinfo 2.1 (fixes #4702) (#4706)
* Always set activitypub protocol in nodeinfo response (fixes #4702)

* Add mandatory fields
2024-05-13 22:53:20 -04:00
Dessalines
cb80980027 Version 0.19.4-beta.7 2024-05-11 13:51:09 -04:00
dullbananas
c4fc3a8ede
Optimize stuff in attempt to fix high amount of locks, and fix comment_aggregates.child_count (#4696)
* separate triggers

* auto_explain.log_triggers=on

* Revert "auto_explain.log_triggers=on"

This reverts commit 078b2dbb9b.

* Revert "separate triggers"

This reverts commit 95600da4af.

* bring back migration

* re-order statements

* add comment about statement ordering

* no redundant updates

* optimize post_aggregates update in comment trigger

* set comment path in trigger

* update comment_aggregates.child_count using trigger

* move `LEFT JOIN post` to inner query

* clean up newest_comment_time_necro

* add down.sql
2024-05-09 08:18:55 -04:00
Nutomic
b4f9ef24a5
Dont exit early when running only scheduled tasks (#4707)
* Dont exit early when running only scheduled tasks (fixes #4709)

* fix
2024-05-08 14:56:44 +02:00
Nutomic
866d752a3c
Instance.preferred_username should be optional (fixes #4701) (#4713) 2024-05-08 08:01:04 -04:00
Nutomic
e0b1d0553d
Add timeout for processing incoming activities (#4708)
* Add timeout for processing incoming activities

* move to const
2024-05-08 08:00:55 -04:00
Nutomic
7c146272c3
Federate with wordpress, improvements for NodeBB, Discourse federation (#4692)
* Federate with wordpress

* upgrade apub lib with fix

* Also read post's community from `audience`

* cleanup

* cargo update

* upgrade apub lib

* add wordpress test activity
2024-05-07 16:20:43 -04:00
Nutomic
cfdc732d3a
On registration set show_nsfw based on site.content_warning (#4616)
Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-05-07 16:18:58 -04:00
Tim Coombs
522f974e30
fix: use docker compose v2 (#4622)
* fix: use docker compose v2

* Using sudo tee.

* fix: correct postgres sed command

---------

Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-05-07 11:41:40 +02:00
SleeplessOne1917
b152be7951
Update rustls (#4690)
* Update rustls

* Format code
2024-05-03 16:06:14 -04:00
SleeplessOne1917
485b0f1a54
Replace unmaintained encoding dep with maintained encoding_rs dep (#4694)
* Replace dependency on unmaintained encoding crate with dep on maintained encoding_rs crate

* Update lockfile

* Taplo format Cargo.toml

* Use better variable name

* Replace into_owned with into
2024-05-03 10:42:48 +00:00
Nutomic
7540b02723
Update activitypub library (#4691)
https://github.com/LemmyNet/activitypub-federation-rust/releases/tag/0.5.5
2024-05-02 12:46:34 -04:00
Nutomic
7746db4169
Testing and minor fix for federation with Discourse (#4628)
* Testing and minor fix for federation with Discourse

* prettier
2024-05-02 07:49:19 -04:00
Dessalines
db2ce81fc4
Show trigger logging. #4681 (#4688) 2024-05-01 18:46:14 -04:00
renovate[bot]
4175a1af80
chore(deps): update rust crate serde_with to 3.8.1 (#4687)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 23:47:02 -04:00
renovate[bot]
563280456e
chore(deps): update pnpm to v9.0.6 (#4682)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 23:43:14 -04:00
Dessalines
2fecb7ecdf
Dont show own comments for liked and disliked_only. Fixes #4675 (#4679)
Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-30 23:26:55 -04:00
renovate[bot]
2c6f9c7fd5
chore(deps): update rust crate serde to 1.0.199 (#4684)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 23:17:23 -04:00
renovate[bot]
e338e59868
fix(deps): update rust crate lettre to 0.11.7 (#4685)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 22:54:16 -04:00
renovate[bot]
b0caa85ed4
chore(deps): update rust crate base64 to 0.22.1 (#4683)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 22:30:19 -04:00
Dessalines
ad60d91f5c
Dont publish lemmy_db_perf to fix crates.io publish. Fixes #4678 (#4680) 2024-04-30 17:51:12 +00:00
Dessalines
6423d2dde5 Version 0.19.4-beta.6 2024-04-30 06:38:44 -04:00
Nutomic
12163701e7
Avoid crash when handling urls without domain (#4676)
* Avoid crash when handling urls without domain

* Add some extra checks
2024-04-30 06:33:37 -04:00
Dessalines
5c35e97a75
Dont show deleted / removed posts when searching. Fixes #4576 (#4671)
* Dont show deleted / removed posts when searching. Fixes #4576

* Address PR comments.

* Clean up comment removed also.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-30 12:24:18 +02:00
SleeplessOne1917
b05f221565
Remove login step from publish to crates.io (#4674) 2024-04-30 00:07:03 +00:00
Nutomic
beec080274
Testing for federation with NodeBB, make community.followers_url optional (#4629)
* Testing for federation with NodeBB, make community.followers_url optional

* clippy
2024-04-29 12:34:11 +02:00
Dessalines
492d8f1b01
Fix communities with broken outboxes, and use PostView. Fixes #4658 (#4668)
* Fix communities with broken outboxes, and use PostView. Fixes #4658

* Fixing tests.

* Dont pass ref and clone.
2024-04-29 12:22:00 +02:00
dullbananas
d3737d4453
Optimize actor_language.rs (#4612)
* Remove useless transaction in actor_language.rs

* Update actor_language.rs

* site

* community

* Update actor_language.rs

* Update actor_language.rs

* Update actor_language.rs

* Update actor_language.rs

* Update actor_language.rs
2024-04-27 10:59:58 -04:00
Dessalines
b459949f57 Version 0.19.4-beta.5 2024-04-25 19:59:24 -04:00
Dessalines
93f5df2d2a
Adding post_id desc to all post_aggregates indexes. Fixes #4618 (#4662)
* Adding post_id desc to all post_aggregates indexes. Fixes #4618

* Running pg_format

* Not rebuilding indexes which had no changes.
2024-04-25 18:19:02 -04:00
Nutomic
cf426493e1
Fix community add mod check (fixes #4624) (#4667) 2024-04-25 11:47:38 -04:00
Dessalines
8e3ff0408e
Fixing extra modlog entries when post_id or comment_id is given. (#4664)
- Previously when given a post_id, it didn't filter out any other
  modlog entries, such as community removals. This fixes that problem.
- Context: https://github.com/LemmyNet/lemmy-ui/pull/2437

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-25 10:26:17 +02:00
Dessalines
66e06b3952
Removing scheme from block urls. Fixes #4656 (#4659)
* Removing scheme from block urls. Fixes #4656

* Fix comment.

* Fixing domain checking.

* Removing pointless URL building in url blocklist regex.

* Remove trailing /
2024-04-23 23:15:20 -04:00
Kroese
6b9d9dfaa5
Fix broken thumbnails (#4661)
* Check is_image_post flag

* Keep cargo_fmt happy

* Filter on is_image_post

* Trigger CI

* Keep cargo_fmt happy
2024-04-23 22:52:56 -04:00
tracyspacy
0eaf8d33e7
Filter_removed_comments_from_search (#4634)
* filter_removed_comments_from_search

* Revert "filter_removed_comments_from_search"

This reverts commit c6d6490afa.

* filtering_removed_comments_search

* filter_deleted_comments

* Revert "filter_deleted_comments"

This reverts commit 7dc1d13d24.

* Revert "filtering_removed_comments_search"

This reverts commit 6e9b1de7a2.

* filtering_removed_dELeted_comments_search
2024-04-22 11:33:02 -04:00
renovate[bot]
c31a29ec7f
chore(deps): update dependency @types/node to v20.12.7 (#4647)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 18:56:35 -04:00
renovate[bot]
80635c9e24
chore(deps): update rust crate base64 to 0.22.0 (#4651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 18:40:38 -04:00
renovate[bot]
95d75e07b2
chore(deps): update pnpm to v9.0.4 (#4649)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 18:23:06 -04:00
renovate[bot]
efbfdc9340
chore(deps): update docker/dockerfile docker tag to v1.7 (#4650)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 18:12:26 -04:00
renovate[bot]
1ae3aab764
chore(deps): update dependency typescript to v5.4.5 (#4648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-19 18:11:21 -04:00
renovate[bot]
f68881c552
chore: Configure Renovate (#4644)
* Add renovate.json

* Updating renovate.

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Dessalines <tyhou13@gmx.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-04-19 17:00:23 -04:00
Dessalines
2ba1ba88b8
Upgrading deps. (#4645) 2024-04-19 16:50:27 -04:00
Dessalines
079fa0b8f6 Version 0.19.4-beta.4 2024-04-18 21:11:15 -04:00
dependabot[bot]
b0a740d5c5
Bump h2 from 0.3.25 to 0.3.26 (#4639)
Bumps [h2](https://github.com/hyperium/h2) from 0.3.25 to 0.3.26.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.26/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.25...v0.3.26)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-18 21:05:45 -04:00
Kroese
ee46242a43
Update aarch64-lemmy-linux-gnu to v0.3.0 (#4638) 2024-04-18 20:34:55 -04:00
dullbananas
4ba6221e04
Move SQL triggers from migrations into reusable sql file (#4333)
* stuff

* stuff including batch_upsert function

* stuff

* do things

* stuff

* different timestamps

* stuff

* Revert changes to comment.rs

* Update comment.rs

* Update comment.rs

* Update post_view.rs

* Update utils.rs

* Update up.sql

* Update up.sql

* Update down.sql

* Update up.sql

* Update main.rs

* use anyhow macro

* Create down.sql

* Create up.sql

* Create replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update utils.rs

* Update .woodpecker.yml

* Update sql_format_check.sh

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Create dump_schema.sh

* Update start_dev_db.sh

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* stuff

* Update replaceable_schema.sql

* Update .pg_format

* fmt

* stuff

* stuff (#21)

* Update replaceable_schema.sql

* Update up.sql

* Update replaceable_schema.sql

* fmt

* update cargo.lock

* stuff

* Update replaceable_schema.sql

* Remove truncate trigger because truncate is already restricted by foreign keys

* Update replaceable_schema.sql

* fix some things

* Update replaceable_schema.sql

* Update replaceable_schema.sql

* Update .woodpecker.yml

* stuff

* fix TG_OP

* Psql env vars

* try to fix combine_transition_tables parse error

* Revert "try to fix combine_transition_tables parse error"

This reverts commit 75d00a4626.

* refactor combine_transition_tables

* try to fix create_triggers

* fix some things

* try to fix combined_transition_tables

* fix sql errors

* update comment count in post trigger

* fmt

* Revert "fmt"

This reverts commit a5bcd0834b.

* Revert "update comment count in post trigger"

This reverts commit 0066a4b42b.

* fix everything

* Update replaceable_schema.sql

* actually fix everything

* refactor create_triggers

* fix

* add semicolons

* add is_counted function and fix incorrect bool operator in update_comment_count_from_post

* refactor comment trigger

* refactor post trigger

* fix

* Delete crates/db_schema/src/utils/series.rs

* subscribers_local

* edit migrations

* move migrations

* remove utils::series module declaration

* fix everything

* stuff

* Move sql to schema_setup dir

* utils.sql

* delete .pg_format

* Update .woodpecker.yml

* Update sql_format_check.sh

* Update .woodpecker.yml

* Merge remote-tracking branch 'upstream/main' into bliss

* fmt

* Create main.rs

* Update lib.rs

* Update main.rs

* Update .woodpecker.yml

* Update main.rs

* Update Cargo.toml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update triggers.sql

* YAY

* Update mod.rs

* Update Cargo.toml

* a

* Update Cargo.toml

* Update Cargo.toml

* Delete crates/db_schema/src/main.rs

* Update Cargo.toml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update utils.sql

* Update utils.sql

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update down.sql

* Update up.sql

* Update triggers.sql

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update triggers.sql

* Update down.sql

* Update .woodpecker.yml

* Update Cargo.toml

* Update .woodpecker.yml

* Update Cargo.toml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update mod.rs

* Update Cargo.toml

* Update mod.rs

* make dump_schema.sh executable

* fix dump_schema.sh

* defer

* diff dumps

* fmt

* Update utils.sql

* Update .woodpecker.yml

* use correct version for pg_dump

* Update .woodpecker.yml

* Update .woodpecker.yml

* change migration date

* atomic site_aggregates insert

* temporarily repeat tests in CI

* drop r schema in CI migration check

* show ReceivedActivity::create error

* move check_diesel_migration CI step

* Update .woodpecker.yml

* Update scheduled_tasks.rs

* Update scheduled_tasks.rs

* update cargo.lock

* move sql files

* move rank functions

* filter post_aggregates update

* fmt

* cargo fmt

* replace post_id with id

* update cargo.lock

* avoid locking rows that need no change in up.sql

* only run replaceable_schema if migrations were run

* debug ci test failure

* make replaceable_schema work in CI

* Update .woodpecker.yml

* remove println

* Use migration revert and git checkout

* Update schema_setup.rs

* Fix

* Update schema_setup.rs

* Update schema_setup.rs

* Update .woodpecker.yml

---------

Co-authored-by: Nutomic <me@nutomic.com>
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-04-17 20:58:44 -04:00
Nutomic
31829b6c05
Untangle thumbnail generation logic (ref #4604) (#4615)
* Untangle thumbnail generation logic (ref #4604)

* prettier

* test cleanup

* fix tests

* also consider opengraph image for local thumbnail generation
2024-04-17 10:36:45 -04:00
TechVest
b0370ae2fd
chore: fix some comments (#4637)
Signed-off-by: TechVest <techdashen@qq.com>
2024-04-17 14:35:54 +02:00
Dessalines
6efab9aab1
Creating a LocalImageView, so that front ends have the Person struct. (#4631)
* Creating a LocalImageView, so that front ends have the Person struct.

* Removing local_user from LocalImageView.

* Add uploader check.
2024-04-16 19:20:44 -04:00
Dessalines
d075acce43
Make all single-fetch database calls return an Option. (#4617)
- Diesel ordinarily throws an error when no results are returned for a
  single fetch, which is a bit confusing. This PR ensures that the
  missing value cases are all caught, and wrapped with new LemmyErrors,
  rather than diesel errors.
- Fixes #4601
2024-04-16 14:48:15 +02:00
Nutomic
3a0c1dca90
Avoid overwriting local objects via federation (#4611)
* Dont allow federation to overwrite local objects

* is_local check in apub lib

* use imports

* fix check, update lib

* use verify_is_remote_object()

* submodule
2024-04-11 10:05:49 -04:00
dullbananas
0f6b13a4ec
Test coverage (#4596)
* update .gitignore

* add test-with-coverage.sh

* coverage gutters extension comment

* move lcov.info to target folder

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-11 10:32:07 +02:00
Dessalines
64760ec960 Version 0.19.4-beta.3 2024-04-10 11:03:11 -04:00
Dessalines
555f789269
Fixing custom_thumbnail updates. (#4593)
* Fixing custom_thumbnail updates.

* Fixing issue with image posts.

* Fixing upgrade deps script.

* Adding API tests for custom thumbnails.

* Remove pointless todo.

* Address PR comments.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-10 10:59:46 -04:00
Dessalines
5dea21d531
Convert all Result<..., LemmyError> into LemmyResult<...> Fixes #4613 (#4614)
* Convert all Result<..., LemmyError> into LemmyResult<...> Fixes #4613

* Fixing clippy.
2024-04-10 10:14:11 -04:00
Kroese
d5622a65f8
Fix for PictrsImageMode::None (#4604)
* Fix PictrsImageMode::None

* Update crates/api_common/src/request.rs

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>

* Fix formatting

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-10 10:09:54 -04:00
Nutomic
9059de8569
Allow fetching from local url, add fetch redirect test (fixes #4526) (#4607)
* Allow fetching from local url, at fetch redirect test (fixes #4526)

* prettier

* update lib

* update apub lib
2024-04-10 10:04:57 -04:00
Nutomic
0203b62a6d
Ignore old federated post edits (ref #4529) (#4586)
* Ignore old federated post edits (ref #4529)

* use filter on insert

* coalesce(updated, published)

* avoid comment conflict clause

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-10 10:03:51 -04:00
Dessalines
99d585b7be
Change defaults on user vote display mode to upvotes + downvotes (#4599)
* Change defaults on user vote display mode to upvotes + downvotes

* Forgot to regenerate the rows.

* Drop and re-add columns instead.
2024-04-10 10:47:05 +02:00
Nutomic
b4670988b5
Change exponential backoff algorithm for federation send (#4597)
* Limit federation send retry interval to one hour

* clippy

* avoid overflow

* change base for exp backoff

* ignore first error

* fix day duration
2024-04-09 19:33:01 -04:00
Nutomic
1d0a6ac08f
Avoid breaking api change, reduce api cache duration (#4610)
* Dont mark site.public_key as `serde(skip)` to avoid breaking change (fixes #4605)

* Reduce cache duration for api
2024-04-09 10:10:20 -04:00
Dessalines
8e54a4a6cc
Fixing bug where comment replies wouldn't be sent to blocked instances. (#4595)
* Fixing bug where comment replies wouldn't be sent to blocked instances.

- Instance blocks should only affect communities, not comments.
- Fixes #4590

* Revert "Fixing bug where comment replies wouldn't be sent to blocked instances."

This reverts commit 1349aa351a.

* Only block replies from the community's instance id.

- Also refactor send_local_notifs slightly, since it has to fetch the
  community now.
- Fixes #4590

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-04-08 16:26:24 +02:00
jim-taylor-business
a14ebefd24
When env variable is set, any config file will be ignored and the default settings will be used (#4594)
* do not panic when no config file found use defaults

* formatting

* implement env variable

* ermove commented code

* remove redundant comment

* remove redundant space

* simplify check logic

* format

* returns and messages

* correct mistake
2024-04-08 12:05:54 +02:00
Dessalines
705e86eb4c
Fixing docker release (#4592)
* Re-add notif on tag failure.

* Upping rust version.

* Version 0.19.4-beta.1a

* Try again.

* Version 0.19.4-beta.1b

* Removing unstable inspect.

* Version 0.19.4-beta.1c

* Remove use release cache.

* Trying to fix cargo publish 1.

* Version 0.19.4-beta.1d

* Re-adding publish release

* Version 0.19.4-beta.2

* Fixing workspace for lemmy_federate
2024-04-04 16:14:59 +02:00
Dessalines
a1d632e582
Re-add notif on tag failure. (#4591) 2024-04-04 10:21:31 +02:00
451 changed files with 18855 additions and 11842 deletions

View file

@ -3,3 +3,5 @@ edition = "2021"
imports_layout = "HorizontalVertical"
imports_granularity = "Crate"
group_imports = "One"
wrap_comments = true
comment_width = 100

View file

@ -2,8 +2,14 @@
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
variables:
- &rust_image "rust:1.77"
- &rust_image "rust:1.81"
- &rust_nightly_image "rustlang/rust:nightly"
- &install_pnpm "corepack enable pnpm"
- &install_binstall "wget -O- https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xvz -C /usr/local/cargo/bin"
- install_diesel_cli: &install_diesel_cli
- apt-get update && apt-get install -y postgresql-client
- cargo install diesel_cli --no-default-features --features postgres
- export PATH="$CARGO_HOME/bin:$PATH"
- &slow_check_paths
- event: pull_request
path:
@ -25,15 +31,6 @@ variables:
".gitmodules",
]
# Broken for cron jobs currently, see
# https://github.com/woodpecker-ci/woodpecker/issues/1716
# clone:
# git:
# image: woodpeckerci/plugin-git
# settings:
# recursive: true
# submodule_update_remote: true
steps:
prepare_repo:
image: alpine:3
@ -42,7 +39,7 @@ steps:
- git submodule init
- git submodule update
when:
- event: pull_request
- event: [pull_request, tag]
prettier_check:
image: tmknom/prettier:3.0.0
@ -66,7 +63,7 @@ steps:
- event: pull_request
cargo_fmt:
image: rustlang/rust:nightly
image: *rust_nightly_image
environment:
# store cargo data in repo folder so that it gets cached between steps
CARGO_HOME: .cargo_home
@ -77,11 +74,9 @@ steps:
- event: pull_request
cargo_machete:
image: rustlang/rust:nightly
image: *rust_nightly_image
commands:
- wget https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz
- tar -xvf cargo-binstall-x86_64-unknown-linux-musl.tgz
- cp cargo-binstall /usr/local/cargo/bin
- *install_binstall
- cargo binstall -y cargo-machete
- cargo machete
when:
@ -133,26 +128,17 @@ steps:
when: *slow_check_paths
check_diesel_schema:
image: willsquire/diesel-cli
image: *rust_image
environment:
CARGO_HOME: .cargo_home
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
commands:
- <<: *install_diesel_cli
- diesel migration run
- diesel print-schema --config-file=diesel.toml > tmp.schema
- diff tmp.schema crates/db_schema/src/schema.rs
when: *slow_check_paths
check_diesel_migration_revertable:
image: willsquire/diesel-cli
environment:
CARGO_HOME: .cargo_home
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
commands:
- diesel migration run
- diesel migration redo
when: *slow_check_paths
check_db_perf_tool:
image: *rust_image
environment:
@ -171,7 +157,7 @@ steps:
CARGO_HOME: .cargo_home
commands:
- rustup component add clippy
- cargo clippy --workspace --tests --all-targets --features console -- -D warnings
- cargo clippy --workspace --tests --all-targets -- -D warnings
when: *slow_check_paths
cargo_build:
@ -189,11 +175,45 @@ steps:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1"
CARGO_HOME: .cargo_home
LEMMY_TEST_FAST_FEDERATION: "1"
commands:
- export LEMMY_CONFIG_LOCATION=../../config/config.hjson
- cargo test --workspace --no-fail-fast
when: *slow_check_paths
check_diesel_migration:
# TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server
image: *rust_image
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
RUST_BACKTRACE: "1"
CARGO_HOME: .cargo_home
DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
PGUSER: lemmy
PGPASSWORD: password
PGHOST: database
PGDATABASE: lemmy
commands:
# Install diesel_cli
- <<: *install_diesel_cli
# Run all migrations
- diesel migration run
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f before.sqldump
# Make sure that the newest migration is revertable without the `r` schema
- diesel migration redo
# Run schema setup twice, which fails on the 2nd time if `DROP SCHEMA IF EXISTS r CASCADE` drops the wrong things
- alias lemmy_schema_setup="target/lemmy_server --disable-scheduled-tasks --disable-http-server --disable-activity-sending"
- lemmy_schema_setup
- lemmy_schema_setup
# Make sure that the newest migration is revertable with the `r` schema
- diesel migration redo
# Check for changes in the schema, which would be caused by an incorrect migration
- psql -c "DROP SCHEMA IF EXISTS r CASCADE;"
- pg_dump --no-owner --no-privileges --no-table-access-method --schema-only --no-sync -f after.sqldump
- diff before.sqldump after.sqldump
when: *slow_check_paths
run_federation_tests:
image: node:20-bookworm-slim
environment:
@ -201,7 +221,7 @@ steps:
DO_WRITE_HOSTS_FILE: "1"
commands:
- *install_pnpm
- apt update && apt install -y bash curl postgresql-client
- apt-get update && apt-get install -y bash curl postgresql-client
- bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/
- pnpm i
@ -220,10 +240,13 @@ steps:
publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
settings:
repo: dessalines/lemmy
dockerfile: docker/Dockerfile
username:
from_secret: docker_username
password:
from_secret: docker_password
platforms: linux/amd64, linux/arm64
build_args:
- RUST_RELEASE_MODE=release
@ -233,10 +256,13 @@ steps:
nightly_build:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
settings:
repo: dessalines/lemmy
dockerfile: docker/Dockerfile
username:
from_secret: docker_username
password:
from_secret: docker_password
platforms: linux/amd64,linux/arm64
build_args:
- RUST_RELEASE_MODE=release
@ -248,11 +274,11 @@ steps:
publish_to_crates_io:
image: *rust_image
commands:
- 'echo "pub const VERSION: &str = \"$(git describe --tag)\";" > "crates/utils/src/version.rs"'
- cargo install cargo-workspaces
- *install_binstall
# Install cargo-workspaces
- cargo binstall -y cargo-workspaces
- cp -r migrations crates/db_schema/
- cargo login "$CARGO_API_TOKEN"
- cargo workspaces publish --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
- cargo workspaces publish --token "$CARGO_API_TOKEN" --from-git --allow-dirty --no-verify --allow-branch "${CI_COMMIT_TAG}" --yes custom "${CI_COMMIT_TAG}"
secrets: [cargo_api_token]
when:
- event: tag
@ -276,7 +302,8 @@ steps:
services:
database:
image: postgres:16-alpine
# 15-alpine image necessary because of diesel tests
image: pgautoupgrade/pgautoupgrade:15-alpine
environment:
POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password

4110
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
[workspace.package]
version = "0.19.4-beta.1c"
version = "0.19.6-beta.7"
edition = "2021"
description = "A link aggregator for the fediverse"
license = "AGPL-3.0"
@ -36,16 +36,6 @@ opt-level = "z" # Optimize for size.
debug = 0
[features]
embed-pictrs = ["pict-rs"]
# This feature requires building with `tokio_unstable` flag, see documentation:
# https://docs.rs/tokio/latest/tokio/#unstable-features
console = [
"console-subscriber",
"opentelemetry",
"opentelemetry-otlp",
"tracing-opentelemetry",
"reqwest-tracing/opentelemetry_0_16",
]
json-log = ["tracing-subscriber/json"]
default = []
@ -67,8 +57,8 @@ members = [
[workspace.lints.clippy]
cast_lossless = "deny"
complexity = "deny"
correctness = "deny"
complexity = { level = "deny", priority = -1 }
correctness = { level = "deny", priority = -1 }
dbg_macro = "deny"
explicit_into_iter_loop = "deny"
explicit_iter_loop = "deny"
@ -79,36 +69,38 @@ inefficient_to_string = "deny"
items-after-statements = "deny"
manual_string_new = "deny"
needless_collect = "deny"
perf = "deny"
perf = { level = "deny", priority = -1 }
redundant_closure_for_method_calls = "deny"
style = "deny"
suspicious = "deny"
style = { level = "deny", priority = -1 }
suspicious = { level = "deny", priority = -1 }
uninlined_format_args = "allow"
unused_self = "deny"
unwrap_used = "deny"
unimplemented = "deny"
[workspace.dependencies]
lemmy_api = { version = "=0.19.4-beta.1c", path = "./crates/api" }
lemmy_api_crud = { version = "=0.19.4-beta.1c", path = "./crates/api_crud" }
lemmy_apub = { version = "=0.19.4-beta.1c", path = "./crates/apub" }
lemmy_utils = { version = "=0.19.4-beta.1c", path = "./crates/utils", default-features = false }
lemmy_db_schema = { version = "=0.19.4-beta.1c", path = "./crates/db_schema" }
lemmy_api_common = { version = "=0.19.4-beta.1c", path = "./crates/api_common" }
lemmy_routes = { version = "=0.19.4-beta.1c", path = "./crates/routes" }
lemmy_db_views = { version = "=0.19.4-beta.1c", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.4-beta.1c", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.4-beta.1c", path = "./crates/db_views_moderator" }
activitypub_federation = { version = "0.5.2", default-features = false, features = [
lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" }
lemmy_api_crud = { version = "=0.19.6-beta.7", path = "./crates/api_crud" }
lemmy_apub = { version = "=0.19.6-beta.7", path = "./crates/apub" }
lemmy_utils = { version = "=0.19.6-beta.7", path = "./crates/utils", default-features = false }
lemmy_db_schema = { version = "=0.19.6-beta.7", path = "./crates/db_schema" }
lemmy_api_common = { version = "=0.19.6-beta.7", path = "./crates/api_common" }
lemmy_routes = { version = "=0.19.6-beta.7", path = "./crates/routes" }
lemmy_db_views = { version = "=0.19.6-beta.7", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.6-beta.7", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.6-beta.7", path = "./crates/db_views_moderator" }
lemmy_federate = { version = "=0.19.6-beta.7", path = "./crates/federate" }
activitypub_federation = { version = "0.6.0-alpha1", default-features = false, features = [
"actix-web",
] }
diesel = "2.1.4"
diesel = "2.1.6"
diesel_migrations = "2.1.0"
diesel-async = "0.4.1"
serde = { version = "1.0.197", features = ["derive"] }
serde_with = "3.7.0"
actix-web = { version = "4.5.1", default-features = false, features = [
serde = { version = "1.0.204", features = ["derive"] }
serde_with = "3.9.0"
actix-web = { version = "4.9.0", default-features = false, features = [
"macros",
"rustls",
"rustls-0_23",
"compress-brotli",
"compress-gzip",
"compress-zstd",
@ -116,56 +108,55 @@ actix-web = { version = "4.5.1", default-features = false, features = [
] }
tracing = "0.1.40"
tracing-actix-web = { version = "0.7.10", default-features = false }
tracing-error = "0.2.0"
tracing-log = "0.2.0"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
url = { version = "2.5.0", features = ["serde"] }
reqwest = { version = "0.11.26", features = ["json", "blocking", "gzip"] }
reqwest-middleware = "0.2.4"
reqwest-tracing = "0.4.7"
url = { version = "2.5.2", features = ["serde"] }
reqwest = { version = "0.12.7", default-features = false, features = [
"json",
"blocking",
"gzip",
"rustls-tls",
] }
reqwest-middleware = "0.3.3"
reqwest-tracing = "0.5.3"
clokwerk = "0.4.0"
doku = { version = "0.21.1", features = ["url-2"] }
bcrypt = "0.15.0"
chrono = { version = "0.4.35", features = ["serde"], default-features = false }
serde_json = { version = "1.0.114", features = ["preserve_order"] }
base64 = "0.21.7"
uuid = { version = "1.7.0", features = ["serde", "v4"] }
async-trait = "0.1.77"
bcrypt = "0.15.1"
chrono = { version = "0.4.38", features = ["serde"], default-features = false }
serde_json = { version = "1.0.121", features = ["preserve_order"] }
base64 = "0.22.1"
uuid = { version = "1.10.0", features = ["serde", "v4"] }
async-trait = "0.1.81"
captcha = "0.0.9"
anyhow = { version = "1.0.81", features = [
anyhow = { version = "1.0.86", features = [
"backtrace",
] } # backtrace is on by default on nightly, but not stable rust
diesel_ltree = "0.3.1"
typed-builder = "0.18.1"
serial_test = "2.0.0"
tokio = { version = "1.36.0", features = ["full"] }
regex = "1.10.3"
once_cell = "1.19.0"
diesel-derive-newtype = "2.1.0"
serial_test = "3.1.1"
tokio = { version = "1.39.2", features = ["full"] }
regex = "1.10.5"
diesel-derive-newtype = "2.1.2"
diesel-derive-enum = { version = "2.1.0", features = ["postgres"] }
strum = "0.25.0"
strum_macros = "0.25.3"
itertools = "0.12.1"
strum = { version = "0.26.3", features = ["derive"] }
itertools = "0.13.0"
futures = "0.3.30"
http = "0.2.12"
http = "1.1"
rosetta-i18n = "0.1.3"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
tracing-opentelemetry = { version = "0.19.0" }
ts-rs = { version = "7.1.1", features = [
"serde-compat",
"chrono-impl",
"no-serde-warnings",
] }
rustls = { version = "0.21.10", features = ["dangerous_configuration"] }
rustls = { version = "0.23.12", features = ["ring"] }
futures-util = "0.3.30"
tokio-postgres = "0.7.10"
tokio-postgres-rustls = "0.10.0"
tokio-postgres = "0.7.11"
tokio-postgres-rustls = "0.12.0"
urlencoding = "2.1.3"
enum-map = "2.7"
moka = { version = "0.12.5", features = ["future"] }
moka = { version = "0.12.8", features = ["future"] }
i-love-jesus = { version = "0.1.0" }
clap = { version = "4.5.2", features = ["derive"] }
clap = { version = "4.5.13", features = ["derive", "env"] }
pretty_assertions = "1.4.0"
derive-new = "0.7.0"
[dependencies]
lemmy_api = { workspace = true }
@ -175,15 +166,13 @@ lemmy_utils = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_api_common = { workspace = true }
lemmy_routes = { workspace = true }
lemmy_federate = { version = "0.19.4-beta.1c", path = "crates/federate" }
lemmy_federate = { workspace = true }
activitypub_federation = { workspace = true }
diesel = { workspace = true }
diesel-async = { workspace = true }
actix-web = { workspace = true }
tracing = { workspace = true }
tracing-actix-web = { workspace = true }
tracing-error = { workspace = true }
tracing-log = { workspace = true }
tracing-subscriber = { workspace = true }
url = { workspace = true }
reqwest = { workspace = true }
@ -191,19 +180,15 @@ reqwest-middleware = { workspace = true }
reqwest-tracing = { workspace = true }
clokwerk = { workspace = true }
serde_json = { workspace = true }
tracing-opentelemetry = { workspace = true, optional = true }
opentelemetry = { workspace = true, optional = true }
console-subscriber = { version = "0.1.10", optional = true }
opentelemetry-otlp = { version = "0.12.0", optional = true }
pict-rs = { version = "0.5.9", optional = true }
rustls = { workspace = true }
tokio.workspace = true
actix-cors = "0.6.5"
actix-cors = "0.7.0"
futures-util = { workspace = true }
chrono = { workspace = true }
prometheus = { version = "0.13.3", features = ["process"] }
prometheus = { version = "0.13.4", features = ["process"] }
serial_test = { workspace = true }
clap = { workspace = true }
actix-web-prom = "0.7.0"
actix-web-prom = "0.8.0"
[dev-dependencies]
pretty_assertions = { workspace = true }

View file

@ -1,42 +0,0 @@
{
"root": true,
"env": {
"browser": true
},
"plugins": ["@typescript-eslint"],
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"warnOnUnsupportedTypeScriptVersion": false
},
"rules": {
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-var-requires": 0,
"arrow-body-style": 0,
"curly": 0,
"eol-last": 0,
"eqeqeq": 0,
"func-style": 0,
"import/no-duplicates": 0,
"max-statements": 0,
"max-params": 0,
"new-cap": 0,
"no-console": 0,
"no-duplicate-imports": 0,
"no-extra-parens": 0,
"no-return-assign": 0,
"no-throw-literal": 0,
"no-trailing-spaces": 0,
"no-unused-expressions": 0,
"no-useless-constructor": 0,
"no-useless-escape": 0,
"no-var": 0,
"prefer-const": 0,
"prefer-rest-params": 0,
"quote-props": 0,
"unicorn/filename-case": 0
}
}

1
api_tests/.npmrc Normal file
View file

@ -0,0 +1 @@
package-manager-strict=false

View file

@ -0,0 +1,56 @@
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
export default [
pluginJs.configs.recommended,
...tseslint.configs.recommended,
{
languageOptions: {
parser: tseslint.parser,
},
},
// For some reason this has to be in its own block
{
ignores: [
"putTypesInIndex.js",
"dist/*",
"docs/*",
".yalc",
"jest.config.js",
],
},
{
files: ["src/**/*"],
rules: {
"@typescript-eslint/no-empty-interface": 0,
"@typescript-eslint/no-empty-function": 0,
"@typescript-eslint/ban-ts-comment": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/explicit-module-boundary-types": 0,
"@typescript-eslint/no-var-requires": 0,
"arrow-body-style": 0,
curly: 0,
"eol-last": 0,
eqeqeq: 0,
"func-style": 0,
"import/no-duplicates": 0,
"max-statements": 0,
"max-params": 0,
"new-cap": 0,
"no-console": 0,
"no-duplicate-imports": 0,
"no-extra-parens": 0,
"no-return-assign": 0,
"no-throw-literal": 0,
"no-trailing-spaces": 0,
"no-unused-expressions": 0,
"no-useless-constructor": 0,
"no-useless-escape": 0,
"no-var": 0,
"prefer-const": 0,
"prefer-rest-params": 0,
"quote-props": 0,
"unicorn/filename-case": 0,
},
},
];

View file

@ -6,10 +6,11 @@
"repository": "https://github.com/LemmyNet/lemmy",
"author": "Dessalines",
"license": "AGPL-3.0",
"packageManager": "pnpm@9.9.0",
"scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
"lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'",
"fix": "prettier --write src && eslint --fix src",
"api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts && jest -i image.spec.ts",
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
"api-test-follow": "jest -i follow.spec.ts",
"api-test-comment": "jest -i comment.spec.ts",
"api-test-post": "jest -i post.spec.ts",
@ -20,16 +21,16 @@
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.11.27",
"@typescript-eslint/eslint-plugin": "^7.2.0",
"@typescript-eslint/parser": "^7.2.0",
"download-file-sync": "^1.0.4",
"eslint": "^8.57.0",
"@types/node": "^22.3.0",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"eslint": "^9.9.0",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.5.0",
"lemmy-js-client": "0.19.4-alpha.13",
"lemmy-js-client": "0.20.0-alpha.11",
"prettier": "^3.2.5",
"ts-jest": "^29.1.0",
"typescript": "^5.4.2"
"typescript": "^5.5.4",
"typescript-eslint": "^8.1.0"
}
}

File diff suppressed because it is too large Load diff

View file

@ -3,19 +3,19 @@
# it is expected that this script is called by run-federation-test.sh script.
set -e
if [ -n "$LEMMY_LOG_LEVEL" ];
if [ -z "$LEMMY_LOG_LEVEL" ];
then
LEMMY_LOG_LEVEL=warn
LEMMY_LOG_LEVEL=info
fi
export RUST_BACKTRACE=1
#export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
export RUST_LOG="warn,lemmy_server=$LEMMY_LOG_LEVEL,lemmy_federate=$LEMMY_LOG_LEVEL,lemmy_api=$LEMMY_LOG_LEVEL,lemmy_api_common=$LEMMY_LOG_LEVEL,lemmy_api_crud=$LEMMY_LOG_LEVEL,lemmy_apub=$LEMMY_LOG_LEVEL,lemmy_db_schema=$LEMMY_LOG_LEVEL,lemmy_db_views=$LEMMY_LOG_LEVEL,lemmy_db_views_actor=$LEMMY_LOG_LEVEL,lemmy_db_views_moderator=$LEMMY_LOG_LEVEL,lemmy_routes=$LEMMY_LOG_LEVEL,lemmy_utils=$LEMMY_LOG_LEVEL,lemmy_websocket=$LEMMY_LOG_LEVEL"
export LEMMY_TEST_FAST_FEDERATION=1 # by default, the persistent federation queue has delays in the scale of 30s-5min
# pictrs setup
if [ ! -f "api_tests/pict-rs" ]; then
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.0-beta.2/pict-rs-linux-amd64" -o api_tests/pict-rs
curl "https://git.asonix.dog/asonix/pict-rs/releases/download/v0.5.16/pict-rs-linux-amd64" -o api_tests/pict-rs
chmod +x api_tests/pict-rs
fi
./api_tests/pict-rs \

View file

@ -37,15 +37,15 @@ import {
followCommunity,
blockCommunity,
delay,
saveUserSettings,
} from "./shared";
import { CommentView, CommunityView } from "lemmy-js-client";
import { CommentView, CommunityView, SaveUserSettings } from "lemmy-js-client";
let betaCommunity: CommunityView | undefined;
let postOnAlphaRes: PostResponse;
beforeAll(async () => {
await setupLogins();
await unfollows();
await Promise.all([followBeta(alpha), followBeta(gamma)]);
betaCommunity = (await resolveBetaCommunity(alpha)).community;
if (betaCommunity) {
@ -53,9 +53,7 @@ beforeAll(async () => {
}
});
afterAll(() => {
unfollows();
});
afterAll(unfollows);
function assertCommentFederation(
commentOne?: CommentView,
@ -94,7 +92,7 @@ test("Create a comment", async () => {
test("Create a comment in a non-existent post", async () => {
await expect(createComment(alpha, -1)).rejects.toStrictEqual(
Error("couldnt_find_post"),
Error("not_found"),
);
});
@ -145,7 +143,7 @@ test("Delete a comment", async () => {
await waitUntil(
() =>
resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
r => r.message !== "couldnt_find_object",
r => r.message !== "not_found",
)
).comment;
if (!gammaComment) {
@ -163,13 +161,13 @@ test("Delete a comment", async () => {
// Make sure that comment is undefined on beta
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
e => e.message == "couldnt_find_object",
e => e.message == "not_found",
);
// Make sure that comment is undefined on gamma after delete
await waitUntil(
() => resolveComment(gamma, commentRes.comment_view.comment).catch(e => e),
e => e.message === "couldnt_find_object",
e => e.message === "not_found",
);
// Test undeleting the comment
@ -184,7 +182,7 @@ test("Delete a comment", async () => {
let betaComment2 = (
await waitUntil(
() => resolveComment(beta, commentRes.comment_view.comment).catch(e => e),
e => e.message !== "couldnt_find_object",
e => e.message !== "not_found",
)
).comment;
expect(betaComment2?.comment.deleted).toBe(false);
@ -446,6 +444,59 @@ test("Reply to a comment from another instance, get notification", async () => {
assertCommentFederation(alphaReply, replyRes.comment_view);
});
test("Bot reply notifications are filtered when bots are hidden", async () => {
const newAlphaBot = await registerUser(alpha, alphaUrl);
let form: SaveUserSettings = {
bot_account: true,
};
await saveUserSettings(newAlphaBot, form);
const alphaCommunity = (
await resolveCommunity(alpha, "!main@lemmy-alpha:8541")
).community;
if (!alphaCommunity) {
throw "Missing alpha community";
}
await alpha.markAllAsRead();
form = {
show_bot_accounts: false,
};
await saveUserSettings(alpha, form);
const postOnAlphaRes = await createPost(alpha, alphaCommunity.community.id);
// Bot reply to alpha's post
let commentRes = await createComment(
newAlphaBot,
postOnAlphaRes.post_view.post.id,
);
expect(commentRes).toBeDefined();
let alphaUnreadCountRes = await getUnreadCount(alpha);
expect(alphaUnreadCountRes.replies).toBe(0);
let alphaUnreadRepliesRes = await getReplies(alpha, true);
expect(alphaUnreadRepliesRes.replies.length).toBe(0);
// This both restores the original state that may be expected by other tests
// implicitly and is used by the next steps to ensure replies are still
// returned when a user later decides to show bot accounts again.
form = {
show_bot_accounts: true,
};
await saveUserSettings(alpha, form);
alphaUnreadCountRes = await getUnreadCount(alpha);
expect(alphaUnreadCountRes.replies).toBe(1);
alphaUnreadRepliesRes = await getReplies(alpha, true);
expect(alphaUnreadRepliesRes.replies.length).toBe(1);
expect(alphaUnreadRepliesRes.replies[0].comment.id).toBe(
commentRes.comment_view.comment.id,
);
});
test("Mention beta from alpha", async () => {
if (!betaCommunity) throw Error("no community");
const postOnAlphaRes = await createPost(alpha, betaCommunity.community.id);

View file

@ -1,5 +1,6 @@
jest.setTimeout(120000);
import { AddModToCommunity } from "lemmy-js-client/dist/types/AddModToCommunity";
import { CommunityView } from "lemmy-js-client/dist/types/CommunityView";
import {
alpha,
@ -9,6 +10,7 @@ import {
resolveCommunity,
createCommunity,
deleteCommunity,
delay,
removeCommunity,
getCommunity,
followCommunity,
@ -31,10 +33,12 @@ import {
searchPostLocal,
longDelay,
editCommunity,
unfollows,
} from "./shared";
import { EditCommunity, EditSite } from "lemmy-js-client";
beforeAll(setupLogins);
afterAll(unfollows);
function assertCommunityFederation(
communityOne?: CommunityView,
@ -240,7 +244,7 @@ test("Admin actions in remote community are not federated to origin", async () =
);
expect(banRes.banned).toBe(true);
// ban doesnt federate to community's origin instance alpha
// ban doesn't federate to community's origin instance alpha
let alphaPost = (await resolvePost(alpha, gammaPost.post)).post;
expect(alphaPost?.creator_banned_from_community).toBe(false);
@ -378,8 +382,8 @@ test("User blocks instance, communities are hidden", async () => {
test("Community follower count is federated", async () => {
// Follow the beta community from alpha
let community = await createCommunity(beta);
let community_id = community.community_view.community.actor_id;
let resolved = await resolveCommunity(alpha, community_id);
let communityActorId = community.community_view.community.actor_id;
let resolved = await resolveCommunity(alpha, communityActorId);
if (!resolved.community) {
throw "Missing beta community";
}
@ -387,7 +391,7 @@ test("Community follower count is federated", async () => {
await followCommunity(alpha, true, resolved.community.community.id);
let followed = (
await waitUntil(
() => resolveCommunity(alpha, community_id),
() => resolveCommunity(alpha, communityActorId),
c => c.community?.subscribed === "Subscribed",
)
).community;
@ -396,7 +400,7 @@ test("Community follower count is federated", async () => {
expect(followed?.counts.subscribers).toBe(1);
// Follow the community from gamma
resolved = await resolveCommunity(gamma, community_id);
resolved = await resolveCommunity(gamma, communityActorId);
if (!resolved.community) {
throw "Missing beta community";
}
@ -404,7 +408,7 @@ test("Community follower count is federated", async () => {
await followCommunity(gamma, true, resolved.community.community.id);
followed = (
await waitUntil(
() => resolveCommunity(gamma, community_id),
() => resolveCommunity(gamma, communityActorId),
c => c.community?.subscribed === "Subscribed",
)
).community;
@ -413,7 +417,7 @@ test("Community follower count is federated", async () => {
expect(followed?.counts?.subscribers).toBe(2);
// Follow the community from delta
resolved = await resolveCommunity(delta, community_id);
resolved = await resolveCommunity(delta, communityActorId);
if (!resolved.community) {
throw "Missing beta community";
}
@ -421,7 +425,7 @@ test("Community follower count is federated", async () => {
await followCommunity(delta, true, resolved.community.community.id);
followed = (
await waitUntil(
() => resolveCommunity(delta, community_id),
() => resolveCommunity(delta, communityActorId),
c => c.community?.subscribed === "Subscribed",
)
).community;
@ -450,7 +454,7 @@ test("Dont receive community activities after unsubscribe", async () => {
);
expect(communityRes1.community_view.counts.subscribers).toBe(2);
// temporarily block alpha, so that it doesnt know about unfollow
// temporarily block alpha, so that it doesn't know about unfollow
let editSiteForm: EditSite = {};
editSiteForm.allowed_instances = ["lemmy-epsilon"];
await beta.editSite(editSiteForm);
@ -511,7 +515,7 @@ test("Fetch community, includes posts", async () => {
expect(post_listing.posts[0].post.ap_id).toBe(postRes.post_view.post.ap_id);
});
test("Content in local-only community doesnt federate", async () => {
test("Content in local-only community doesn't federate", async () => {
// create a community and set it local-only
let communityRes = (await createCommunity(alpha)).community_view.community;
let form: EditCommunity = {
@ -523,11 +527,49 @@ test("Content in local-only community doesnt federate", async () => {
// cant resolve the community from another instance
await expect(
resolveCommunity(beta, communityRes.actor_id),
).rejects.toStrictEqual(Error("couldnt_find_object"));
).rejects.toStrictEqual(Error("not_found"));
// create a post, also cant resolve it
let postRes = await createPost(alpha, communityRes.id);
await expect(resolvePost(beta, postRes.post_view.post)).rejects.toStrictEqual(
Error("couldnt_find_object"),
Error("not_found"),
);
});
test("Remote mods can edit communities", async () => {
let communityRes = await createCommunity(alpha);
let betaCommunity = await resolveCommunity(
beta,
communityRes.community_view.community.actor_id,
);
if (!betaCommunity.community) {
throw "Missing beta community";
}
let betaOnAlpha = await resolvePerson(alpha, "lemmy_beta@lemmy-beta:8551");
let form: AddModToCommunity = {
community_id: communityRes.community_view.community.id,
person_id: betaOnAlpha.person?.person.id as number,
added: true,
};
alpha.addModToCommunity(form);
let form2: EditCommunity = {
community_id: betaCommunity.community?.community.id as number,
description: "Example description",
};
await editCommunity(beta, form2);
// give alpha time to get and process the edit
await delay(1000);
let alphaCommunity = await getCommunity(
alpha,
communityRes.community_view.community.id,
);
await expect(alphaCommunity.community_view.community.description).toBe(
"Example description",
);
});

View file

@ -11,51 +11,59 @@ import {
betaUrl,
registerUser,
unfollows,
delay,
} from "./shared";
beforeAll(setupLogins);
afterAll(() => {
unfollows();
});
afterAll(unfollows);
test("Follow local community", async () => {
let user = await registerUser(beta, betaUrl);
let community = (await resolveBetaCommunity(user)).community!;
expect(community.counts.subscribers).toBe(1);
expect(community.counts.subscribers_local).toBe(1);
let follow = await followCommunity(user, true, community.community.id);
// Make sure the follow response went through
expect(follow.community_view.community.local).toBe(true);
expect(follow.community_view.subscribed).toBe("Subscribed");
expect(follow.community_view.counts.subscribers).toBe(2);
expect(follow.community_view.counts.subscribers_local).toBe(2);
expect(follow.community_view.counts.subscribers).toBe(
community.counts.subscribers + 1,
);
expect(follow.community_view.counts.subscribers_local).toBe(
community.counts.subscribers_local + 1,
);
// Test an unfollow
let unfollow = await followCommunity(user, false, community.community.id);
expect(unfollow.community_view.subscribed).toBe("NotSubscribed");
expect(unfollow.community_view.counts.subscribers).toBe(1);
expect(unfollow.community_view.counts.subscribers_local).toBe(1);
expect(unfollow.community_view.counts.subscribers).toBe(
community.counts.subscribers,
);
expect(unfollow.community_view.counts.subscribers_local).toBe(
community.counts.subscribers_local,
);
});
test("Follow federated community", async () => {
// It takes about 1 second for the community aggregates to federate
let betaCommunity = (
await delay(2000); // if this is the second test run, we don't have a way to wait for the correct number of subscribers
const betaCommunityInitial = (
await waitUntil(
() => resolveBetaCommunity(alpha),
c =>
c.community?.counts.subscribers === 1 &&
c.community.counts.subscribers_local === 0,
c => !!c.community && c.community?.counts.subscribers >= 1,
)
).community;
if (!betaCommunity) {
if (!betaCommunityInitial) {
throw "Missing beta community";
}
let follow = await followCommunity(alpha, true, betaCommunity.community.id);
let follow = await followCommunity(
alpha,
true,
betaCommunityInitial.community.id,
);
expect(follow.community_view.subscribed).toBe("Pending");
betaCommunity = (
const betaCommunity = (
await waitUntil(
() => resolveBetaCommunity(alpha),
c => c.community?.subscribed === "Subscribed",
@ -66,20 +74,24 @@ test("Follow federated community", async () => {
expect(betaCommunity?.community.local).toBe(false);
expect(betaCommunity?.community.name).toBe("main");
expect(betaCommunity?.subscribed).toBe("Subscribed");
expect(betaCommunity?.counts.subscribers_local).toBe(1);
expect(betaCommunity?.counts.subscribers_local).toBe(
betaCommunityInitial.counts.subscribers_local + 1,
);
// check that unfollow was federated
let communityOnBeta1 = await resolveBetaCommunity(beta);
expect(communityOnBeta1.community?.counts.subscribers).toBe(2);
expect(communityOnBeta1.community?.counts.subscribers_local).toBe(1);
expect(communityOnBeta1.community?.counts.subscribers).toBe(
betaCommunityInitial.counts.subscribers + 1,
);
// Check it from local
let site = await getSite(alpha);
let remoteCommunityId = site.my_user?.follows.find(
c => c.community.local == false,
c =>
c.community.local == false &&
c.community.id === betaCommunityInitial.community.id,
)?.community.id;
expect(remoteCommunityId).toBeDefined();
expect(site.my_user?.follows.length).toBe(2);
if (!remoteCommunityId) {
throw "Missing remote community id";
@ -91,10 +103,21 @@ test("Follow federated community", async () => {
// Make sure you are unsubbed locally
let siteUnfollowCheck = await getSite(alpha);
expect(siteUnfollowCheck.my_user?.follows.length).toBe(1);
expect(
siteUnfollowCheck.my_user?.follows.find(
c => c.community.id === betaCommunityInitial.community.id,
),
).toBe(undefined);
// check that unfollow was federated
let communityOnBeta2 = await resolveBetaCommunity(beta);
expect(communityOnBeta2.community?.counts.subscribers).toBe(1);
let communityOnBeta2 = await waitUntil(
() => resolveBetaCommunity(beta),
c =>
c.community?.counts.subscribers ===
betaCommunityInitial.counts.subscribers,
);
expect(communityOnBeta2.community?.counts.subscribers).toBe(
betaCommunityInitial.counts.subscribers,
);
expect(communityOnBeta2.community?.counts.subscribers_local).toBe(1);
});

View file

@ -15,7 +15,6 @@ import {
createCommunity,
createPost,
deleteAllImages,
delta,
epsilon,
followCommunity,
gamma,
@ -28,20 +27,24 @@ import {
setupLogins,
waitForPost,
unfollows,
getPost,
waitUntil,
createPostWithThumbnail,
sampleImage,
sampleSite,
} from "./shared";
const downloadFileSync = require("download-file-sync");
beforeAll(setupLogins);
afterAll(() => {
unfollows();
afterAll(async () => {
await Promise.all([unfollows(), deleteAllImages(alpha)]);
});
test("Upload image and delete it", async () => {
// Before running this test, you need to delete all previous images in the DB
await deleteAllImages(alpha);
// Upload test image. We use a simple string buffer as pictrs doesnt require an actual image
// Upload test image. We use a simple string buffer as pictrs doesn't require an actual image
// in testing mode.
const upload_form: UploadImage = {
image: Buffer.from("test"),
@ -53,7 +56,8 @@ test("Upload image and delete it", async () => {
expect(upload.delete_url).toBeDefined();
// ensure that image download is working. theres probably a better way to do this
const content = downloadFileSync(upload.url);
const response = await fetch(upload.url ?? "");
const content = await response.text();
expect(content.length).toBeGreaterThan(0);
// Ensure that it comes back with the list_media endpoint
@ -71,9 +75,14 @@ test("Upload image and delete it", async () => {
// The deleteUrl is a combination of the endpoint, delete token, and alias
let firstImage = listMediaRes.images[0];
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.pictrs_delete_token}/${firstImage.pictrs_alias}`;
let deleteUrl = `${alphaUrl}/pictrs/image/delete/${firstImage.local_image.pictrs_delete_token}/${firstImage.local_image.pictrs_alias}`;
expect(deleteUrl).toBe(upload.delete_url);
// Make sure the uploader is correct
expect(firstImage.person.actor_id).toBe(
`http://lemmy-alpha:8541/u/lemmy_alpha`,
);
// delete image
const delete_form: DeleteImage = {
token: upload.files![0].delete_token,
@ -83,7 +92,8 @@ test("Upload image and delete it", async () => {
expect(delete_).toBe(true);
// ensure that image is deleted
const content2 = downloadFileSync(upload.url);
const response2 = await fetch(upload.url ?? "");
const content2 = await response2.text();
expect(content2).toBe("");
// Ensure that it shows the image is deleted
@ -111,7 +121,8 @@ test("Purge user, uploaded image removed", async () => {
expect(upload.delete_url).toBeDefined();
// ensure that image download is working. theres probably a better way to do this
const content = downloadFileSync(upload.url);
const response = await fetch(upload.url ?? "");
const content = await response.text();
expect(content.length).toBeGreaterThan(0);
// purge user
@ -123,7 +134,8 @@ test("Purge user, uploaded image removed", async () => {
expect(delete_.success).toBe(true);
// ensure that image is deleted
const content2 = downloadFileSync(upload.url);
const response2 = await fetch(upload.url ?? "");
const content2 = await response2.text();
expect(content2).toBe("");
});
@ -141,7 +153,8 @@ test("Purge post, linked image removed", async () => {
expect(upload.delete_url).toBeDefined();
// ensure that image download is working. theres probably a better way to do this
const content = downloadFileSync(upload.url);
const response = await fetch(upload.url ?? "");
const content = await response.text();
expect(content.length).toBeGreaterThan(0);
let community = await resolveBetaCommunity(user);
@ -151,9 +164,9 @@ test("Purge post, linked image removed", async () => {
upload.url,
);
expect(post.post_view.post.url).toBe(upload.url);
expect(post.post_view.image_details).toBeDefined();
// purge post
const purgeForm: PurgePost = {
post_id: post.post_view.post.id,
};
@ -161,52 +174,102 @@ test("Purge post, linked image removed", async () => {
expect(delete_.success).toBe(true);
// ensure that image is deleted
const content2 = downloadFileSync(upload.url);
const response2 = await fetch(upload.url ?? "");
const content2 = await response2.text();
expect(content2).toBe("");
});
test("Images in remote post are proxied if setting enabled", async () => {
let user = await registerUser(beta, betaUrl);
test("Images in remote image post are proxied if setting enabled", async () => {
let community = await createCommunity(gamma);
const upload_form: UploadImage = {
image: Buffer.from("test"),
};
const upload = await user.uploadImage(upload_form);
let post = await createPost(
let postRes = await createPost(
gamma,
community.community_view.community.id,
upload.url,
"![](http://example.com/image2.png)",
sampleImage,
`![](${sampleImage})`,
);
expect(post.post_view.post).toBeDefined();
const post = postRes.post_view.post;
expect(post).toBeDefined();
// Make sure it fetched the image details
expect(postRes.post_view.image_details).toBeDefined();
// remote image gets proxied after upload
expect(
post.post_view.post.url?.startsWith(
post.thumbnail_url?.startsWith(
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
),
).toBeTruthy();
expect(
post.post_view.post.body?.startsWith(
"![](http://lemmy-gamma:8561/api/v3/image_proxy?url",
),
post.body?.startsWith("![](http://lemmy-gamma:8561/api/v3/image_proxy?url"),
).toBeTruthy();
let epsilonPost = await resolvePost(epsilon, post.post_view.post);
expect(epsilonPost.post).toBeDefined();
// Make sure that it ends with jpg, to be sure its an image
expect(post.thumbnail_url?.endsWith(".jpg")).toBeTruthy();
let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post);
expect(epsilonPostRes.post).toBeDefined();
// Fetch the post again, the metadata should be backgrounded now
// Wait for the metadata to get fetched, since this is backgrounded now
let epsilonPostRes2 = await waitUntil(
() => getPost(epsilon, epsilonPostRes.post!.post.id),
p => p.post_view.post.thumbnail_url != undefined,
);
const epsilonPost = epsilonPostRes2.post_view.post;
// remote image gets proxied after federation
expect(
epsilonPost.post!.post.url?.startsWith(
epsilonPost.thumbnail_url?.startsWith(
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
),
).toBeTruthy();
expect(
epsilonPost.post!.post.body?.startsWith(
epsilonPost.body?.startsWith(
"![](http://lemmy-epsilon:8581/api/v3/image_proxy?url",
),
).toBeTruthy();
// Make sure that it ends with jpg, to be sure its an image
expect(epsilonPost.thumbnail_url?.endsWith(".jpg")).toBeTruthy();
});
test("Thumbnail of remote image link is proxied if setting enabled", async () => {
let community = await createCommunity(gamma);
let postRes = await createPost(
gamma,
community.community_view.community.id,
// The sample site metadata thumbnail ends in png
sampleSite,
);
const post = postRes.post_view.post;
expect(post).toBeDefined();
// remote image gets proxied after upload
expect(
post.thumbnail_url?.startsWith(
"http://lemmy-gamma:8561/api/v3/image_proxy?url",
),
).toBeTruthy();
// Make sure that it ends with png, to be sure its an image
expect(post.thumbnail_url?.endsWith(".png")).toBeTruthy();
let epsilonPostRes = await resolvePost(epsilon, postRes.post_view.post);
expect(epsilonPostRes.post).toBeDefined();
let epsilonPostRes2 = await waitUntil(
() => getPost(epsilon, epsilonPostRes.post!.post.id),
p => p.post_view.post.thumbnail_url != undefined,
);
const epsilonPost = epsilonPostRes2.post_view.post;
expect(
epsilonPost.thumbnail_url?.startsWith(
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
),
).toBeTruthy();
// Make sure that it ends with png, to be sure its an image
expect(epsilonPost.thumbnail_url?.endsWith(".png")).toBeTruthy();
});
test("No image proxying if setting is disabled", async () => {
@ -226,15 +289,15 @@ test("No image proxying if setting is disabled", async () => {
alpha,
community.community_view.community.id,
upload.url,
"![](http://example.com/image2.png)",
`![](${sampleImage})`,
);
expect(post.post_view.post).toBeDefined();
// remote image doesnt get proxied after upload
// remote image doesn't get proxied after upload
expect(
post.post_view.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
).toBeTruthy();
expect(post.post_view.post.body).toBe("![](http://example.com/image2.png)");
expect(post.post_view.post.body).toBe(`![](${sampleImage})`);
let betaPost = await waitForPost(
beta,
@ -243,12 +306,67 @@ test("No image proxying if setting is disabled", async () => {
);
expect(betaPost.post).toBeDefined();
// remote image doesnt get proxied after federation
// remote image doesn't get proxied after federation
expect(
betaPost.post.url?.startsWith("http://127.0.0.1:8551/pictrs/image/"),
).toBeTruthy();
expect(betaPost.post.body).toBe("![](http://example.com/image2.png)");
expect(betaPost.post.body).toBe(`![](${sampleImage})`);
// Make sure the alt text got federated
expect(post.post_view.post.alt_text).toBe(betaPost.post.alt_text);
});
test("Make regular post, and give it a custom thumbnail", async () => {
const uploadForm1: UploadImage = {
image: Buffer.from("testRegular1"),
};
const upload1 = await alphaImage.uploadImage(uploadForm1);
const community = await createCommunity(alphaImage);
// Use wikipedia since it has an opengraph image
const wikipediaUrl = "https://wikipedia.org/";
let post = await createPostWithThumbnail(
alphaImage,
community.community_view.community.id,
wikipediaUrl,
upload1.url!,
);
// Wait for the metadata to get fetched, since this is backgrounded now
post = await waitUntil(
() => getPost(alphaImage, post.post_view.post.id),
p => p.post_view.post.thumbnail_url != undefined,
);
expect(post.post_view.post.url).toBe(wikipediaUrl);
// Make sure it uses custom thumbnail
expect(post.post_view.post.thumbnail_url).toBe(upload1.url);
});
test("Create an image post, and make sure a custom thumbnail doesn't overwrite it", async () => {
const uploadForm1: UploadImage = {
image: Buffer.from("test1"),
};
const upload1 = await alphaImage.uploadImage(uploadForm1);
const uploadForm2: UploadImage = {
image: Buffer.from("test2"),
};
const upload2 = await alphaImage.uploadImage(uploadForm2);
const community = await createCommunity(alphaImage);
let post = await createPostWithThumbnail(
alphaImage,
community.community_view.community.id,
upload1.url!,
upload2.url!,
);
post = await waitUntil(
() => getPost(alphaImage, post.post_view.post.id),
p => p.post_view.post.thumbnail_url != undefined,
);
expect(post.post_view.post.url).toBe(upload1.url);
// Make sure the custom thumbnail is ignored
expect(post.post_view.post.thumbnail_url == upload2.url).toBe(false);
});

View file

@ -48,24 +48,27 @@ beforeAll(async () => {
await setupLogins();
betaCommunity = (await resolveBetaCommunity(alpha)).community;
expect(betaCommunity).toBeDefined();
await unfollows();
});
afterAll(() => {
unfollows();
});
afterAll(unfollows);
async function assertPostFederation(postOne: PostView, postTwo: PostView) {
async function assertPostFederation(
postOne: PostView,
postTwo: PostView,
waitForMeta = true,
) {
// Link metadata is generated in background task and may not be ready yet at this time,
// so wait for it explicitly. For removed posts we cant refetch anything.
postOne = await waitForPost(beta, postOne.post, res => {
return res === null || res?.post.embed_title !== null;
});
postTwo = await waitForPost(
beta,
postTwo.post,
res => res === null || res?.post.embed_title !== null,
);
if (waitForMeta) {
postOne = await waitForPost(beta, postOne.post, res => {
return res === null || !!res?.post.embed_title;
});
postTwo = await waitForPost(
beta,
postTwo.post,
res => res === null || !!res?.post.embed_title,
);
}
expect(postOne?.post.ap_id).toBe(postTwo?.post.ap_id);
expect(postOne?.post.name).toBe(postTwo?.post.name);
@ -85,10 +88,7 @@ async function assertPostFederation(postOne: PostView, postTwo: PostView) {
test("Create a post", async () => {
// Setup some allowlists and blocklists
let editSiteForm: EditSite = {
allowed_instances: ["lemmy-beta"],
};
await delta.editSite(editSiteForm);
const editSiteForm: EditSite = {};
editSiteForm.allowed_instances = [];
editSiteForm.blocked_instances = ["lemmy-alpha"];
@ -125,12 +125,12 @@ test("Create a post", async () => {
// Delta only follows beta, so it should not see an alpha ap_id
await expect(
resolvePost(delta, postRes.post_view.post),
).rejects.toStrictEqual(Error("couldnt_find_object"));
).rejects.toStrictEqual(Error("not_found"));
// Epsilon has alpha blocked, it should not see the alpha post
await expect(
resolvePost(epsilon, postRes.post_view.post),
).rejects.toStrictEqual(Error("couldnt_find_object"));
).rejects.toStrictEqual(Error("not_found"));
// remove added allow/blocklists
editSiteForm.allowed_instances = [];
@ -140,9 +140,7 @@ test("Create a post", async () => {
});
test("Create a post in a non-existent community", async () => {
await expect(createPost(alpha, -2)).rejects.toStrictEqual(
Error("couldnt_find_community"),
);
await expect(createPost(alpha, -2)).rejects.toStrictEqual(Error("not_found"));
});
test("Unlike a post", async () => {
@ -414,7 +412,11 @@ test("Remove a post from admin and community on same instance", async () => {
p => p?.post_view.post.removed ?? false,
);
expect(alphaPost?.post_view.post.removed).toBe(true);
await assertPostFederation(alphaPost.post_view, removePostRes.post_view);
await assertPostFederation(
alphaPost.post_view,
removePostRes.post_view,
false,
);
// Undelete
let undeletedPost = await removePost(beta, false, betaPost.post);
@ -498,17 +500,24 @@ test("Enforce site ban federation for local user", async () => {
alpha,
alphaPerson.person.id,
false,
false,
true,
);
expect(unBanAlpha.banned).toBe(false);
// existing alpha post should be restored on beta
betaBanRes = await waitUntil(
() => getPost(beta, searchBeta1.post.id),
s => !s.post_view.post.removed,
);
expect(betaBanRes.post_view.post.removed).toBe(false);
// Login gets invalidated by ban, need to login again
if (!alphaUserPerson) {
throw "Missing alpha person";
}
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
alphaUserHttp.setHeaders({
Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "",
Authorization: "Bearer " + newAlphaUserJwt.jwt,
});
// alpha makes new post in beta community, it federates
let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id);
@ -619,7 +628,7 @@ test("Enforce community ban for federated user", async () => {
// Alpha tries to make post on beta, but it fails because of ban
await expect(
createPost(alpha, betaCommunity.community.id),
).rejects.toStrictEqual(Error("banned_from_community"));
).rejects.toStrictEqual(Error("person_is_banned_from_community"));
// Unban alpha
let unBanAlpha = await banPersonFromCommunity(
@ -663,40 +672,60 @@ test("A and G subscribe to B (center) A posts, it gets announced to G", async ()
});
test("Report a post", async () => {
// Note, this is a different one from the setup
let betaCommunity = (await resolveBetaCommunity(beta)).community;
if (!betaCommunity) {
throw "Missing beta community";
}
// Create post from alpha
let alphaCommunity = (await resolveBetaCommunity(alpha)).community!;
await followBeta(alpha);
let postRes = await createPost(beta, betaCommunity.community.id);
let postRes = await createPost(alpha, alphaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
let alphaPost = (await resolvePost(alpha, postRes.post_view.post)).post;
if (!alphaPost) {
throw "Missing alpha post";
}
let alphaReport = (
await reportPost(alpha, alphaPost.post.id, randomString(10))
).post_report_view.post_report;
// Send report from gamma
let gammaPost = (await resolvePost(gamma, alphaPost.post)).post!;
let gammaReport = (
await reportPost(gamma, gammaPost.post.id, randomString(10))
).post_report_view.post_report;
expect(gammaReport).toBeDefined();
// Report was federated to community instance
let betaReport = (await waitUntil(
() =>
listPostReports(beta).then(p =>
p.post_reports.find(
r =>
r.post_report.original_post_name === alphaReport.original_post_name,
r.post_report.original_post_name === gammaReport.original_post_name,
),
),
res => !!res,
))!.post_report;
expect(betaReport).toBeDefined();
expect(betaReport.resolved).toBe(false);
expect(betaReport.original_post_name).toBe(alphaReport.original_post_name);
expect(betaReport.original_post_url).toBe(alphaReport.original_post_url);
expect(betaReport.original_post_body).toBe(alphaReport.original_post_body);
expect(betaReport.reason).toBe(alphaReport.reason);
expect(betaReport.original_post_name).toBe(gammaReport.original_post_name);
//expect(betaReport.original_post_url).toBe(gammaReport.original_post_url);
expect(betaReport.original_post_body).toBe(gammaReport.original_post_body);
expect(betaReport.reason).toBe(gammaReport.reason);
await unfollowRemotes(alpha);
// Report was federated to poster's instance
let alphaReport = (await waitUntil(
() =>
listPostReports(alpha).then(p =>
p.post_reports.find(
r =>
r.post_report.original_post_name === gammaReport.original_post_name,
),
),
res => !!res,
))!.post_report;
expect(alphaReport).toBeDefined();
expect(alphaReport.resolved).toBe(false);
expect(alphaReport.original_post_name).toBe(gammaReport.original_post_name);
//expect(alphaReport.original_post_url).toBe(gammaReport.original_post_url);
expect(alphaReport.original_post_body).toBe(gammaReport.original_post_body);
expect(alphaReport.reason).toBe(gammaReport.reason);
});
test("Fetch post via redirect", async () => {
@ -731,7 +760,7 @@ test("Block post that contains banned URL", async () => {
await epsilon.editSite(editSiteForm);
await delay(500);
await delay();
if (!betaCommunity) {
throw "Missing beta community";
@ -745,3 +774,23 @@ test("Block post that contains banned URL", async () => {
editSiteForm.blocked_urls = [];
await epsilon.editSite(editSiteForm);
});
test("Fetch post with redirect", async () => {
let alphaPost = await createPost(alpha, betaCommunity!.community.id);
expect(alphaPost.post_view.post).toBeDefined();
// beta fetches from alpha as usual
let betaPost = await resolvePost(beta, alphaPost.post_view.post);
expect(betaPost.post).toBeDefined();
// gamma fetches from beta, and gets redirected to alpha
let gammaPost = await resolvePost(gamma, betaPost.post!.post);
expect(gammaPost.post).toBeDefined();
// fetch remote object from local url, which redirects to the original url
let form: ResolveObject = {
q: `http://lemmy-gamma:8561/post/${gammaPost.post!.post.id}`,
};
let gammaPost2 = await gamma.resolveObject(form);
expect(gammaPost2.post).toBeDefined();
});

View file

@ -21,9 +21,7 @@ beforeAll(async () => {
recipient_id = 3;
});
afterAll(() => {
unfollows();
});
afterAll(unfollows);
test("Create a private message", async () => {
let pmRes = await createPrivateMessage(alpha, recipient_id);

View file

@ -81,21 +81,24 @@ import { ListingType } from "lemmy-js-client/dist/types/ListingType";
export const fetchFunction = fetch;
export const imageFetchLimit = 50;
export const sampleImage =
"https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg";
export const sampleSite = "https://yahoo.com";
export let alphaUrl = "http://127.0.0.1:8541";
export let betaUrl = "http://127.0.0.1:8551";
export let gammaUrl = "http://127.0.0.1:8561";
export let deltaUrl = "http://127.0.0.1:8571";
export let epsilonUrl = "http://127.0.0.1:8581";
export const alphaUrl = "http://127.0.0.1:8541";
export const betaUrl = "http://127.0.0.1:8551";
export const gammaUrl = "http://127.0.0.1:8561";
export const deltaUrl = "http://127.0.0.1:8571";
export const epsilonUrl = "http://127.0.0.1:8581";
export let alpha = new LemmyHttp(alphaUrl, { fetchFunction });
export let alphaImage = new LemmyHttp(alphaUrl);
export let beta = new LemmyHttp(betaUrl, { fetchFunction });
export let gamma = new LemmyHttp(gammaUrl, { fetchFunction });
export let delta = new LemmyHttp(deltaUrl, { fetchFunction });
export let epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
export const alpha = new LemmyHttp(alphaUrl, { fetchFunction });
export const alphaImage = new LemmyHttp(alphaUrl);
export const beta = new LemmyHttp(betaUrl, { fetchFunction });
export const gamma = new LemmyHttp(gammaUrl, { fetchFunction });
export const delta = new LemmyHttp(deltaUrl, { fetchFunction });
export const epsilon = new LemmyHttp(epsilonUrl, { fetchFunction });
export let betaAllowedInstances = [
export const betaAllowedInstances = [
"lemmy-alpha",
"lemmy-gamma",
"lemmy-delta",
@ -180,6 +183,10 @@ export async function setupLogins() {
];
await gamma.editSite(editSiteForm);
// Setup delta allowed instance
editSiteForm.allowed_instances = ["lemmy-beta"];
await delta.editSite(editSiteForm);
// Create the main alpha/beta communities
// Ignore thrown errors of duplicates
try {
@ -190,7 +197,7 @@ export async function setupLogins() {
// (because last_successful_id is set to current id when federation to an instance is first started)
// only needed the first time so do in this try
await delay(10_000);
} catch (_) {
} catch {
console.log("Communities already exist");
}
}
@ -203,6 +210,7 @@ export async function createPost(
// use example.com for consistent title and embed description
name: string = randomString(5),
alt_text = randomString(10),
custom_thumbnail: string | undefined = undefined,
): Promise<PostResponse> {
let form: CreatePost = {
name,
@ -210,6 +218,7 @@ export async function createPost(
body,
alt_text,
community_id,
custom_thumbnail,
};
return api.createPost(form);
}
@ -226,6 +235,21 @@ export async function editPost(
return api.editPost(form);
}
export async function createPostWithThumbnail(
api: LemmyHttp,
community_id: number,
url: string,
custom_thumbnail: string,
): Promise<PostResponse> {
let form: CreatePost = {
name: randomString(10),
url,
community_id,
custom_thumbnail,
};
return api.createPost(form);
}
export async function deletePost(
api: LemmyHttp,
deleted: boolean,
@ -340,10 +364,13 @@ export async function getUnreadCount(
return api.getUnreadCount();
}
export async function getReplies(api: LemmyHttp): Promise<GetRepliesResponse> {
export async function getReplies(
api: LemmyHttp,
unread_only: boolean = false,
): Promise<GetRepliesResponse> {
let form: GetReplies = {
sort: "New",
unread_only: false,
unread_only,
};
return api.getReplies(form);
}
@ -392,13 +419,13 @@ export async function banPersonFromSite(
api: LemmyHttp,
person_id: number,
ban: boolean,
remove_data: boolean,
remove_or_restore_data: boolean,
): Promise<BanPersonResponse> {
// Make sure lemmy-beta/c/main is cached on lemmy_alpha
let form: BanPerson = {
person_id,
ban,
remove_data,
remove_or_restore_data,
};
return api.banPerson(form);
}
@ -407,13 +434,13 @@ export async function banPersonFromCommunity(
api: LemmyHttp,
person_id: number,
community_id: number,
remove_data: boolean,
remove_or_restore_data: boolean,
ban: boolean,
): Promise<BanFromCommunityResponse> {
let form: BanFromCommunity = {
person_id,
community_id,
remove_data: remove_data,
remove_or_restore_data,
ban,
};
return api.banFromCommunity(form);
@ -663,7 +690,7 @@ export async function saveUserSettingsBio(
blur_nsfw: false,
auto_expand: true,
theme: "darkly",
default_sort_type: "Active",
default_post_sort_type: "Active",
default_listing_type: "All",
interface_language: "en",
show_avatars: true,
@ -676,14 +703,14 @@ export async function saveUserSettingsBio(
export async function saveUserSettingsFederated(
api: LemmyHttp,
): Promise<SuccessResponse> {
let avatar = "https://image.flaticon.com/icons/png/512/35/35896.png";
let banner = "https://image.flaticon.com/icons/png/512/36/35896.png";
let avatar = sampleImage;
let banner = sampleImage;
let bio = "a changed bio";
let form: SaveUserSettings = {
show_nsfw: false,
blur_nsfw: true,
auto_expand: false,
default_sort_type: "Hot",
default_post_sort_type: "Hot",
default_listing_type: "All",
interface_language: "",
avatar,
@ -743,6 +770,7 @@ export async function unfollowRemotes(
await Promise.all(
remoteFollowed.map(cu => followCommunity(api, false, cu.community.id)),
);
let siteRes = await getSite(api);
return siteRes;
}
@ -871,15 +899,17 @@ export async function deleteAllImages(api: LemmyHttp) {
const imagesRes = await api.listAllMedia({
limit: imageFetchLimit,
});
imagesRes.images;
for (const image of imagesRes.images) {
const form: DeleteImage = {
token: image.pictrs_delete_token,
filename: image.pictrs_alias,
};
await api.deleteImage(form);
}
Promise.all(
imagesRes.images
.map(image => {
const form: DeleteImage = {
token: image.local_image.pictrs_delete_token,
filename: image.local_image.pictrs_alias,
};
return form;
})
.map(form => api.deleteImage(form)),
);
}
export async function unfollows() {
@ -890,6 +920,24 @@ export async function unfollows() {
unfollowRemotes(delta),
unfollowRemotes(epsilon),
]);
await Promise.all([
purgeAllPosts(alpha),
purgeAllPosts(beta),
purgeAllPosts(gamma),
purgeAllPosts(delta),
purgeAllPosts(epsilon),
]);
}
export async function purgeAllPosts(api: LemmyHttp) {
// The best way to get all federated items, is to find the posts
let res = await api.getPosts({ type_: "All", limit: 50 });
await Promise.all(
Array.from(new Set(res.posts.map(p => p.post.id)))
.map(post_id => api.purgePost({ post_id }))
// Ignore errors
.map(p => p.catch(e => e)),
);
}
export function getCommentParentId(comment: Comment): number | undefined {

View file

@ -20,11 +20,14 @@ import {
getComments,
fetchFunction,
alphaImage,
unfollows,
saveUserSettingsBio,
} from "./shared";
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
beforeAll(setupLogins);
afterAll(unfollows);
let apShortname: string;
@ -128,7 +131,11 @@ test("Requests with invalid auth should be treated as unauthenticated", async ()
});
test("Create user with Arabic name", async () => {
let user = await registerUser(alpha, alphaUrl, "تجريب");
let user = await registerUser(
alpha,
alphaUrl,
"تجريب" + Math.random().toString().slice(2, 10), // less than actor_name_max_length
);
let site = await getSite(user);
expect(site.my_user).toBeDefined();
@ -184,10 +191,26 @@ test("Set a new avatar, old avatar is deleted", async () => {
expect(upload2.url).toBeDefined();
let form2 = {
avatar: upload1.url,
avatar: upload2.url,
};
await saveUserSettings(alpha, form2);
// make sure only the new avatar is kept
const listMediaRes2 = await alphaImage.listMedia();
expect(listMediaRes2.images.length).toBe(1);
// Upload that same form2 avatar, make sure it isn't replaced / deleted
await saveUserSettings(alpha, form2);
// make sure only the new avatar is kept
const listMediaRes3 = await alphaImage.listMedia();
expect(listMediaRes3.images.length).toBe(1);
// Now try to save a user settings, with the icon missing,
// and make sure it doesn't clear the data, or delete the image
await saveUserSettingsBio(alpha);
let site = await getSite(alpha);
expect(site.my_user?.local_user_view.person.avatar).toBe(upload2.url);
// make sure only the new avatar is kept
const listMediaRes4 = await alphaImage.listMedia();
expect(listMediaRes4.images.length).toBe(1);
});

View file

@ -26,6 +26,7 @@ body = """
{%- endif %}
{%- endfor -%}
{%- if github -%}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
{% raw %}\n{% endraw -%}
## New Contributors
@ -36,6 +37,7 @@ body = """
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor -%}
{%- endif -%}
{% if version %}
{% if previous.version %}
@ -70,6 +72,7 @@ commit_preprocessors = [
# remove issue numbers from commits
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
]
commit_parsers = [{ field = "author.name", pattern = "renovate", skip = true }]
# protect breaking changes from being skipped due to matching a skipping commit_parser
protect_breaking_commits = false
# filter out the commits that are not matched by commit parsers

View file

@ -47,9 +47,10 @@
#
# To be removed in 0.20
cache_external_link_previews: true
# Specifies how to handle remote images, so that users don't have to connect directly to remote servers.
# Specifies how to handle remote images, so that users don't have to connect directly to remote
# servers.
image_mode:
# Leave images unchanged, don't generate any local thumbnails for post urls. Instead the the
# Leave images unchanged, don't generate any local thumbnails for post urls. Instead the
# Opengraph image is directly returned as thumbnail
"None"
@ -64,10 +65,11 @@
# or
# If enabled, all images from remote domains are rewritten to pass through `/api/v3/image_proxy`,
# including embedded images in markdown. Images are stored temporarily in pict-rs for caching.
# This improves privacy as users don't expose their IP to untrusted servers, and decreases load
# on other servers. However it increases bandwidth use for the local server.
# If enabled, all images from remote domains are rewritten to pass through
# `/api/v3/image_proxy`, including embedded images in markdown. Images are stored temporarily
# in pict-rs for caching. This improves privacy as users don't expose their IP to untrusted
# servers, and decreases load on other servers. However it increases bandwidth use for the
# local server.
#
# Requires pict-rs 0.5
"ProxyAllImages"
@ -106,10 +108,12 @@
port: 8536
# Whether the site is available over TLS. Needs to be true for federation to work.
tls_enabled: true
# The number of activitypub federation workers that can be in-flight concurrently
worker_count: 0
# The number of activitypub federation retry workers that can be in-flight concurrently
retry_count: 0
federation: {
# Limit to the number of concurrent outgoing federation requests per target instance.
# Set this to a higher value than 1 (e.g. 6) only if you have a huge instance (>10 activities
# per second) and if a receiving instance is not keeping up.
concurrent_sends_per_instance: 1
}
prometheus: {
bind: "127.0.0.1"
port: 10002

View file

@ -33,13 +33,14 @@ anyhow = { workspace = true }
tracing = { workspace = true }
chrono = { workspace = true }
url = { workspace = true }
wav = "1.0.0"
hound = "3.5.1"
sitemap-rs = "0.2.1"
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
actix-web-httpauth = "0.8.1"
totp-rs = { version = "5.6.0", features = ["gen_secret", "otpauth"] }
actix-web-httpauth = "0.8.2"
[dev-dependencies]
serial_test = { workspace = true }
tokio = { workspace = true }
elementtree = "1.2.3"
pretty_assertions = { workspace = true }
lemmy_api_crud = { workspace = true }

View file

@ -9,15 +9,20 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn distinguish_comment(
data: Json<DistinguishComment>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<CommentResponse>, LemmyError> {
let orig_comment = CommentView::read(&mut context.pool(), data.comment_id, None).await?;
) -> LemmyResult<Json<CommentResponse>> {
let orig_comment = CommentView::read(
&mut context.pool(),
data.comment_id,
Some(&local_user_view.local_user),
)
.await?;
check_community_user_action(
&local_user_view.person,
@ -52,7 +57,7 @@ pub async fn distinguish_comment(
let comment_view = CommentView::read(
&mut context.pool(),
data.comment_id,
Some(local_user_view.person.id),
Some(&local_user_view.local_user),
)
.await?;

View file

@ -17,7 +17,7 @@ use lemmy_db_schema::{
traits::Likeable,
};
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use std::ops::Deref;
#[tracing::instrument(skip(context))]
@ -25,7 +25,7 @@ pub async fn like_comment(
data: Json<CreateCommentLike>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<CommentResponse>, LemmyError> {
) -> LemmyResult<Json<CommentResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let mut recipient_ids = Vec::<LocalUserId>::new();
@ -35,7 +35,12 @@ pub async fn like_comment(
check_bot_account(&local_user_view.person)?;
let comment_id = data.comment_id;
let orig_comment = CommentView::read(&mut context.pool(), comment_id, None).await?;
let orig_comment = CommentView::read(
&mut context.pool(),
comment_id,
Some(&local_user_view.local_user),
)
.await?;
check_community_user_action(
&local_user_view.person,
@ -46,7 +51,7 @@ pub async fn like_comment(
// Add parent poster or commenter to recipients
let comment_reply = CommentReply::read_by_comment(&mut context.pool(), comment_id).await;
if let Ok(reply) = comment_reply {
if let Ok(Some(reply)) = comment_reply {
let recipient_id = reply.recipient_id;
if let Ok(local_recipient) = LocalUserView::read_person(&mut context.pool(), recipient_id).await
{

View file

@ -5,7 +5,7 @@ use lemmy_api_common::{
utils::is_mod_or_admin,
};
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
/// Lists likes for a comment
#[tracing::instrument(skip(context))]
@ -13,13 +13,14 @@ pub async fn list_comment_likes(
data: Query<ListCommentLikes>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListCommentLikesResponse>, LemmyError> {
) -> LemmyResult<Json<ListCommentLikesResponse>> {
let comment_view = CommentView::read(
&mut context.pool(),
data.comment_id,
Some(local_user_view.person.id),
Some(&local_user_view.local_user),
)
.await?;
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,

View file

@ -8,14 +8,14 @@ use lemmy_db_schema::{
traits::Saveable,
};
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn save_comment(
data: Json<SaveComment>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<CommentResponse>, LemmyError> {
) -> LemmyResult<Json<CommentResponse>> {
let comment_saved_form = CommentSavedForm {
comment_id: data.comment_id,
person_id: local_user_view.person.id,
@ -32,8 +32,12 @@ pub async fn save_comment(
}
let comment_id = data.comment_id;
let person_id = local_user_view.person.id;
let comment_view = CommentView::read(&mut context.pool(), comment_id, Some(person_id)).await?;
let comment_view = CommentView::read(
&mut context.pool(),
comment_id,
Some(&local_user_view.local_user),
)
.await?;
Ok(Json(CommentResponse {
comment_view,

View file

@ -19,7 +19,7 @@ use lemmy_db_schema::{
traits::Reportable,
};
use lemmy_db_views::structs::{CommentReportView, CommentView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Creates a comment report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
@ -27,7 +27,7 @@ pub async fn create_comment_report(
data: Json<CreateCommentReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<CommentReportResponse>, LemmyError> {
) -> LemmyResult<Json<CommentReportResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = data.reason.trim().to_string();
@ -35,7 +35,12 @@ pub async fn create_comment_report(
let person_id = local_user_view.person.id;
let comment_id = data.comment_id;
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
let comment_view = CommentView::read(
&mut context.pool(),
comment_id,
Some(&local_user_view.local_user),
)
.await?;
check_community_user_action(
&local_user_view.person,

View file

@ -5,7 +5,7 @@ use lemmy_api_common::{
utils::check_community_mod_of_any_or_admin_action,
};
use lemmy_db_views::{comment_report_view::CommentReportQuery, structs::LocalUserView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
/// Lists comment reports for a community if an id is supplied
/// or returns all comment reports for communities a user moderates
@ -14,7 +14,7 @@ pub async fn list_comment_reports(
data: Query<ListCommentReports>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListCommentReportsResponse>, LemmyError> {
) -> LemmyResult<Json<ListCommentReportsResponse>> {
let community_id = data.community_id;
let comment_id = data.comment_id;
let unresolved_only = data.unresolved_only.unwrap_or_default();

View file

@ -6,7 +6,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{source::comment_report::CommentReport, traits::Reportable};
use lemmy_db_views::structs::{CommentReportView, LocalUserView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Resolves or unresolves a comment report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
@ -14,7 +14,7 @@ pub async fn resolve_comment_report(
data: Json<ResolveCommentReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<CommentReportResponse>, LemmyError> {
) -> LemmyResult<Json<CommentReportResponse>> {
let report_id = data.report_id;
let person_id = local_user_view.person.id;
let report = CommentReportView::read(&mut context.pool(), report_id, person_id).await?;

View file

@ -9,20 +9,21 @@ use lemmy_api_common::{
use lemmy_db_schema::{
source::{
community::{Community, CommunityModerator, CommunityModeratorForm},
local_user::LocalUser,
moderator::{ModAddCommunity, ModAddCommunityForm},
},
traits::{Crud, Joinable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn add_mod_to_community(
data: Json<AddModToCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<AddModToCommunityResponse>, LemmyError> {
) -> LemmyResult<Json<AddModToCommunityResponse>> {
let community_id = data.community_id;
// Verify that only mods or admins can add mod
@ -33,9 +34,30 @@ pub async fn add_mod_to_community(
&mut context.pool(),
)
.await?;
// If its a mod removal, also check that you're a higher mod.
if !data.added {
LocalUser::is_higher_mod_or_admin_check(
&mut context.pool(),
community_id,
local_user_view.person.id,
vec![data.person_id],
)
.await?;
}
let community = Community::read(&mut context.pool(), community_id).await?;
// If user is admin and community is remote, explicitly check that he is a
// moderator. This is necessary because otherwise the action would be rejected
// by the community's home instance.
if local_user_view.local_user.admin && !community.local {
Err(LemmyErrorType::NotAModerator)?
CommunityModeratorView::check_is_community_moderator(
&mut context.pool(),
community.id,
local_user_view.person.id,
)
.await?;
}
// Update in local database

View file

@ -4,7 +4,11 @@ use lemmy_api_common::{
community::{BanFromCommunity, BanFromCommunityResponse},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_mod_action, check_expire_time, remove_user_data_in_community},
utils::{
check_community_mod_action,
check_expire_time,
remove_or_restore_user_data_in_community,
},
};
use lemmy_db_schema::{
source::{
@ -14,6 +18,7 @@ use lemmy_db_schema::{
CommunityPersonBan,
CommunityPersonBanForm,
},
local_user::LocalUser,
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
},
traits::{Bannable, Crud, Followable},
@ -21,7 +26,7 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::validation::is_valid_body_field,
};
@ -30,9 +35,8 @@ pub async fn ban_from_community(
data: Json<BanFromCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<BanFromCommunityResponse>, LemmyError> {
) -> LemmyResult<Json<BanFromCommunityResponse>> {
let banned_person_id = data.person_id;
let remove_data = data.remove_data.unwrap_or(false);
let expires = check_expire_time(data.expires)?;
// Verify that only mods or admins can ban
@ -43,7 +47,18 @@ pub async fn ban_from_community(
&mut context.pool(),
)
.await?;
is_valid_body_field(&data.reason, false)?;
LocalUser::is_higher_mod_or_admin_check(
&mut context.pool(),
data.community_id,
local_user_view.person.id,
vec![data.person_id],
)
.await?;
if let Some(reason) = &data.reason {
is_valid_body_field(reason, false)?;
}
let community_user_ban_form = CommunityPersonBanForm {
community_id: data.community_id,
@ -73,9 +88,16 @@ pub async fn ban_from_community(
}
// Remove/Restore their data if that's desired
if remove_data {
remove_user_data_in_community(data.community_id, banned_person_id, &mut context.pool()).await?;
}
if data.remove_or_restore_data.unwrap_or(false) {
let remove_data = data.ban;
remove_or_restore_user_data_in_community(
data.community_id,
banned_person_id,
remove_data,
&mut context.pool(),
)
.await?;
};
// Mod tables
let form = ModBanFromCommunityForm {

View file

@ -14,14 +14,14 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn block_community(
data: Json<BlockCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<BlockCommunityResponse>, LemmyError> {
) -> LemmyResult<Json<BlockCommunityResponse>> {
let community_id = data.community_id;
let person_id = local_user_view.person.id;
let community_block_form = CommunityBlockForm {
@ -50,8 +50,13 @@ pub async fn block_community(
.with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?;
}
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?;
let community_view = CommunityView::read(
&mut context.pool(),
community_id,
Some(&local_user_view.local_user),
false,
)
.await?;
ActivityChannel::submit_activity(
SendActivityData::FollowCommunity(

View file

@ -15,14 +15,14 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn follow_community(
data: Json<FollowCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<CommunityResponse>, LemmyError> {
) -> LemmyResult<Json<CommunityResponse>> {
let community = Community::read(&mut context.pool(), data.community_id).await?;
let mut community_follower_form = CommunityFollowerForm {
community_id: community.id,
@ -60,9 +60,14 @@ pub async fn follow_community(
}
let community_id = data.community_id;
let person_id = local_user_view.person.id;
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?;
let community_view = CommunityView::read(
&mut context.pool(),
community_id,
Some(&local_user_view.local_user),
false,
)
.await?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(Json(CommunityResponse {

View file

@ -15,14 +15,14 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn hide_community(
data: Json<HideCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
// Verify its a admin (only admin can hide or unhide it)
is_admin(&local_user_view)?;

View file

@ -15,7 +15,7 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
location_info,
};
@ -26,7 +26,7 @@ pub async fn transfer_community(
data: Json<TransferCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetCommunityResponse>, LemmyError> {
) -> LemmyResult<Json<GetCommunityResponse>> {
let community_id = data.community_id;
let mut community_mods =
CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
@ -76,16 +76,16 @@ pub async fn transfer_community(
ModTransferCommunity::create(&mut context.pool(), &form).await?;
let community_id = data.community_id;
let person_id = local_user_view.person.id;
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
.await
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
let community_view = CommunityView::read(
&mut context.pool(),
community_id,
Some(&local_user_view.local_user),
false,
)
.await?;
let community_id = data.community_id;
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntFindCommunity)?;
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
// Return the jwt
Ok(Json(GetCommunityResponse {

View file

@ -26,7 +26,7 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
error::{LemmyErrorExt, LemmyErrorExt2, LemmyErrorType, LemmyResult},
utils::slurs::check_slurs,
};
use std::io::Cursor;
@ -44,32 +44,38 @@ pub mod site;
pub mod sitemap;
/// Converts the captcha to a base64 encoded wav audio file
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyError> {
pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> LemmyResult<String> {
let letters = captcha.as_wav();
// Decode each wav file, concatenate the samples
let mut concat_samples: Vec<i16> = Vec::new();
let mut any_header: Option<wav::Header> = None;
let mut any_header: Option<hound::WavSpec> = None;
for letter in letters {
let mut cursor = Cursor::new(letter.unwrap_or_default());
let (header, samples) = wav::read(&mut cursor)?;
any_header = Some(header);
if let Some(samples16) = samples.as_sixteen() {
concat_samples.extend(samples16);
} else {
Err(LemmyErrorType::CouldntCreateAudioCaptcha)?
}
let reader = hound::WavReader::new(&mut cursor)?;
any_header = Some(reader.spec());
let samples16 = reader
.into_samples::<i16>()
.collect::<Result<Vec<_>, _>>()
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
concat_samples.extend(samples16);
}
// Encode the concatenated result as a wav file
let mut output_buffer = Cursor::new(vec![]);
if let Some(header) = any_header {
wav::write(
header,
&wav::BitDepth::Sixteen(concat_samples),
&mut output_buffer,
)
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
let mut writer = hound::WavWriter::new(&mut output_buffer, header)
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
let mut writer16 = writer.get_i16_writer(concat_samples.len() as u32);
for sample in concat_samples {
writer16.write_sample(sample);
}
writer16
.flush()
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
writer
.finalize()
.with_lemmy_type(LemmyErrorType::CouldntCreateAudioCaptcha)?;
Ok(base64.encode(output_buffer.into_inner()))
} else {
@ -78,7 +84,7 @@ pub(crate) fn captcha_as_wav_base64(captcha: &Captcha) -> Result<String, LemmyEr
}
/// Check size of report
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Result<(), LemmyError> {
pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> LemmyResult<()> {
let slur_regex = &local_site_to_slur_regex(local_site);
check_slurs(reason, slur_regex)?;
@ -91,7 +97,7 @@ pub(crate) fn check_report_reason(reason: &str, local_site: &LocalSite) -> Resul
}
}
pub fn read_auth_token(req: &HttpRequest) -> Result<Option<String>, LemmyError> {
pub fn read_auth_token(req: &HttpRequest) -> LemmyResult<Option<String>> {
// Try reading jwt from auth header
if let Ok(header) = Authorization::<Bearer>::parse(req) {
Ok(Some(header.as_ref().token().to_string()))
@ -135,7 +141,7 @@ pub(crate) fn generate_totp_2fa_secret() -> String {
Secret::generate_secret().to_string()
}
fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> Result<TOTP, LemmyError> {
fn build_totp_2fa(hostname: &str, username: &str, secret: &str) -> LemmyResult<TOTP> {
let sec = Secret::Raw(secret.as_bytes().to_vec());
let sec_bytes = sec
.to_bytes()
@ -166,7 +172,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
target: &Person,
ban: bool,
reason: &Option<String>,
remove_data: &Option<bool>,
remove_or_restore_data: &Option<bool>,
expires: &Option<i64>,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
@ -224,7 +230,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
person_id: target.id,
ban,
reason: reason.clone(),
remove_data: *remove_data,
remove_or_restore_data: *remove_or_restore_data,
expires: *expires,
};
@ -248,7 +254,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities(
pub async fn local_user_view_from_jwt(
jwt: &str,
context: &LemmyContext,
) -> Result<LocalUserView, LemmyError> {
) -> LemmyResult<LocalUserView> {
let local_user_id = Claims::validate(jwt, context)
.await
.with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
@ -259,8 +265,6 @@ pub async fn local_user_view_from_jwt(
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
use super::*;

View file

@ -13,23 +13,33 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn add_admin(
data: Json<AddAdmin>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<AddAdminResponse>, LemmyError> {
) -> LemmyResult<Json<AddAdminResponse>> {
// Make sure user is an admin
is_admin(&local_user_view)?;
// If its an admin removal, also check that you're a higher admin
if !data.added {
LocalUser::is_higher_admin_check(
&mut context.pool(),
local_user_view.person.id,
vec![data.person_id],
)
.await?;
}
// Make sure that the person_id added is local
let added_local_user = LocalUserView::read_person(&mut context.pool(), data.person_id)
.await
.with_lemmy_type(LemmyErrorType::ObjectNotLocal)?;
.map_err(|_| LemmyErrorType::ObjectNotLocal)?;
let added_admin = LocalUser::update(
LocalUser::update(
&mut context.pool(),
added_local_user.local_user.id,
&LocalUserUpdateForm {
@ -43,7 +53,7 @@ pub async fn add_admin(
// Mod tables
let form = ModAddForm {
mod_person_id: local_user_view.person.id,
other_person_id: added_admin.person_id,
other_person_id: added_local_user.person.id,
removed: Some(!data.added),
};

View file

@ -5,10 +5,11 @@ use lemmy_api_common::{
context::LemmyContext,
person::{BanPerson, BanPersonResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_expire_time, is_admin, remove_user_data},
utils::{check_expire_time, is_admin, remove_user_data, restore_user_data},
};
use lemmy_db_schema::{
source::{
local_user::LocalUser,
login_token::LoginToken,
moderator::{ModBan, ModBanForm},
person::{Person, PersonUpdateForm},
@ -18,7 +19,7 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType},
error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::validation::is_valid_body_field,
};
@ -27,11 +28,21 @@ pub async fn ban_from_site(
data: Json<BanPerson>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<BanPersonResponse>, LemmyError> {
) -> LemmyResult<Json<BanPersonResponse>> {
// Make sure user is an admin
is_admin(&local_user_view)?;
is_valid_body_field(&data.reason, false)?;
// Also make sure you're a higher admin than the target
LocalUser::is_higher_admin_check(
&mut context.pool(),
local_user_view.person.id,
vec![data.person_id],
)
.await?;
if let Some(reason) = &data.reason {
is_valid_body_field(reason, false)?;
}
let expires = check_expire_time(data.expires)?;
@ -54,10 +65,13 @@ pub async fn ban_from_site(
}
// Remove their data if that's desired
let remove_data = data.remove_data.unwrap_or(false);
if remove_data {
remove_user_data(person.id, &context).await?;
}
if data.remove_or_restore_data.unwrap_or(false) {
if data.ban {
remove_user_data(person.id, &context).await?;
} else {
restore_user_data(person.id, &context).await?;
}
};
// Mod tables
let form = ModBanForm {
@ -77,7 +91,7 @@ pub async fn ban_from_site(
&person,
data.ban,
&data.reason,
&data.remove_data,
&data.remove_or_restore_data,
&data.expires,
&context,
)
@ -88,7 +102,7 @@ pub async fn ban_from_site(
moderator: local_user_view.person,
banned_user: person_view.person.clone(),
reason: data.reason.clone(),
remove_data: data.remove_data,
remove_or_restore_data: data.remove_or_restore_data,
ban: data.ban,
expires: data.expires,
},

View file

@ -9,14 +9,14 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn block_person(
data: Json<BlockPerson>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<BlockPersonResponse>, LemmyError> {
) -> LemmyResult<Json<BlockPersonResponse>> {
let target_id = data.person_id;
let person_id = local_user_view.person.id;
@ -30,8 +30,11 @@ pub async fn block_person(
target_id,
};
let target_user = LocalUserView::read_person(&mut context.pool(), target_id).await;
if target_user.map(|t| t.local_user.admin) == Ok(true) {
let target_user = LocalUserView::read_person(&mut context.pool(), target_id)
.await
.ok();
if target_user.is_some_and(|t| t.local_user.admin) {
Err(LemmyErrorType::CantBlockAdmin)?
}

View file

@ -11,7 +11,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::source::{local_user::LocalUser, login_token::LoginToken};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn change_password(
@ -19,7 +19,7 @@ pub async fn change_password(
req: HttpRequest,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<LoginResponse>, LemmyError> {
) -> LemmyResult<Json<LoginResponse>> {
password_length_check(&data.new_password)?;
// Make sure passwords match
@ -28,11 +28,13 @@ pub async fn change_password(
}
// Check the old password
let valid: bool = verify(
&data.old_password,
&local_user_view.local_user.password_encrypted,
)
.unwrap_or(false);
let valid: bool = if let Some(password_encrypted) = &local_user_view.local_user.password_encrypted
{
verify(&data.old_password, password_encrypted).unwrap_or(false)
} else {
data.old_password.is_empty()
};
if !valid {
Err(LemmyErrorType::IncorrectLogin)?
}

View file

@ -10,18 +10,18 @@ use lemmy_db_schema::source::{
login_token::LoginToken,
password_reset_request::PasswordResetRequest,
};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn change_password_after_reset(
data: Json<PasswordChangeAfterReset>,
context: Data<LemmyContext>,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
// Fetch the user_id from the token
let token = data.token.clone();
let local_user_id = PasswordResetRequest::read_from_token(&mut context.pool(), &token)
.await
.map(|p| p.local_user_id)?;
let local_user_id = PasswordResetRequest::read_and_delete(&mut context.pool(), &token)
.await?
.local_user_id;
password_length_check(&data.password)?;

View file

@ -1,14 +1,13 @@
use crate::{build_totp_2fa, generate_totp_2fa_secret};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
person::GenerateTotpSecretResponse,
sensitive::Sensitive,
use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse};
use lemmy_db_schema::source::{
local_user::{LocalUser, LocalUserUpdateForm},
site::Site,
};
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyError, LemmyErrorType};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
/// Generate a new secret for two-factor-authentication. Afterwards you need to call [toggle_totp]
/// to enable it. This can only be called if 2FA is currently disabled.
@ -16,16 +15,15 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType};
pub async fn generate_totp_secret(
local_user_view: LocalUserView,
context: Data<LemmyContext>,
) -> Result<Json<GenerateTotpSecretResponse>, LemmyError> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
) -> LemmyResult<Json<GenerateTotpSecretResponse>> {
let site = Site::read_local(&mut context.pool()).await?;
if local_user_view.local_user.totp_2fa_enabled {
return Err(LemmyErrorType::TotpAlreadyEnabled)?;
}
let secret = generate_totp_2fa_secret();
let secret_url =
build_totp_2fa(&site_view.site.name, &local_user_view.person.name, &secret)?.get_url();
let secret_url = build_totp_2fa(&site.name, &local_user_view.person.name, &secret)?.get_url();
let local_user_form = LocalUserUpdateForm {
totp_2fa_secret: Some(Some(secret)),
@ -39,6 +37,6 @@ pub async fn generate_totp_secret(
.await?;
Ok(Json(GenerateTotpSecretResponse {
totp_secret_url: Sensitive::new(secret_url),
totp_secret_url: secret_url.into(),
}))
}

View file

@ -17,10 +17,10 @@ use lemmy_db_schema::source::{
captcha_answer::{CaptchaAnswer, CaptchaAnswerForm},
local_site::LocalSite,
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn get_captcha(context: Data<LemmyContext>) -> Result<HttpResponse, LemmyError> {
pub async fn get_captcha(context: Data<LemmyContext>) -> LemmyResult<HttpResponse> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let mut res = HttpResponseBuilder::new(StatusCode::OK);
res.insert_header(CacheControl(vec![CacheDirective::NoStore]));

View file

@ -2,12 +2,12 @@ use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, person::BannedPersonsResponse, utils::is_admin};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
pub async fn list_banned_users(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<BannedPersonsResponse>, LemmyError> {
) -> LemmyResult<Json<BannedPersonsResponse>> {
// Make sure user is an admin
is_admin(&local_user_view)?;

View file

@ -1,14 +1,14 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::context::LemmyContext;
use lemmy_api_common::{context::LemmyContext, person::ListLoginsResponse};
use lemmy_db_schema::source::login_token::LoginToken;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
pub async fn list_logins(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<Vec<LoginToken>>, LemmyError> {
) -> LemmyResult<Json<ListLoginsResponse>> {
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
Ok(Json(logins))
Ok(Json(ListLoginsResponse { logins }))
}

View file

@ -3,19 +3,18 @@ use lemmy_api_common::{
context::LemmyContext,
person::{ListMedia, ListMediaResponse},
};
use lemmy_db_schema::source::images::LocalImage;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_media(
data: Query<ListMedia>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListMediaResponse>, LemmyError> {
) -> LemmyResult<Json<ListMediaResponse>> {
let page = data.page;
let limit = data.limit;
let images = LocalImage::get_all_paged_by_local_user_id(
let images = LocalImageView::get_all_paged_by_local_user_id(
&mut context.pool(),
local_user_view.local_user.id,
page,

View file

@ -1,4 +1,4 @@
use crate::{check_totp_2fa_valid, local_user::check_email_verified};
use crate::check_totp_2fa_valid;
use actix_web::{
web::{Data, Json},
HttpRequest,
@ -8,37 +8,31 @@ use lemmy_api_common::{
claims::Claims,
context::LemmyContext,
person::{Login, LoginResponse},
utils::check_user_valid,
};
use lemmy_db_schema::{
source::{local_site::LocalSite, registration_application::RegistrationApplication},
utils::DbPool,
RegistrationMode,
utils::{check_email_verified, check_registration_application, check_user_valid},
};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn login(
data: Json<Login>,
req: HttpRequest,
context: Data<LemmyContext>,
) -> Result<Json<LoginResponse>, LemmyError> {
) -> LemmyResult<Json<LoginResponse>> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
// Fetch that username / email
let username_or_email = data.username_or_email.clone();
let local_user_view =
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email)
.await
.with_lemmy_type(LemmyErrorType::IncorrectLogin)?;
LocalUserView::find_by_email_or_name(&mut context.pool(), &username_or_email).await?;
// Verify the password
let valid: bool = verify(
&data.password,
&local_user_view.local_user.password_encrypted,
)
.unwrap_or(false);
let valid: bool = local_user_view
.local_user
.password_encrypted
.as_ref()
.and_then(|password_encrypted| verify(&data.password, password_encrypted).ok())
.unwrap_or(false);
if !valid {
Err(LemmyErrorType::IncorrectLogin)?
}
@ -65,26 +59,3 @@ pub async fn login(
registration_created: false,
}))
}
async fn check_registration_application(
local_user_view: &LocalUserView,
local_site: &LocalSite,
pool: &mut DbPool<'_>,
) -> Result<(), LemmyError> {
if (local_site.registration_mode == RegistrationMode::RequireApplication
|| local_site.registration_mode == RegistrationMode::Closed)
&& !local_user_view.local_user.accepted_application
&& !local_user_view.local_user.admin
{
// Fetch the registration application. If no admin id is present its still pending. Otherwise it
// was processed (either accepted or denied).
let local_user_id = local_user_view.local_user.id;
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?;
if registration.admin_id.is_some() {
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
} else {
Err(LemmyErrorType::RegistrationApplicationIsPending)?
}
}
Ok(())
}

View file

@ -1,6 +1,3 @@
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
pub mod add_admin;
pub mod ban_person;
pub mod block;
@ -20,15 +17,3 @@ pub mod save_settings;
pub mod update_totp;
pub mod validate_auth;
pub mod verify_email;
/// Check if the user's email is verified if email verification is turned on
/// However, skip checking verification if the user is an admin
fn check_email_verified(local_user_view: &LocalUserView, site_view: &SiteView) -> LemmyResult<()> {
if !local_user_view.local_user.admin
&& site_view.local_site.require_email_verification
&& !local_user_view.local_user.email_verified
{
Err(LemmyErrorType::EmailNotVerified)?
}
Ok(())
}

View file

@ -5,14 +5,14 @@ use lemmy_api_common::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::person_mention_view::PersonMentionQuery;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_mentions(
data: Query<GetPersonMentions>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetPersonMentionsResponse>, LemmyError> {
) -> LemmyResult<Json<GetPersonMentionsResponse>> {
let sort = data.sort;
let page = data.page;
let limit = data.limit;

View file

@ -5,14 +5,14 @@ use lemmy_api_common::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::comment_reply_view::CommentReplyQuery;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_replies(
data: Query<GetReplies>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetRepliesResponse>, LemmyError> {
) -> LemmyResult<Json<GetRepliesResponse>> {
let sort = data.sort;
let page = data.page;
let limit = data.limit;

View file

@ -6,13 +6,13 @@ use lemmy_db_schema::source::{
private_message::PrivateMessage,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_all_notifications_read(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetRepliesResponse>, LemmyError> {
) -> LemmyResult<Json<GetRepliesResponse>> {
let person_id = local_user_view.person.id;
// Mark all comment_replies as read

View file

@ -9,14 +9,14 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonMentionView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_person_mention_as_read(
data: Json<MarkPersonMentionAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PersonMentionResponse>, LemmyError> {
) -> LemmyResult<Json<PersonMentionResponse>> {
let person_mention_id = data.person_mention_id;
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;

View file

@ -9,14 +9,14 @@ use lemmy_db_schema::{
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::CommentReplyView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_reply_as_read(
data: Json<MarkCommentReplyAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<CommentReplyResponse>, LemmyError> {
) -> LemmyResult<Json<CommentReplyResponse>> {
let comment_reply_id = data.comment_reply_id;
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id).await?;

View file

@ -2,18 +2,21 @@ use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn unread_count(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetUnreadCountResponse>, LemmyError> {
) -> LemmyResult<Json<GetUnreadCountResponse>> {
let person_id = local_user_view.person.id;
let replies = CommentReplyView::get_unread_replies(&mut context.pool(), person_id).await?;
let replies =
CommentReplyView::get_unread_replies(&mut context.pool(), &local_user_view.local_user).await?;
let mentions = PersonMentionView::get_unread_mentions(&mut context.pool(), person_id).await?;
let mentions =
PersonMentionView::get_unread_mentions(&mut context.pool(), &local_user_view.local_user)
.await?;
let private_messages =
PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?;

View file

@ -10,14 +10,14 @@ use lemmy_db_views::structs::{
PostReportView,
PrivateMessageReportView,
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn report_count(
data: Query<GetReportCount>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetReportCountResponse>, LemmyError> {
) -> LemmyResult<Json<GetReportCountResponse>> {
let person_id = local_user_view.person.id;
let admin = local_user_view.local_user.admin;
let community_id = data.community_id;

View file

@ -1,14 +1,12 @@
use crate::local_user::check_email_verified;
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
person::PasswordReset,
utils::send_password_reset_email,
utils::{check_email_verified, send_password_reset_email},
SuccessResponse,
};
use lemmy_db_schema::source::password_reset_request::PasswordResetRequest;
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn reset_password(
@ -19,17 +17,8 @@ pub async fn reset_password(
let email = data.email.to_lowercase();
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
.await
.with_lemmy_type(LemmyErrorType::IncorrectLogin)?;
.map_err(|_| LemmyErrorType::IncorrectLogin)?;
// Check for too many attempts (to limit potential abuse)
let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count(
&mut context.pool(),
local_user_view.local_user.id,
)
.await?;
if recent_resets_count >= 3 {
Err(LemmyErrorType::PasswordResetLimitReached)?
}
let site_view = SiteView::read_local(&mut context.pool()).await?;
check_email_verified(&local_user_view, &site_view)?;

View file

@ -21,44 +21,49 @@ use lemmy_db_schema::{
person::{Person, PersonUpdateForm},
},
traits::Crud,
utils::diesel_option_overwrite,
utils::{diesel_string_update, diesel_url_update},
};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{
error::{LemmyError, LemmyErrorType},
error::{LemmyErrorType, LemmyResult},
utils::validation::{is_valid_bio_field, is_valid_display_name, is_valid_matrix_id},
};
use std::ops::Deref;
#[tracing::instrument(skip(context))]
pub async fn save_user_settings(
data: Json<SaveUserSettings>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let slur_regex = local_site_to_slur_regex(&site_view.local_site);
let url_blocklist = get_url_blocklist(&context).await?;
let bio = diesel_option_overwrite(
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context).await?,
let bio = diesel_string_update(
process_markdown_opt(&data.bio, &slur_regex, &url_blocklist, &context)
.await?
.as_deref(),
);
replace_image(&data.avatar, &local_user_view.person.avatar, &context).await?;
replace_image(&data.banner, &local_user_view.person.banner, &context).await?;
let avatar = proxy_image_link_opt_api(&data.avatar, &context).await?;
let banner = proxy_image_link_opt_api(&data.banner, &context).await?;
let display_name = diesel_option_overwrite(data.display_name.clone());
let matrix_user_id = diesel_option_overwrite(data.matrix_user_id.clone());
let avatar = diesel_url_update(data.avatar.as_deref())?;
replace_image(&avatar, &local_user_view.person.avatar, &context).await?;
let avatar = proxy_image_link_opt_api(avatar, &context).await?;
let banner = diesel_url_update(data.banner.as_deref())?;
replace_image(&banner, &local_user_view.person.banner, &context).await?;
let banner = proxy_image_link_opt_api(banner, &context).await?;
let display_name = diesel_string_update(data.display_name.as_deref());
let matrix_user_id = diesel_string_update(data.matrix_user_id.as_deref());
let email_deref = data.email.as_deref().map(str::to_lowercase);
let email = diesel_option_overwrite(email_deref.clone());
let email = diesel_string_update(email_deref.as_deref());
if let Some(Some(email)) = &email {
let previous_email = local_user_view.local_user.email.clone().unwrap_or_default();
// if email was changed, check that it is not taken and send verification mail
if &previous_email != email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?;
}
if previous_email.deref() != email {
LocalUser::check_is_email_taken(&mut context.pool(), email).await?;
send_verification_email(
&local_user_view,
email,
@ -69,7 +74,8 @@ pub async fn save_user_settings(
}
}
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None value
// When the site requires email, make sure email is not Some(None). IE, an overwrite to a None
// value
if let Some(email) = &email {
if email.is_none() && site_view.local_site.require_email_verification {
Err(LemmyErrorType::EmailRequired)?
@ -94,7 +100,8 @@ pub async fn save_user_settings(
let local_user_id = local_user_view.local_user.id;
let person_id = local_user_view.person.id;
let default_listing_type = data.default_listing_type;
let default_sort_type = data.default_sort_type;
let default_post_sort_type = data.default_post_sort_type;
let default_comment_sort_type = data.default_comment_sort_type;
let person_form = PersonUpdateForm {
display_name,
@ -123,10 +130,9 @@ pub async fn save_user_settings(
send_notifications_to_email: data.send_notifications_to_email,
show_nsfw: data.show_nsfw,
blur_nsfw: data.blur_nsfw,
auto_expand: data.auto_expand,
show_bot_accounts: data.show_bot_accounts,
show_scores: data.show_scores,
default_sort_type,
default_post_sort_type,
default_comment_sort_type,
default_listing_type,
theme: data.theme.clone(),
interface_language: data.interface_language.clone(),
@ -139,11 +145,7 @@ pub async fn save_user_settings(
..Default::default()
};
// Ignore errors, because 'no fields updated' will return an error.
// https://github.com/LemmyNet/lemmy/issues/4076
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form)
.await
.ok();
LocalUser::update(&mut context.pool(), local_user_id, &local_user_form).await?;
// Update the vote display modes
let vote_display_modes_form = LocalUserVoteDisplayModeUpdateForm {

View file

@ -6,7 +6,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
/// Enable or disable two-factor-authentication. The current setting is determined from
/// [LocalUser.totp_2fa_enabled].
@ -21,7 +21,7 @@ pub async fn update_totp(
data: Json<UpdateTotp>,
local_user_view: LocalUserView,
context: Data<LemmyContext>,
) -> Result<Json<UpdateTotpResponse>, LemmyError> {
) -> LemmyResult<Json<UpdateTotpResponse>> {
check_totp_2fa_valid(
&local_user_view,
&Some(data.totp_token.clone()),

View file

@ -4,7 +4,7 @@ use actix_web::{
HttpRequest,
};
use lemmy_api_common::{context::LemmyContext, SuccessResponse};
use lemmy_utils::error::{LemmyError, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
/// Returns an error message if the auth token is invalid for any reason. Necessary because other
/// endpoints silently treat any call with invalid auth as unauthenticated.
@ -12,7 +12,7 @@ use lemmy_utils::error::{LemmyError, LemmyErrorType};
pub async fn validate_auth(
req: HttpRequest,
context: Data<LemmyContext>,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
let jwt = read_auth_token(&req)?;
if let Some(jwt) = jwt {
local_user_view_from_jwt(&jwt, &context).await?;

View file

@ -5,17 +5,12 @@ use lemmy_api_common::{
utils::send_new_applicant_email_to_admins,
SuccessResponse,
};
use lemmy_db_schema::{
source::{
email_verification::EmailVerification,
local_user::{LocalUser, LocalUserUpdateForm},
person::Person,
},
traits::Crud,
RegistrationMode,
use lemmy_db_schema::source::{
email_verification::EmailVerification,
local_user::{LocalUser, LocalUserUpdateForm},
};
use lemmy_db_views::structs::SiteView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::LemmyResult;
pub async fn verify_email(
data: Json<VerifyEmail>,
@ -23,9 +18,7 @@ pub async fn verify_email(
) -> LemmyResult<Json<SuccessResponse>> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let token = data.token.clone();
let verification = EmailVerification::read_for_token(&mut context.pool(), &token)
.await
.with_lemmy_type(LemmyErrorType::TokenNotFound)?;
let verification = EmailVerification::read_for_token(&mut context.pool(), &token).await?;
let form = LocalUserUpdateForm {
// necessary in case this is a new signup
@ -36,17 +29,20 @@ pub async fn verify_email(
};
let local_user_id = verification.local_user_id;
let local_user = LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
LocalUser::update(&mut context.pool(), local_user_id, &form).await?;
EmailVerification::delete_old_tokens_for_local_user(&mut context.pool(), local_user_id).await?;
// send out notification about registration application to admins if enabled
if site_view.local_site.registration_mode == RegistrationMode::RequireApplication
&& site_view.local_site.application_email_admins
{
let person = Person::read(&mut context.pool(), local_user.person_id).await?;
send_new_applicant_email_to_admins(&person.name, &mut context.pool(), context.settings())
.await?;
if site_view.local_site.application_email_admins {
let local_user = LocalUserView::read(&mut context.pool(), local_user_id).await?;
send_new_applicant_email_to_admins(
&local_user.person.name,
&mut context.pool(),
context.settings(),
)
.await?;
}
Ok(Json(SuccessResponse::default()))

View file

@ -16,14 +16,14 @@ use lemmy_db_schema::{
PostFeatureType,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn feature_post(
data: Json<FeaturePost>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PostResponse>, LemmyError> {
) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
@ -70,11 +70,5 @@ pub async fn feature_post(
)
.await?;
build_post_response(
&context,
orig_post.community_id,
&local_user_view.person,
post_id,
)
.await
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
}

View file

@ -4,14 +4,22 @@ use lemmy_api_common::{
post::{GetSiteMetadata, GetSiteMetadataResponse},
request::fetch_link_metadata,
};
use lemmy_utils::error::LemmyError;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{
error::{LemmyErrorExt, LemmyResult},
LemmyErrorType,
};
use url::Url;
#[tracing::instrument(skip(context))]
pub async fn get_link_metadata(
data: Query<GetSiteMetadata>,
context: Data<LemmyContext>,
) -> Result<Json<GetSiteMetadataResponse>, LemmyError> {
let metadata = fetch_link_metadata(&data.url, false, &context).await?;
// Require an account for this API
_local_user_view: LocalUserView,
) -> LemmyResult<Json<GetSiteMetadataResponse>> {
let url = Url::parse(&data.url).with_lemmy_type(LemmyErrorType::InvalidUrl)?;
let metadata = fetch_link_metadata(&url, &context).await?;
Ok(Json(GetSiteMetadataResponse { metadata }))
}

View file

@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, post::HidePost, SuccessResponse};
use lemmy_db_schema::source::post::PostHide;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
use std::collections::HashSet;
#[tracing::instrument(skip(context))]
@ -10,7 +10,7 @@ pub async fn hide_post(
data: Json<HidePost>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
let post_ids = HashSet::from_iter(data.post_ids.clone());
if post_ids.len() > MAX_API_PARAM_ELEMENTS {

View file

@ -21,7 +21,7 @@ use lemmy_db_schema::{
traits::{Crud, Likeable},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use std::ops::Deref;
#[tracing::instrument(skip(context))]
@ -29,7 +29,7 @@ pub async fn like_post(
data: Json<CreatePostLike>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PostResponse>, LemmyError> {
) -> LemmyResult<Json<PostResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
// Don't do a downvote if site has downvotes disabled
@ -66,25 +66,20 @@ pub async fn like_post(
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
}
// Mark the post as read
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
let community = Community::read(&mut context.pool(), post.community_id).await?;
ActivityChannel::submit_activity(
SendActivityData::LikePostOrComment {
object_id: post.ap_id,
actor: local_user_view.person.clone(),
community: Community::read(&mut context.pool(), post.community_id).await?,
community,
score: data.score,
},
&context,
)
.await?;
build_post_response(
context.deref(),
post.community_id,
&local_user_view.person,
post_id,
)
.await
build_post_response(context.deref(), post.community_id, local_user_view, post_id).await
}

View file

@ -6,7 +6,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{source::post::Post, traits::Crud};
use lemmy_db_views::structs::{LocalUserView, VoteView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
/// Lists likes for a post
#[tracing::instrument(skip(context))]
@ -14,7 +14,7 @@ pub async fn list_post_likes(
data: Query<ListPostLikes>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListPostLikesResponse>, LemmyError> {
) -> LemmyResult<Json<ListPostLikesResponse>> {
let post = Post::read(&mut context.pool(), data.post_id).await?;
is_mod_or_admin(
&mut context.pool(),

View file

@ -15,14 +15,14 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn lock_post(
data: Json<LockPost>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PostResponse>, LemmyError> {
) -> LemmyResult<Json<PostResponse>> {
let post_id = data.post_id;
let orig_post = Post::read(&mut context.pool(), post_id).await?;
@ -61,11 +61,5 @@ pub async fn lock_post(
)
.await?;
build_post_response(
&context,
orig_post.community_id,
&local_user_view.person,
post_id,
)
.await
build_post_response(&context, orig_post.community_id, local_user_view, post_id).await
}

View file

@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, post::MarkPostAsRead, SuccessResponse};
use lemmy_db_schema::source::post::PostRead;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType, MAX_API_PARAM_ELEMENTS};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
use std::collections::HashSet;
#[tracing::instrument(skip(context))]
@ -10,7 +10,7 @@ pub async fn mark_post_as_read(
data: Json<MarkPostAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
let post_ids = HashSet::from_iter(data.post_ids.clone());
if post_ids.len() > MAX_API_PARAM_ELEMENTS {

View file

@ -9,14 +9,14 @@ use lemmy_db_schema::{
traits::Saveable,
};
use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn save_post(
data: Json<SavePost>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PostResponse>, LemmyError> {
) -> LemmyResult<Json<PostResponse>> {
let post_saved_form = PostSavedForm {
post_id: data.post_id,
person_id: local_user_view.person.id,
@ -34,9 +34,14 @@ pub async fn save_post(
let post_id = data.post_id;
let person_id = local_user_view.person.id;
let post_view = PostView::read(&mut context.pool(), post_id, Some(person_id), false).await?;
let post_view = PostView::read(
&mut context.pool(),
post_id,
Some(&local_user_view.local_user),
false,
)
.await?;
// Mark the post as read
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
Ok(Json(PostResponse { post_view }))

View file

@ -19,7 +19,7 @@ use lemmy_db_schema::{
traits::Reportable,
};
use lemmy_db_views::structs::{LocalUserView, PostReportView, PostView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Creates a post report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
@ -27,7 +27,7 @@ pub async fn create_post_report(
data: Json<CreatePostReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PostReportResponse>, LemmyError> {
) -> LemmyResult<Json<PostReportResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = data.reason.trim().to_string();

View file

@ -5,7 +5,7 @@ use lemmy_api_common::{
utils::check_community_mod_of_any_or_admin_action,
};
use lemmy_db_views::{post_report_view::PostReportQuery, structs::LocalUserView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
/// Lists post reports for a community if an id is supplied
/// or returns all post reports for communities a user moderates
@ -14,7 +14,7 @@ pub async fn list_post_reports(
data: Query<ListPostReports>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListPostReportsResponse>, LemmyError> {
) -> LemmyResult<Json<ListPostReportsResponse>> {
let community_id = data.community_id;
let post_id = data.post_id;
let unresolved_only = data.unresolved_only.unwrap_or_default();

View file

@ -6,7 +6,7 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{source::post_report::PostReport, traits::Reportable};
use lemmy_db_views::structs::{LocalUserView, PostReportView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
/// Resolves or unresolves a post report and notifies the moderators of the community
#[tracing::instrument(skip(context))]
@ -14,7 +14,7 @@ pub async fn resolve_post_report(
data: Json<ResolvePostReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PostReportResponse>, LemmyError> {
) -> LemmyResult<Json<PostReportResponse>> {
let report_id = data.report_id;
let person_id = local_user_view.person.id;
let report = PostReportView::read(&mut context.pool(), report_id, person_id).await?;

View file

@ -8,14 +8,14 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_pm_as_read(
data: Json<MarkPrivateMessageAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PrivateMessageResponse>, LemmyError> {
) -> LemmyResult<Json<PrivateMessageResponse>> {
// Checking permissions
let private_message_id = data.private_message_id;
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;

View file

@ -14,14 +14,14 @@ use lemmy_db_schema::{
traits::{Crud, Reportable},
};
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn create_pm_report(
data: Json<CreatePrivateMessageReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
) -> LemmyResult<Json<PrivateMessageReportResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
let reason = data.reason.trim().to_string();

View file

@ -8,14 +8,14 @@ use lemmy_db_views::{
private_message_report_view::PrivateMessageReportQuery,
structs::LocalUserView,
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_pm_reports(
data: Query<ListPrivateMessageReports>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListPrivateMessageReportsResponse>, LemmyError> {
) -> LemmyResult<Json<ListPrivateMessageReportsResponse>> {
is_admin(&local_user_view)?;
let unresolved_only = data.unresolved_only.unwrap_or_default();

View file

@ -6,14 +6,14 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{source::private_message_report::PrivateMessageReport, traits::Reportable};
use lemmy_db_views::structs::{LocalUserView, PrivateMessageReportView};
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn resolve_pm_report(
data: Json<ResolvePrivateMessageReport>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<PrivateMessageReportResponse>, LemmyError> {
) -> LemmyResult<Json<PrivateMessageReportResponse>> {
is_admin(&local_user_view)?;
let report_id = data.report_id;

View file

@ -9,14 +9,14 @@ use lemmy_db_schema::{
traits::Blockable,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyErrorExt, LemmyErrorType};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn block_instance(
data: Json<BlockInstance>,
local_user_view: LocalUserView,
context: Data<LemmyContext>,
) -> Result<Json<BlockInstanceResponse>, LemmyError> {
) -> LemmyResult<Json<BlockInstanceResponse>> {
let instance_id = data.instance_id;
let person_id = local_user_view.person.id;
if local_user_view.person.instance_id == instance_id {

View file

@ -5,12 +5,12 @@ use lemmy_api_common::{
utils::build_federated_instances,
};
use lemmy_db_views::structs::SiteView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn get_federated_instances(
context: Data<LemmyContext>,
) -> Result<Json<GetFederatedInstancesResponse>, LemmyError> {
) -> LemmyResult<Json<GetFederatedInstancesResponse>> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let federated_instances =
build_federated_instances(&site_view.local_site, &mut context.pool()).await?;

View file

@ -7,14 +7,15 @@ use lemmy_db_schema::{
local_site_url_blocklist::LocalSiteUrlBlocklist,
local_user::{LocalUser, LocalUserUpdateForm},
moderator::{ModAdd, ModAddForm},
oauth_provider::OAuthProvider,
tagline::Tagline,
},
traits::Crud,
};
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{
error::{LemmyError, LemmyErrorType},
error::{LemmyErrorType, LemmyResult},
VERSION,
};
@ -22,7 +23,7 @@ use lemmy_utils::{
pub async fn leave_admin(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetSiteResponse>, LemmyError> {
) -> LemmyResult<Json<GetSiteResponse>> {
is_admin(&local_user_view)?;
// Make sure there isn't just one admin (so if one leaves, there will still be one left)
@ -60,10 +61,9 @@ pub async fn leave_admin(
let all_languages = Language::read_all(&mut context.pool()).await?;
let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?;
let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?;
let custom_emojis =
CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?;
let oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?;
let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
let tagline = Tagline::get_random(&mut context.pool()).await?;
Ok(Json(GetSiteResponse {
site_view,
@ -72,8 +72,11 @@ pub async fn leave_admin(
my_user: None,
all_languages,
discussion_languages,
taglines,
custom_emojis,
oauth_providers: Some(oauth_providers),
admin_oauth_providers: None,
blocked_urls,
tagline,
taglines: vec![],
custom_emojis: vec![],
}))
}

View file

@ -4,21 +4,20 @@ use lemmy_api_common::{
person::{ListMedia, ListMediaResponse},
utils::is_admin,
};
use lemmy_db_schema::source::images::LocalImage;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_db_views::structs::{LocalImageView, LocalUserView};
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_all_media(
data: Query<ListMedia>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListMediaResponse>, LemmyError> {
) -> LemmyResult<Json<ListMediaResponse>> {
// Only let admins view all media
is_admin(&local_user_view)?;
let page = data.page;
let limit = data.limit;
let images = LocalImage::get_all(&mut context.pool(), page, limit).await?;
let images = LocalImageView::get_all(&mut context.pool(), page, limit).await?;
Ok(Json(ListMediaResponse { images }))
}

View file

@ -24,7 +24,7 @@ use lemmy_db_views_moderator::structs::{
ModTransferCommunityView,
ModlogListParams,
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
use ModlogActionType::*;
#[tracing::instrument(skip(context))]
@ -32,7 +32,7 @@ pub async fn get_mod_log(
data: Query<GetModlog>,
context: Data<LemmyContext>,
local_user_view: Option<LocalUserView>,
) -> Result<Json<GetModlogResponse>, LemmyError> {
) -> LemmyResult<Json<GetModlogResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
check_private_instance(&local_user_view, &local_site)?;

View file

@ -10,26 +10,40 @@ use lemmy_api_common::{
use lemmy_db_schema::{
source::{
comment::Comment,
local_user::LocalUser,
moderator::{AdminPurgeComment, AdminPurgeCommentForm},
},
traits::Crud,
};
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_comment(
data: Json<PurgeComment>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
// Only let admin purge an item
is_admin(&local_user_view)?;
let comment_id = data.comment_id;
// Read the comment to get the post_id and community
let comment_view = CommentView::read(&mut context.pool(), comment_id, None).await?;
let comment_view = CommentView::read(
&mut context.pool(),
comment_id,
Some(&local_user_view.local_user),
)
.await?;
// Also check that you're a higher admin
LocalUser::is_higher_admin_check(
&mut context.pool(),
local_user_view.person.id,
vec![comment_view.creator.id],
)
.await?;
let post_id = comment_view.comment.post_id;

View file

@ -9,27 +9,45 @@ use lemmy_api_common::{
SuccessResponse,
};
use lemmy_db_schema::{
newtypes::PersonId,
source::{
community::Community,
local_user::LocalUser,
moderator::{AdminPurgeCommunity, AdminPurgeCommunityForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_community(
data: Json<PurgeCommunity>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
// Only let admin purge an item
is_admin(&local_user_view)?;
// Read the community to get its images
let community = Community::read(&mut context.pool(), data.community_id).await?;
// Also check that you're a higher admin than all the mods
let community_mod_person_ids =
CommunityModeratorView::for_community(&mut context.pool(), community.id)
.await?
.iter()
.map(|cmv| cmv.moderator.id)
.collect::<Vec<PersonId>>();
LocalUser::is_higher_admin_check(
&mut context.pool(),
local_user_view.person.id,
community_mod_person_ids,
)
.await?;
if let Some(banner) = &community.banner {
purge_image_from_pictrs(banner, &context).await.ok();
}

View file

@ -10,24 +10,34 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
source::{
local_user::LocalUser,
moderator::{AdminPurgePerson, AdminPurgePersonForm},
person::{Person, PersonUpdateForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_person(
data: Json<PurgePerson>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
// Only let admin purge an item
is_admin(&local_user_view)?;
// Also check that you're a higher admin
LocalUser::is_higher_admin_check(
&mut context.pool(),
local_user_view.person.id,
vec![data.person_id],
)
.await?;
let person = Person::read(&mut context.pool(), data.person_id).await?;
ban_nonlocal_user_from_local_communities(
&local_user_view,
&person,
@ -65,7 +75,7 @@ pub async fn purge_person(
moderator: local_user_view.person,
banned_user: person,
reason: data.reason.clone(),
remove_data: Some(true),
remove_or_restore_data: Some(true),
ban: true,
expires: None,
},

View file

@ -10,26 +10,35 @@ use lemmy_api_common::{
};
use lemmy_db_schema::{
source::{
local_user::LocalUser,
moderator::{AdminPurgePost, AdminPurgePostForm},
post::Post,
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn purge_post(
data: Json<PurgePost>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
) -> LemmyResult<Json<SuccessResponse>> {
// Only let admin purge an item
is_admin(&local_user_view)?;
// Read the post to get the community_id
let post = Post::read(&mut context.pool(), data.post_id).await?;
// Also check that you're a higher admin
LocalUser::is_higher_admin_check(
&mut context.pool(),
local_user_view.person.id,
vec![post.creator_id],
)
.await?;
// Purge image
if let Some(url) = &post.url {
purge_image_from_pictrs(url, &context).await.ok();

View file

@ -1,4 +1,5 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
site::{ApproveRegistrationApplication, RegistrationApplicationResponse},
@ -10,48 +11,60 @@ use lemmy_db_schema::{
registration_application::{RegistrationApplication, RegistrationApplicationUpdateForm},
},
traits::Crud,
utils::diesel_option_overwrite,
utils::{diesel_string_update, get_conn},
};
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::{LemmyError, LemmyResult};
pub async fn approve_registration_application(
data: Json<ApproveRegistrationApplication>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<RegistrationApplicationResponse>, LemmyError> {
) -> LemmyResult<Json<RegistrationApplicationResponse>> {
let app_id = data.id;
// Only let admins do this
is_admin(&local_user_view)?;
// Update the registration with reason, admin_id
let deny_reason = diesel_option_overwrite(data.deny_reason.clone());
let app_form = RegistrationApplicationUpdateForm {
admin_id: Some(Some(local_user_view.person.id)),
deny_reason,
};
let pool = &mut context.pool();
let conn = &mut get_conn(pool).await?;
let tx_data = data.clone();
let approved_user_id = conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
// Update the registration with reason, admin_id
let deny_reason = diesel_string_update(tx_data.deny_reason.as_deref());
let app_form = RegistrationApplicationUpdateForm {
admin_id: Some(Some(local_user_view.person.id)),
deny_reason,
};
let registration_application =
RegistrationApplication::update(&mut context.pool(), app_id, &app_form).await?;
let registration_application =
RegistrationApplication::update(&mut conn.into(), app_id, &app_form).await?;
// Update the local_user row
let local_user_form = LocalUserUpdateForm {
accepted_application: Some(data.approve),
..Default::default()
};
// Update the local_user row
let local_user_form = LocalUserUpdateForm {
accepted_application: Some(tx_data.approve),
..Default::default()
};
let approved_user_id = registration_application.local_user_id;
LocalUser::update(&mut context.pool(), approved_user_id, &local_user_form).await?;
let approved_user_id = registration_application.local_user_id;
LocalUser::update(&mut conn.into(), approved_user_id, &local_user_form).await?;
Ok::<_, LemmyError>(approved_user_id)
}) as _
})
.await?;
if data.approve {
let approved_local_user_view =
LocalUserView::read(&mut context.pool(), approved_user_id).await?;
if approved_local_user_view.local_user.email.is_some() {
// Email sending may fail, but this won't revert the application approval
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
}
}
};
// Read the view
let registration_application =

View file

@ -0,0 +1,26 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
site::{GetRegistrationApplication, RegistrationApplicationResponse},
utils::is_admin,
};
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
use lemmy_utils::error::LemmyResult;
/// Lists registration applications, filterable by undenied only.
pub async fn get_registration_application(
data: Query<GetRegistrationApplication>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<RegistrationApplicationResponse>> {
// Make sure user is an admin
is_admin(&local_user_view)?;
// Read the view
let registration_application =
RegistrationApplicationView::read_by_person(&mut context.pool(), data.person_id).await?;
Ok(Json(RegistrationApplicationResponse {
registration_application,
}))
}

View file

@ -1,4 +1,5 @@
use actix_web::web::{Data, Json, Query};
use activitypub_federation::config::Data;
use actix_web::web::{Json, Query};
use lemmy_api_common::{
context::LemmyContext,
site::{ListRegistrationApplications, ListRegistrationApplicationsResponse},
@ -9,14 +10,14 @@ use lemmy_db_views::{
registration_application_view::RegistrationApplicationQuery,
structs::LocalUserView,
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
/// Lists registration applications, filterable by undenied only.
pub async fn list_registration_applications(
data: Query<ListRegistrationApplications>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListRegistrationApplicationsResponse>, LemmyError> {
) -> LemmyResult<Json<ListRegistrationApplicationsResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin

View file

@ -1,3 +1,6 @@
pub mod approve;
pub mod get;
pub mod list;
#[cfg(test)]
mod tests;
pub mod unread_count;

View file

@ -0,0 +1,420 @@
use crate::site::registration_applications::{
approve::approve_registration_application,
list::list_registration_applications,
unread_count::get_unread_registration_application_count,
};
use activitypub_federation::config::Data;
use actix_web::web::{Json, Query};
use lemmy_api_common::{
context::LemmyContext,
site::{
ApproveRegistrationApplication,
EditSite,
GetUnreadRegistrationApplicationCountResponse,
ListRegistrationApplicationsResponse,
},
};
use lemmy_api_crud::site::update::update_site;
use lemmy_db_schema::{
newtypes::InstanceId,
source::{
instance::Instance,
local_site::{LocalSite, LocalSiteInsertForm},
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitInsertForm},
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
person::{Person, PersonInsertForm},
registration_application::{RegistrationApplication, RegistrationApplicationInsertForm},
site::{Site, SiteInsertForm},
},
traits::Crud,
utils::DbPool,
RegistrationMode,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API};
use serial_test::serial;
#[expect(clippy::unwrap_used)]
async fn create_test_site(context: &Data<LemmyContext>) -> LemmyResult<(Instance, LocalUserView)> {
let pool = &mut context.pool();
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string())
.await
.expect("Create test instance");
let admin_person = Person::create(
pool,
&PersonInsertForm::test_form(inserted_instance.id, "admin"),
)
.await?;
LocalUser::create(
pool,
&LocalUserInsertForm::test_form_admin(admin_person.id),
vec![],
)
.await?;
let admin_local_user_view = LocalUserView::read_person(pool, admin_person.id).await?;
let site_form = SiteInsertForm::new("test site".to_string(), inserted_instance.id);
let site = Site::create(pool, &site_form).await.unwrap();
// Create a local site, since this is necessary for determining if email verification is
// required
let local_site_form = LocalSiteInsertForm {
require_email_verification: Some(true),
application_question: Some(".".to_string()),
registration_mode: Some(RegistrationMode::RequireApplication),
site_setup: Some(true),
..LocalSiteInsertForm::new(site.id)
};
let local_site = LocalSite::create(pool, &local_site_form).await.unwrap();
// Required to have a working local SiteView when updating the site to change email verification
// requirement or registration mode
let rate_limit_form = LocalSiteRateLimitInsertForm::new(local_site.id);
LocalSiteRateLimit::create(pool, &rate_limit_form)
.await
.unwrap();
Ok((inserted_instance, admin_local_user_view))
}
async fn signup(
pool: &mut DbPool<'_>,
instance_id: InstanceId,
name: &str,
email: Option<&str>,
) -> LemmyResult<(LocalUser, RegistrationApplication)> {
let person_insert_form = PersonInsertForm::test_form(instance_id, name);
let person = Person::create(pool, &person_insert_form).await?;
let local_user_insert_form = match email {
Some(email) => LocalUserInsertForm {
email: Some(email.to_string()),
email_verified: Some(false),
..LocalUserInsertForm::test_form(person.id)
},
None => LocalUserInsertForm::test_form(person.id),
};
let local_user = LocalUser::create(pool, &local_user_insert_form, vec![]).await?;
let application_insert_form = RegistrationApplicationInsertForm {
local_user_id: local_user.id,
answer: "x".to_string(),
};
let application = RegistrationApplication::create(pool, &application_insert_form).await?;
Ok((local_user, application))
}
#[expect(clippy::unwrap_used)]
async fn get_application_statuses(
context: &Data<LemmyContext>,
admin: LocalUserView,
) -> LemmyResult<(
Json<GetUnreadRegistrationApplicationCountResponse>,
Json<ListRegistrationApplicationsResponse>,
Json<ListRegistrationApplicationsResponse>,
)> {
let application_count =
get_unread_registration_application_count(context.reset_request_count(), admin.clone()).await?;
let unread_applications = list_registration_applications(
Query::from_query("unread_only=true").unwrap(),
context.reset_request_count(),
admin.clone(),
)
.await?;
let all_applications = list_registration_applications(
Query::from_query("unread_only=false").unwrap(),
context.reset_request_count(),
admin,
)
.await?;
Ok((application_count, unread_applications, all_applications))
}
#[serial]
#[tokio::test]
#[expect(clippy::indexing_slicing)]
async fn test_application_approval() -> LemmyResult<()> {
let context = LemmyContext::init_test_context().await;
let pool = &mut context.pool();
let (instance, admin_local_user_view) = create_test_site(&context).await?;
// Non-unread counts unfortunately are duplicated due to different types (i64 vs usize)
let mut expected_total_applications = 0;
let mut expected_unread_applications = 0u8;
let (local_user_with_email, app_with_email) =
signup(pool, instance.id, "user_w_email", Some("lemmy@localhost")).await?;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// When email verification is required and the email is not verified the application should not
// be visible to admins
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
LocalUser::update(
pool,
local_user_with_email.id,
&LocalUserUpdateForm {
email_verified: Some(true),
..Default::default()
},
)
.await?;
expected_total_applications += 1;
expected_unread_applications += 1;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// When email verification is required and the email is verified the application should be
// visible to admins
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert!(
!unread_applications.registration_applications[0]
.creator_local_user
.accepted_application
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
let approval = approve_registration_application(
Json(ApproveRegistrationApplication {
id: app_with_email.id,
approve: true,
deny_reason: None,
}),
context.reset_request_count(),
admin_local_user_view.clone(),
)
.await;
// Approval should be processed up until email sending is attempted
assert!(approval.is_err_and(|e| e.error_type == LemmyErrorType::NoEmailSetup));
expected_unread_applications -= 1;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// When the application is approved it should only be returned for unread queries
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
assert!(
all_applications.registration_applications[0]
.creator_local_user
.accepted_application
);
let (_local_user, app_with_email_2) = signup(
pool,
instance.id,
"user_w_email_2",
Some("lemmy2@localhost"),
)
.await?;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// Email not verified, so application still not visible
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
update_site(
Json(EditSite {
require_email_verification: Some(false),
..Default::default()
}),
context.reset_request_count(),
admin_local_user_view.clone(),
)
.await?;
// TODO: There is probably a better way to ensure cache invalidation
tokio::time::sleep(CACHE_DURATION_API).await;
expected_total_applications += 1;
expected_unread_applications += 1;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// After disabling email verification the application should now be visible
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
approve_registration_application(
Json(ApproveRegistrationApplication {
id: app_with_email_2.id,
approve: false,
deny_reason: None,
}),
context.reset_request_count(),
admin_local_user_view.clone(),
)
.await?;
expected_unread_applications -= 1;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// Denied applications should not be marked as unread
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
signup(pool, instance.id, "user_wo_email", None).await?;
expected_total_applications += 1;
expected_unread_applications += 1;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// New user without email should immediately be visible
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
signup(pool, instance.id, "user_w_email_3", None).await?;
expected_total_applications += 1;
expected_unread_applications += 1;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// New user with email should immediately be visible
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
update_site(
Json(EditSite {
registration_mode: Some(RegistrationMode::Open),
..Default::default()
}),
context.reset_request_count(),
admin_local_user_view.clone(),
)
.await?;
// TODO: There is probably a better way to ensure cache invalidation
tokio::time::sleep(CACHE_DURATION_API).await;
let (application_count, unread_applications, all_applications) =
get_application_statuses(&context, admin_local_user_view.clone()).await?;
// TODO: At this time applications do not get approved when switching to open registration, so the
// numbers will not change. See https://github.com/LemmyNet/lemmy/issues/4969
// expected_application_count = 0;
// expected_unread_applications_len = 0;
// When applications are not required all previous applications should become approved but still
// visible
assert_eq!(
application_count.registration_applications,
i64::from(expected_unread_applications),
);
assert_eq!(
unread_applications.registration_applications.len(),
usize::from(expected_unread_applications),
);
assert_eq!(
all_applications.registration_applications.len(),
expected_total_applications,
);
LocalSite::delete(pool).await?;
// Instance deletion cascades cleanup of all created persons
Instance::delete(pool, instance.id).await?;
Ok(())
}

View file

@ -1,4 +1,5 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
site::GetUnreadRegistrationApplicationCountResponse,
@ -6,12 +7,12 @@ use lemmy_api_common::{
};
use lemmy_db_schema::source::local_site::LocalSite;
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
pub async fn get_unread_registration_application_count(
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<GetUnreadRegistrationApplicationCountResponse>, LemmyError> {
) -> LemmyResult<Json<GetUnreadRegistrationApplicationCountResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
// Only let admins do this

View file

@ -42,7 +42,7 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[expect(clippy::unwrap_used)]
pub(crate) mod tests {
use crate::sitemap::generate_urlset;

View file

@ -25,7 +25,7 @@ full = [
"lemmy_db_views_moderator/full",
"lemmy_utils/full",
"activitypub_federation",
"encoding",
"encoding_rs",
"reqwest-middleware",
"webpage",
"ts-rs",
@ -34,7 +34,6 @@ full = [
"reqwest",
"actix-web",
"futures",
"once_cell",
"jsonwebtoken",
"mime",
]
@ -61,18 +60,17 @@ reqwest = { workspace = true, optional = true }
ts-rs = { workspace = true, optional = true }
moka.workspace = true
anyhow.workspace = true
once_cell = { workspace = true, optional = true }
actix-web = { workspace = true, optional = true }
enum-map = { workspace = true }
urlencoding = { workspace = true }
mime = { version = "0.3.17", optional = true }
webpage = { version = "1.6", default-features = false, features = [
webpage = { version = "2.0", default-features = false, features = [
"serde",
], optional = true }
encoding = { version = "0.2.33", optional = true }
jsonwebtoken = { version = "8.3.0", optional = true }
encoding_rs = { version = "0.8.34", optional = true }
jsonwebtoken = { version = "9.3.0", optional = true }
# necessary for wasmt compilation
getrandom = { version = "0.2.12", features = ["js"] }
getrandom = { version = "0.2.15", features = ["js"] }
[package.metadata.cargo-machete]
ignored = ["getrandom"]

View file

@ -19,14 +19,13 @@ use lemmy_db_schema::{
comment_reply::{CommentReply, CommentReplyInsertForm},
person::Person,
person_mention::{PersonMention, PersonMentionInsertForm},
post::Post,
},
traits::Crud,
};
use lemmy_db_views::structs::{CommentView, LocalUserView, PostView};
use lemmy_db_views_actor::structs::CommunityView;
use lemmy_utils::{
error::LemmyError,
error::LemmyResult,
utils::{markdown::markdown_to_html, mention::MentionData},
};
@ -35,9 +34,10 @@ pub async fn build_comment_response(
comment_id: CommentId,
local_user_view: Option<LocalUserView>,
recipient_ids: Vec<LocalUserId>,
) -> Result<CommentResponse, LemmyError> {
let person_id = local_user_view.map(|l| l.person.id);
let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id).await?;
) -> LemmyResult<CommentResponse> {
let local_user = local_user_view.map(|l| l.local_user);
let comment_view =
CommentView::read(&mut context.pool(), comment_id, local_user.as_ref()).await?;
Ok(CommentResponse {
comment_view,
recipient_ids,
@ -48,15 +48,15 @@ pub async fn build_community_response(
context: &LemmyContext,
local_user_view: LocalUserView,
community_id: CommunityId,
) -> Result<Json<CommunityResponse>, LemmyError> {
) -> LemmyResult<Json<CommunityResponse>> {
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
.await
.is_ok();
let person_id = local_user_view.person.id;
let local_user = local_user_view.local_user;
let community_view = CommunityView::read(
&mut context.pool(),
community_id,
Some(person_id),
Some(&local_user),
is_mod_or_admin,
)
.await?;
@ -71,16 +71,17 @@ pub async fn build_community_response(
pub async fn build_post_response(
context: &LemmyContext,
community_id: CommunityId,
person: &Person,
local_user_view: LocalUserView,
post_id: PostId,
) -> Result<Json<PostResponse>, LemmyError> {
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id)
) -> LemmyResult<Json<PostResponse>> {
let local_user = local_user_view.local_user;
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &local_user_view.person, community_id)
.await
.is_ok();
let post_view = PostView::read(
&mut context.pool(),
post_id,
Some(person.id),
Some(&local_user),
is_mod_or_admin,
)
.await?;
@ -91,16 +92,26 @@ pub async fn build_post_response(
#[tracing::instrument(skip_all)]
pub async fn send_local_notifs(
mentions: Vec<MentionData>,
comment: &Comment,
comment_id: CommentId,
person: &Person,
post: &Post,
do_send_email: bool,
context: &LemmyContext,
) -> Result<Vec<LocalUserId>, LemmyError> {
local_user_view: Option<&LocalUserView>,
) -> LemmyResult<Vec<LocalUserId>> {
let mut recipient_ids = Vec::new();
let inbox_link = format!("{}/inbox", context.settings().get_protocol_and_hostname());
let community_id = post.community_id;
// let person = my_local_user.person;
// Read the comment view to get extra info
let comment_view = CommentView::read(
&mut context.pool(),
comment_id,
local_user_view.map(|view| &view.local_user),
)
.await?;
let comment = comment_view.comment;
let post = comment_view.post;
let community = comment_view.community;
// Send the local mentions
for mention in mentions
@ -112,12 +123,13 @@ pub async fn send_local_notifs(
if let Ok(mention_user_view) = user_view {
// TODO
// At some point, make it so you can't tag the parent creator either
// Potential duplication of notifications, one for reply and the other for mention, is handled below by checking recipient ids
// Potential duplication of notifications, one for reply and the other for mention, is handled
// below by checking recipient ids
recipient_ids.push(mention_user_view.local_user.id);
let user_mention_form = PersonMentionInsertForm {
recipient_id: mention_user_view.person.id,
comment_id: comment.id,
comment_id,
read: None,
};
@ -152,8 +164,9 @@ pub async fn send_local_notifs(
let check_blocks = check_person_instance_community_block(
person.id,
parent_creator_id,
person.instance_id,
community_id,
// Only block from the community's instance_id
community.instance_id,
community.id,
&mut context.pool(),
)
.await
@ -194,11 +207,13 @@ pub async fn send_local_notifs(
}
}
} else {
// Use the post creator to check blocks
let check_blocks = check_person_instance_community_block(
person.id,
post.creator_id,
person.instance_id,
community_id,
// Only block from the community's instance_id
community.instance_id,
community.id,
&mut context.pool(),
)
.await

View file

@ -1,9 +1,10 @@
use crate::{context::LemmyContext, sensitive::Sensitive};
use crate::context::LemmyContext;
use actix_web::{http::header::USER_AGENT, HttpRequest};
use chrono::Utc;
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use lemmy_db_schema::{
newtypes::LocalUserId,
sensitive::SensitiveString,
source::login_token::{LoginToken, LoginTokenCreateForm},
};
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
@ -28,19 +29,15 @@ impl Claims {
let claims =
decode::<Claims>(jwt, &key, &validation).with_lemmy_type(LemmyErrorType::NotLoggedIn)?;
let user_id = LocalUserId(claims.claims.sub.parse()?);
let is_valid = LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
if !is_valid {
Err(LemmyErrorType::NotLoggedIn)?
} else {
Ok(user_id)
}
LoginToken::validate(&mut context.pool(), user_id, jwt).await?;
Ok(user_id)
}
pub async fn generate(
user_id: LocalUserId,
req: HttpRequest,
context: &LemmyContext,
) -> LemmyResult<Sensitive<String>> {
) -> LemmyResult<SensitiveString> {
let hostname = context.settings().hostname.clone();
let my_claims = Claims {
sub: user_id.0.to_string(),
@ -50,7 +47,7 @@ impl Claims {
let secret = &context.secret().jwt_secret;
let key = EncodingKey::from_secret(secret.as_ref());
let token = encode(&Header::default(), &my_claims, &key)?;
let token: SensitiveString = encode(&Header::default(), &my_claims, &key)?.into();
let ip = req
.connection_info()
.realip_remote_addr()
@ -67,13 +64,12 @@ impl Claims {
user_agent,
};
LoginToken::create(&mut context.pool(), form).await?;
Ok(Sensitive::new(token))
Ok(token)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
#[expect(clippy::unwrap_used)]
mod tests {
use crate::{claims::Claims, context::LemmyContext};
@ -99,7 +95,7 @@ mod tests {
async fn test_should_not_validate_user_token_after_password_change() {
let pool_ = build_db_pool_for_tests().await;
let pool = &mut (&pool_).into();
let secret = Secret::init(pool).await.unwrap();
let secret = Secret::init(pool).await.unwrap().unwrap();
let context = LemmyContext::create(
pool_.clone(),
ClientBuilder::new(Client::default()).build(),
@ -111,18 +107,11 @@ mod tests {
.await
.unwrap();
let new_person = PersonInsertForm::builder()
.name("Gerry9812".into())
.public_key("pubkey".to_string())
.instance_id(inserted_instance.id)
.build();
let new_person = PersonInsertForm::test_form(inserted_instance.id, "Gerry9812");
let inserted_person = Person::create(pool, &new_person).await.unwrap();
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.password_encrypted("123456".to_string())
.build();
let local_user_form = LocalUserInsertForm::test_form(inserted_person.id);
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
.await

View file

@ -3,7 +3,7 @@ use lemmy_db_schema::{
source::site::Site,
CommunityVisibility,
ListingType,
SortType,
PostSortType,
};
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView, PersonView};
use serde::{Deserialize, Serialize};
@ -74,7 +74,7 @@ pub struct CommunityResponse {
/// Fetches a list of communities.
pub struct ListCommunities {
pub type_: Option<ListingType>,
pub sort: Option<SortType>,
pub sort: Option<PostSortType>,
pub show_nsfw: Option<bool>,
pub page: Option<i64>,
pub limit: Option<i64>,
@ -97,7 +97,9 @@ pub struct BanFromCommunity {
pub community_id: CommunityId,
pub person_id: PersonId,
pub ban: bool,
pub remove_data: Option<bool>,
/// Optionally remove or restore all their data. Useful for new troll accounts.
/// If ban is true, then this means remove. If ban is false, it means restore.
pub remove_or_restore_data: Option<bool>,
pub reason: Option<String>,
/// A time that the ban will expire, in unix epoch seconds.
///

View file

@ -55,7 +55,7 @@ impl LemmyContext {
/// Initialize a context for use in tests which blocks federation network calls.
///
/// Do not use this in production code.
pub async fn init_test_context() -> Data<LemmyContext> {
pub async fn init_test_federation_config() -> FederationConfig<LemmyContext> {
// call this to run migrations
let pool = build_db_pool_for_tests().await;
@ -64,20 +64,25 @@ impl LemmyContext {
let client = ClientBuilder::new(client).build();
let secret = Secret {
id: 0,
jwt_secret: String::new(),
jwt_secret: String::new().into(),
};
let rate_limit_cell = RateLimitCell::with_test_config();
let context = LemmyContext::create(pool, client, secret, rate_limit_cell.clone());
let config = FederationConfig::builder()
FederationConfig::builder()
.domain(context.settings().hostname.clone())
.app_data(context)
.debug(true)
// Dont allow any network fetches
.http_fetch_limit(0)
.build()
.await
.expect("build federation config");
.expect("build federation config")
}
pub async fn init_test_context() -> Data<LemmyContext> {
let config = Self::init_test_federation_config().await;
config.to_request_data()
}
}

View file

@ -1,6 +1,7 @@
use lemmy_db_schema::newtypes::CustomEmojiId;
use lemmy_db_views::structs::CustomEmojiView;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
use url::Url;
@ -46,3 +47,23 @@ pub struct DeleteCustomEmoji {
pub struct CustomEmojiResponse {
pub custom_emoji: CustomEmojiView,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// A response for custom emojis.
pub struct ListCustomEmojisResponse {
pub custom_emojis: Vec<CustomEmojiView>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Fetches a list of custom emojis.
pub struct ListCustomEmojis {
pub page: Option<i64>,
pub limit: Option<i64>,
pub category: Option<String>,
pub ignore_page_limits: Option<bool>,
}

View file

@ -7,6 +7,7 @@ pub mod community;
#[cfg(feature = "full")]
pub mod context;
pub mod custom_emoji;
pub mod oauth_provider;
pub mod person;
pub mod post;
pub mod private_message;
@ -14,8 +15,8 @@ pub mod private_message;
pub mod request;
#[cfg(feature = "full")]
pub mod send_activity;
pub mod sensitive;
pub mod site;
pub mod tagline;
#[cfg(feature = "full")]
pub mod utils;
@ -27,7 +28,7 @@ pub extern crate lemmy_utils;
pub use lemmy_utils::LemmyErrorType;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use std::{cmp::min, time::Duration};
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(ts_rs::TS))]
@ -43,7 +44,39 @@ impl Default for SuccessResponse {
}
}
/// how long to sleep based on how many retries have already happened
// TODO: use from_days once stabilized
// https://github.com/rust-lang/rust/issues/120301
const DAY: Duration = Duration::from_secs(24 * 60 * 60);
/// Calculate how long to sleep until next federation send based on how many
/// retries have already happened. Uses exponential backoff with maximum of one day. The first
/// error is ignored.
pub fn federate_retry_sleep_duration(retry_count: i32) -> Duration {
Duration::from_secs_f64(2.0_f64.powf(f64::from(retry_count)))
debug_assert!(retry_count != 0);
if retry_count == 1 {
return Duration::from_secs(0);
}
let retry_count = retry_count - 1;
let pow = 1.25_f64.powf(retry_count.into());
let pow = Duration::try_from_secs_f64(pow).unwrap_or(DAY);
min(DAY, pow)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
#[test]
fn test_federate_retry_sleep_duration() {
assert_eq!(Duration::from_secs(0), federate_retry_sleep_duration(1));
assert_eq!(
Duration::new(1, 250000000),
federate_retry_sleep_duration(2)
);
assert_eq!(
Duration::new(2, 441406250),
federate_retry_sleep_duration(5)
);
assert_eq!(DAY, federate_retry_sleep_duration(100));
}
}

View file

@ -0,0 +1,70 @@
use lemmy_db_schema::newtypes::OAuthProviderId;
use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
use url::Url;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Create an external auth method.
pub struct CreateOAuthProvider {
pub display_name: String,
pub issuer: String,
pub authorization_endpoint: String,
pub token_endpoint: String,
pub userinfo_endpoint: String,
pub id_claim: String,
pub client_id: String,
pub client_secret: String,
pub scopes: String,
pub auto_verify_email: Option<bool>,
pub account_linking_enabled: Option<bool>,
pub enabled: Option<bool>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Edit an external auth method.
pub struct EditOAuthProvider {
pub id: OAuthProviderId,
pub display_name: Option<String>,
pub authorization_endpoint: Option<String>,
pub token_endpoint: Option<String>,
pub userinfo_endpoint: Option<String>,
pub id_claim: Option<String>,
pub client_secret: Option<String>,
pub scopes: Option<String>,
pub auto_verify_email: Option<bool>,
pub account_linking_enabled: Option<bool>,
pub enabled: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Delete an external auth method.
pub struct DeleteOAuthProvider {
pub id: OAuthProviderId,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Logging in with an OAuth 2.0 authorization
pub struct AuthenticateWithOauth {
pub code: String,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub oauth_provider_id: OAuthProviderId,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub redirect_uri: Url,
pub show_nsfw: Option<bool>,
/// Username is mandatory at registration time
pub username: Option<String>,
/// An answer is mandatory if require application is enabled on the server
pub answer: Option<String>,
}

View file

@ -1,13 +1,13 @@
use crate::sensitive::Sensitive;
use lemmy_db_schema::{
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId},
source::{images::LocalImage, site::Site},
sensitive::SensitiveString,
source::{login_token::LoginToken, site::Site},
CommentSortType,
ListingType,
PostListingMode,
SortType,
PostSortType,
};
use lemmy_db_views::structs::{CommentView, PostView};
use lemmy_db_views::structs::{CommentView, LocalImageView, PostView};
use lemmy_db_views_actor::structs::{
CommentReplyView,
CommunityModeratorView,
@ -25,8 +25,8 @@ use ts_rs::TS;
#[cfg_attr(feature = "full", ts(export))]
/// Logging into lemmy.
pub struct Login {
pub username_or_email: Sensitive<String>,
pub password: Sensitive<String>,
pub username_or_email: SensitiveString,
pub password: SensitiveString,
/// May be required, if totp is enabled for their account.
pub totp_2fa_token: Option<String>,
}
@ -38,11 +38,11 @@ pub struct Login {
/// Register / Sign up to lemmy.
pub struct Register {
pub username: String,
pub password: Sensitive<String>,
pub password_verify: Sensitive<String>,
pub show_nsfw: bool,
pub password: SensitiveString,
pub password_verify: SensitiveString,
pub show_nsfw: Option<bool>,
/// email is mandatory if email verification is enabled on the server
pub email: Option<Sensitive<String>>,
pub email: Option<SensitiveString>,
/// The UUID of the captcha item.
pub captcha_uuid: Option<String>,
/// Your captcha answer.
@ -84,12 +84,18 @@ pub struct CaptchaResponse {
pub struct SaveUserSettings {
/// Show nsfw posts.
pub show_nsfw: Option<bool>,
/// Blur nsfw posts.
pub blur_nsfw: Option<bool>,
pub auto_expand: Option<bool>,
/// Your user's theme.
pub theme: Option<String>,
pub default_sort_type: Option<SortType>,
/// The default post listing type, usually "local"
pub default_listing_type: Option<ListingType>,
/// A post-view mode that changes how multiple post listings look.
pub post_listing_mode: Option<PostListingMode>,
/// The default post sort, usually "active"
pub default_post_sort_type: Option<PostSortType>,
/// The default comment sort, usually "hot"
pub default_comment_sort_type: Option<CommentSortType>,
/// The language of the lemmy interface
pub interface_language: Option<String>,
/// A URL for your avatar.
@ -99,7 +105,7 @@ pub struct SaveUserSettings {
/// Your display name, which can contain strange characters, and does not need to be unique.
pub display_name: Option<String>,
/// Your email.
pub email: Option<Sensitive<String>>,
pub email: Option<SensitiveString>,
/// Your bio / info, in markdown.
pub bio: Option<String>,
/// Your matrix user id. Ex: @my_user:matrix.org
@ -120,11 +126,10 @@ pub struct SaveUserSettings {
pub open_links_in_new_tab: Option<bool>,
/// Enable infinite scroll
pub infinite_scroll_enabled: Option<bool>,
/// A post-view mode that changes how multiple post listings look.
pub post_listing_mode: Option<PostListingMode>,
/// Whether to allow keyboard navigation (for browsing and interacting with posts and comments).
pub enable_keyboard_navigation: Option<bool>,
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or should be paused
/// Whether user avatars or inline images in the UI that are gifs should be allowed to play or
/// should be paused
pub enable_animated_images: Option<bool>,
/// Whether to auto-collapse bot comments.
pub collapse_bot_comments: Option<bool>,
@ -140,9 +145,9 @@ pub struct SaveUserSettings {
#[cfg_attr(feature = "full", ts(export))]
/// Changes your account password.
pub struct ChangePassword {
pub new_password: Sensitive<String>,
pub new_password_verify: Sensitive<String>,
pub old_password: Sensitive<String>,
pub new_password: SensitiveString,
pub new_password_verify: SensitiveString,
pub old_password: SensitiveString,
}
#[skip_serializing_none]
@ -151,8 +156,9 @@ pub struct ChangePassword {
#[cfg_attr(feature = "full", ts(export))]
/// A response for your login.
pub struct LoginResponse {
/// This is None in response to `Register` if email verification is enabled, or the server requires registration applications.
pub jwt: Option<Sensitive<String>>,
/// This is None in response to `Register` if email verification is enabled, or the server
/// requires registration applications.
pub jwt: Option<SensitiveString>,
/// If registration applications are required, this will return true for a signup response.
pub registration_created: bool,
/// If email verifications are required, this will return true for a signup response.
@ -170,7 +176,7 @@ pub struct GetPersonDetails {
pub person_id: Option<PersonId>,
/// Example: dessalines , or dessalines@xyz.tld
pub username: Option<String>,
pub sort: Option<SortType>,
pub sort: Option<PostSortType>,
pub page: Option<i64>,
pub limit: Option<i64>,
pub community_id: Option<CommunityId>,
@ -215,8 +221,9 @@ pub struct AddAdminResponse {
pub struct BanPerson {
pub person_id: PersonId,
pub ban: bool,
/// Optionally remove all their data. Useful for new troll accounts.
pub remove_data: Option<bool>,
/// Optionally remove or restore all their data. Useful for new troll accounts.
/// If ban is true, then this means remove. If ban is false, it means restore.
pub remove_or_restore_data: Option<bool>,
pub reason: Option<String>,
/// A time that the ban will expire, in unix epoch seconds.
///
@ -340,7 +347,7 @@ pub struct CommentReplyResponse {
#[cfg_attr(feature = "full", ts(export))]
/// Delete your account.
pub struct DeleteAccount {
pub password: Sensitive<String>,
pub password: SensitiveString,
pub delete_content: bool,
}
@ -349,7 +356,7 @@ pub struct DeleteAccount {
#[cfg_attr(feature = "full", ts(export))]
/// Reset your password via email.
pub struct PasswordReset {
pub email: Sensitive<String>,
pub email: SensitiveString,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
@ -357,9 +364,9 @@ pub struct PasswordReset {
#[cfg_attr(feature = "full", ts(export))]
/// Change your password after receiving a reset request.
pub struct PasswordChangeAfterReset {
pub token: Sensitive<String>,
pub password: Sensitive<String>,
pub password_verify: Sensitive<String>,
pub token: SensitiveString,
pub password: SensitiveString,
pub password_verify: SensitiveString,
}
#[skip_serializing_none]
@ -405,7 +412,7 @@ pub struct VerifyEmail {
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct GenerateTotpSecretResponse {
pub totp_secret_url: Sensitive<String>,
pub totp_secret_url: SensitiveString,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
@ -437,5 +444,12 @@ pub struct ListMedia {
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ListMediaResponse {
pub images: Vec<LocalImage>,
pub images: Vec<LocalImageView>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
pub struct ListLoginsResponse {
pub logins: Vec<LoginToken>,
}

View file

@ -2,7 +2,7 @@ use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, DbUrl, LanguageId, PostId, PostReportId},
ListingType,
PostFeatureType,
SortType,
PostSortType,
};
use lemmy_db_views::structs::{PaginationCursor, PostReportView, PostView, VoteView};
use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView};
@ -10,7 +10,6 @@ use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")]
use ts_rs::TS;
use url::Url;
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
@ -20,8 +19,7 @@ use url::Url;
pub struct CreatePost {
pub name: String,
pub community_id: CommunityId,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub url: Option<Url>,
pub url: Option<String>,
/// An optional body for the post in markdown.
pub body: Option<String>,
/// An optional alt_text, usable for image posts.
@ -30,9 +28,10 @@ pub struct CreatePost {
pub honeypot: Option<String>,
pub nsfw: Option<bool>,
pub language_id: Option<LanguageId>,
#[cfg_attr(feature = "full", ts(type = "string"))]
/// Instead of fetching a thumbnail, use a custom one.
pub custom_thumbnail: Option<Url>,
pub custom_thumbnail: Option<String>,
/// Time when this post should be scheduled. Null means publish immediately.
pub scheduled_publish_time: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -72,7 +71,7 @@ pub struct GetPostResponse {
/// Get a list of posts.
pub struct GetPosts {
pub type_: Option<ListingType>,
pub sort: Option<SortType>,
pub sort: Option<PostSortType>,
/// DEPRECATED, use page_cursor
pub page: Option<i64>,
pub limit: Option<i64>,
@ -82,6 +81,10 @@ pub struct GetPosts {
pub liked_only: Option<bool>,
pub disliked_only: Option<bool>,
pub show_hidden: Option<bool>,
/// If true, then show the read posts (even if your user setting is to hide them)
pub show_read: Option<bool>,
/// If true, then show the nsfw posts (even if your user setting is to hide them)
pub show_nsfw: Option<bool>,
pub page_cursor: Option<PaginationCursor>,
}
@ -114,17 +117,17 @@ pub struct CreatePostLike {
pub struct EditPost {
pub post_id: PostId,
pub name: Option<String>,
#[cfg_attr(feature = "full", ts(type = "string"))]
pub url: Option<Url>,
pub url: Option<String>,
/// An optional body for the post in markdown.
pub body: Option<String>,
/// An optional alt_text, usable for image posts.
pub alt_text: Option<String>,
pub nsfw: Option<bool>,
pub language_id: Option<LanguageId>,
#[cfg_attr(feature = "full", ts(type = "string"))]
/// Instead of fetching a thumbnail, use a custom one.
pub custom_thumbnail: Option<Url>,
pub custom_thumbnail: Option<String>,
/// Time when this post should be scheduled. Null means publish immediately.
pub scheduled_publish_time: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
@ -249,8 +252,7 @@ pub struct ListPostReportsResponse {
#[cfg_attr(feature = "full", ts(export))]
/// Get metadata for a given site.
pub struct GetSiteMetadata {
#[cfg_attr(feature = "full", ts(type = "string"))]
pub url: Url,
pub url: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -270,8 +272,6 @@ pub struct LinkMetadata {
#[serde(flatten)]
pub opengraph_data: OpenGraphData,
pub content_type: Option<String>,
#[serde(skip)]
pub thumbnail: Option<DbUrl>,
}
#[skip_serializing_none]

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