Compare commits

...

186 commits

Author SHA1 Message Date
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
Nutomic 087684658a
Cache result of LocalSite::read to avoid unnecessary db calls (#4585)
* Cache result of LocalSite::read to avoid unnecessary db calls

* single const for cache duration

* clippy

* revert apub send changes

* clippy

* fmt
2024-04-03 17:38:31 -04:00
Dessalines aaaa362b98
Remove latest tag for pgformatter. (#4589) 2024-04-03 17:29:24 -04:00
Dessalines 5237233f97 Version 0.19.4-beta.1 2024-04-03 16:50:35 -04:00
Dessalines 94438a8516
Removing cardano, this was never used. (#4588) 2024-04-03 10:38:57 +02:00
Dessalines 4d9c16a336
Fix private message sort order. #4581 (#4587) 2024-04-02 13:25:28 -04:00
Nutomic ae9f82b452
Read crate version from cargo.toml (fixes #4583) (#4584) 2024-04-02 11:19:51 -04:00
dullbananas 007e9b7aab
Optimize Community::set_featured_posts (#4579)
* Don't lock excess rows in Community::set_featured_posts

* Update community.rs

* Update community.rs

* Update community.rs

* Update community.rs
2024-04-02 11:19:04 -04:00
tracyspacy 60f9a97dfa
Fix unnecessarily duplicated notifs (#4578)
* add check to remove duplicated notifs

* added comments
2024-03-29 17:09:19 -04:00
Dessalines 067332553d
Fixing woodpecker. (#4575) 2024-03-27 11:09:44 -04:00
Nutomic a4b79ca610
Generate post thumbnail/metadata in background (ref #4529) (#4564)
* Generate post thumbnail/metadata in background (ref #4529)

* fix api test

* Apply suggestions from code review

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

* fix test

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-03-27 10:54:42 -04:00
Dessalines a632a86852
Delete a person's local images on delete account. (#4506)
* Delete a person's local images on delete account.

* Rename purge function to delete.

* Use purge_user_account instead of Person::delete_account in purge person.

* Fixing clippy
2024-03-27 10:28:02 -04:00
Nutomic 85ee89f4e8
When uploading new icon/avatar/banner, delete old one (#4573) 2024-03-27 09:00:52 -04:00
Dessalines 6bfbb9332d
Adding listMedia endpoint, to view all your local image uploads. (#4509)
* Adding listMedia endpoint, to view all your local image uploads.

- Fixes #4445

* Fix ts import.

* Forgot to order by published desc

* Adding an endpoint to list all images, for admins only.

* Forgot to add file.

* Add additional test.

* Use better logic for no-limit version.

* Better call sites.

* Adding another test.

* Fix tests.

* Moving list_media to /account action.

* Addressing PR comments.

* Removing pointless comment.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-03-26 12:06:11 -04:00
Dessalines 945064726f
Add creator_banned_from_community to vote_view. (#4568)
* Add creator_banned_from_community to vote_view.

- Fixes #4561

* Adding tests.
2024-03-26 16:22:04 +01:00
Dessalines 7929e77602
Fixing issue with comment replies wrongly marked as read. (#4567)
* Fixing issue with comment replies wrongly marked as read.

- Fixes #4566

* Elaborating on a comment.
2024-03-26 10:46:37 -04:00
Dessalines 95069d7648
Fixing some clippy and woodpecker lints. (#4565)
* Fixing some clippy and woodpecker lints.

* Try fixing woodpecker 1.

* Revert "Try fixing woodpecker 1."

This reverts commit 7c2020a08d.
2024-03-26 10:17:42 +01:00
Dessalines e4356a7701
Fixing a few broken tests from the change in LocalUser::create (#4569) 2024-03-25 19:14:35 -04:00
Nutomic 846848c4f6
On registration, automatically set content languages from accept-language header (#4550)
* On registration, automatically set content languages from accept header

* no need to set site language or default language for new user anymore

* fix test

* fix langs

* avoid duplicate writing of new user languages
2024-03-25 16:02:12 -04:00
Nutomic d06ef2c47e
Migrate apub block activity to standard endTime property and deprecate expires (fixes #2316) (#4541)
* Migrate apub block activity to standard `endTime` property (fixes #2316)

* add todo
2024-03-25 08:10:09 -04:00
Sander Saarend 99d01e186a
Fix rate limiter (#4560) 2024-03-25 07:56:03 -04:00
Nutomic ef4bb3cc40
Add delete user field removeData to apub assets (fixes #4544) (#4549) 2024-03-22 18:41:59 -04:00
Nutomic 21547dedf7
Fix handling of apub downvote (fixes #4545) (#4551)
* Fix handling of apub downvote (fixes #4545)

* fmt

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-03-22 18:41:09 -04:00
Nutomic baf5921d2c
Add comment about console feature requiring tokio_unstable (fixes #4553) (#4555) 2024-03-22 18:40:08 -04:00
SleeplessOne1917 6679b2559f
Add SleeplessOne1917 to CODEOWNERS (#4558)
Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-03-22 18:31:45 -04:00
SleeplessOne1917 38c22d9453
Add banned_from_community to PostView and CommentView (#4552)
* Add banned_from_community to PostView and CommentView

* Add post view test

* Add tests for CommentView

* Add tests for case where local user is not banned from community

---------

Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-03-22 18:31:08 -04:00
Tomas 78581bd696
Add ARG RUSTFLAGS into Dockerfile (#4556)
* Add ARG RUSTFLAGS into Dockerfile 

Allows passing RUSTFLAGS through --build-arg

* Remove invalid ARG syntax
2024-03-21 16:42:30 +01:00
Dessalines 2fd81067c7
Temporarily comment sticky test. (#4538) 2024-03-18 12:54:45 +01:00
Dessalines 0f77951e05
Upgrading deps. (#4537)
* Upgrading deps.

* Addressing PR comments
2024-03-18 10:36:49 +01:00
Nutomic 9d4299aaac
Dont require leading ! or @ for webfinger resolve (#4513)
* Dont require leading ! or @ for webfinger resolve

* fmt

* clippy
2024-03-15 08:42:09 -04:00
Nutomic 43378c5bb3
Fix video thumbnail generation (fixes #3484) (#4539)
* Fix video thumbnail generation (fixes #3484)

* fix test
2024-03-15 08:41:16 -04:00
flamingos-cant 19a1a077c5
Add a blocklist for URLs. (#4515)
* Add a blocklist for URLs.

* Fix SQL format

* Make clippy happy.

* Use regex for URL matching.

* Escape regex chars in URLs.

* Use post for modification.

* Make URL block regex static and remove API routes.

* Add date fields to table and use transaction.

* Use Cache for blocklist.

* Rename check_links + move list to parameters of process_markdown.

* SQL format.

* Format, again.

* Remove println.

* Add API test.

* Set a shorter lifetime for regex in debug mode.

* Add missing macro.

* Update lemmy-js-client

* Update api_test/pnpm-lock.yaml

* Don't break other tests

* Use different URL for test

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
Co-authored-by: Nutomic <me@nutomic.com>
2024-03-15 07:03:29 -04:00
Nutomic 0e7080337b
Dont allow admins to post in community with posting_restricted_to_mods (fixes #3571) (#4534)
* Dont allow admins to post in community with `posting_restricted_to_mods` (fixes #3571)

* fmt
2024-03-14 17:31:54 -04:00
Nutomic 835d329134
Fix longstanding bug that breaks initial community view (fixes #3529) (#4535) 2024-03-14 16:57:56 -04:00
Nutomic f1de7b7590
Automatically include apub hashtag with posts (fixes #3906) (#4533) 2024-03-14 12:16:45 -04:00
Dessalines 255e695633
Adding extra fields to PostReport and CommentReport views. (#4520)
- Fixes #4200
2024-03-13 12:11:24 -04:00
Dessalines 15f02f00a9
Add a vote_display_mode local_user setting. (#4450)
* Add a vote_display_mode local_user setting.

- Fixes #4449

* Changing HideDownvotes to Score.

* Adding ScoreAndDownvote display mode.

* Adding upvote and downvote mode.

* Extracting vote_display_mode to another table.

* Fixing fmt.

* Remove published and updated columns.

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-03-13 12:10:58 -04:00
Dessalines 45c56df4e8
Adding git cliff to help generate changelogs. (#4522) 2024-03-12 11:24:27 +01:00
Nutomic 5d361d63ef
Change 2FA to use hostname as issuer (fixes #4518) (#4525) 2024-03-11 16:27:05 -04:00
Dessalines 10bf7464b1
Add code of conduct rules to issue template. (#4523) 2024-03-08 11:53:57 -05:00
Nutomic 5859502a2a
Fix missing private key for signed fetch (#4516)
* Fix missing private key for signed fetch (fixes #4451)

* clippy

* instance actor name and webfinger

* better webfinger handling

* upgrade lib

* update test asset
2024-03-08 10:23:15 -05:00
Nutomic 00f7778485
Store thumbnails in db table local_image (#4512)
* Store thumbnails in db table local_image

* fmt
2024-03-08 10:17:26 -05:00
Nutomic bc2e75d5a3
Add @dullbananas to codeowners (#4521) 2024-03-08 10:16:05 -05:00
battmdpkq f228f9d7a9
fix some typos (#4519)
Signed-off-by: battmdpkq <cmaker@163.com>
2024-03-08 10:38:20 +01:00
Nutomic fed6b61eaf
Upgrade apub lib, correct webfinger content-type (#4498)
* Upgrade apub lib, correct webfinger content-type

* fmt

* fix test by avoiding network fetch
2024-03-06 11:21:46 -05:00
Raphael Lullis bdabc5e827
Add curl to base images (#4510)
Having a tool like curl or wget can help us to run healthchecks on
docker-based deployments more easily. This commit adds curl to the
list of deb packages that are installed as external dependencies
2024-03-06 11:15:13 +01:00
Nutomic 97e2dbdfd7
Update contact section in readme (#4508)
* Update contact section in readme

* Update README.md

* Update README.md
2024-03-05 13:07:52 -05:00
Nutomic 22c2e20fc5
Remove contributing.md (#4507) 2024-03-05 13:07:20 -05:00
Dessalines 6778279bb6
When purging a federated user, federate local community removals. (#4505) 2024-03-05 09:31:04 -05:00
Nutomic 1ad9a211c9
Improve contributing instructions (#4504) 2024-03-05 09:14:15 -05:00
Nutomic 157378b4c9
Clear text of deleted/removed comments (#4503) 2024-03-05 08:52:35 -05:00
Dessalines 7f9950fe85
Add alt_text for posts. Fixes #1086 (#4477)
* Add alt_text for posts. Fixes #1086

* Moving alt_text to attachment name.

* Cleaning up mod action line.

* Addressing PR comments

* Addressing PR comments.

* Fixing clones.
2024-03-05 11:34:57 +01:00
Dessalines 52155c74cb
View report history for a post or comment. Fixes #4190 (#4492) 2024-03-05 11:31:40 +01:00
SleeplessOne1917 36ad1868b3
Make SiteAggregates derive Copy and Hash (#4501)
Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-03-05 11:14:12 +01:00
Dessalines 65da4e7dbd
View mod history for a post or comment. Fixes #4162 (#4491) 2024-03-04 11:42:25 -05:00
Nutomic 3c358e5b0b
Mods and admins can comment in locked posts (fixes #4116) (#4488)
* Mods and admins can comment in locked posts (fixes #4116)

* fmt

* fix

* fix test
2024-03-04 09:15:21 -05:00
Dessalines eb1245bceb
When using saved_only, sort posts / comments by the saved publish time, not the item creation time (#4479)
* Work on saved selection.

* Using single value for join.

* Removing unecessary check.

* Remove saved_only pointless block.
2024-03-04 14:19:51 +01:00
Dessalines 7eec8714d7
When site banning a federated user, also remove their content from our local communities. (#4464)
* When banning a federated user, also remove their content from our local
communities.

- This works by:
  - Before a site ban, find all posts and comments to local communities
  - Send a federated community ban action for each local comm.
  - This also removes their content in the apub receive code.
- Adding back in federated community ban api tests.
- Adding in two more api tests for site bans.
- Fixes #4118

* Add local community ban, and nonlocal person check.

* Ignoring errors.

* Move local check into function.

* Addressing PR comments 2
2024-03-01 13:45:06 -05:00
Nutomic 08b01a377d
Support listing type for person (fixes #4146) (#4487)
* Support listing type for person (fixes #4146)

* add test
2024-03-01 11:53:20 -05:00
Nutomic c5e54a318a
Store password reset token after email successfully sent (fixes #3757) (#4489) 2024-03-01 11:32:59 -05:00
Nutomic a7fa075e8c
Make logs less verbose (fixes #3627) (#4490) 2024-03-01 11:32:13 -05:00
Nutomic c895e57086
Remove unneeded error "last successful id is higher than latest id" (fixes #4363) (#4486) 2024-03-01 11:31:37 -05:00
Dessalines e1b26897be
Add nlnet grant line in readme. (#4484) 2024-03-01 11:22:53 -05:00
Dessalines 87b577467b
Adding ability to hide posts. (#4480)
* Adding ability to hide posts.

- Adds an post/hide API route.
- Adds a `show_hidden` (default false) to `GetPosts`.
- Adds a `hidden` field to `PostView`.
- Removes the single `post_id` from MarkPostAsRead.
- Fixes #1403

* Add a check to make sure hidden field is true.

* Fixing test.

* Add back semicolon
2024-02-29 10:42:34 -05:00
Nutomic 6d815db375
Require verified email to reset password (#4482) 2024-02-29 09:12:45 -05:00
Nutomic 328a48c9f5
Remove error-type feature from lemmy-utils (#4474)
* Remove error-type feature from lemmy-utils

* fixes

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
2024-02-27 11:13:52 -05:00
Nutomic ab4deaa49a
Add api test for synchronizing featured posts (ref #4475) (#4476)
* Correctly synchronize collection of community featured posts (fixes #3867)

* Add api test for synchronizing featured posts (ref #4475)

* prettier
2024-02-27 09:11:41 -05:00
Dessalines e01ea32928
Fix doctype check issue for metadata fetching. Fixes #4468 (#4472)
* Fix doctype check issue for metadata fetching. Fixes #4468

* Change warn to info.
2024-02-26 10:24:09 -05:00
Dessalines f3d48f2c2c
Adding some recommended fixes from nightly clippy. (#4473) 2024-02-26 09:47:10 -05:00
Nutomic 7316dd281a
Correctly synchronize collection of community featured posts (fixes #3867) (#4475) 2024-02-26 09:45:23 -05:00
Richard Schwab 80bfd23b4d
Ensure rustfmt is installed in cargo fmt CI job (#4466) 2024-02-26 11:22:22 +01:00
SleeplessOne1917 f42420809b
Expose LemmyErrorType in lemmy_api_common (#4439)
* Expose LemmyErrorType in lemmy_api_common

* Make conditional compilation gates for utils

* Make it so api_common doesn't pull in unnecessary deps

* Make error type non exhaustive

* Fix formatting

* Format toml

* Add some convenience derives to LemmyError

* Simplify features

* Fix CI compile error

---------

Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-02-24 19:54:27 -05:00
dullbananas f56b84615c
Move DbUrl trait impls to newtypes.rs (#4463)
* Move DbUrl trait impls to newtypes.rs

* Update utils.rs
2024-02-19 12:41:28 -05:00
dullbananas d79502dff3
Escape backslashes in fuzzy_search (#4462)
* Escape backslashes in fuzzy_search

* Update utils.rs
2024-02-18 09:12:56 -05:00
dullbananas ae62ef2b7e
Ignore expired bans in CommentReportView::read, just like in CommentReportQuery::list (#4457)
* Update comment_report_view.rs

* Update comment_report_view.rs

* Update comment_report_view.rs

* Update comment_report_view.rs
2024-02-18 09:12:12 -05:00
SleeplessOne1917 39345466da
Make it so the signed in user can see if they're banned from a community (#4458)
* Make it so the signed in user can see if they're banned from a community

* Use more appropriate field name

---------

Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-02-18 09:09:46 -05:00
Dessalines 5d551e6da5
Adding an instance-level default_sort_type (#4454)
* Adding an instance-level default_sort_type

- Fixes #3796

* Fixing comment.

* Put user sort before site sort.
2024-02-16 09:36:46 -05:00
Nutomic ffcf415cac
Dont log db url on connection error (fixes #4453) (#4456)
* Dont log db url on connection error (fixes #4453)

* remove format
2024-02-16 08:50:06 -05:00
Nutomic 86b44c2a4d
Add site.content_warning, local_site.default_post_listing_mode (#4393)
* Include local_site.content_warning setting for showing nsfw by default

* Add community setting `only_followers_can_vote`

* clippy

* add auto_expand_images site setting

* cleanup

* add missing api params

* postquery/communityquery changes

* clippy

* change error

* replace auto_expand_images with default_site_post_listing_mode

* change post/community query params

* get rid of only_followers_can_vote

* machete

* fix

* clippy

* revert remaining vote changes

* remove dead code

* remove unused var

* fmt
2024-02-16 07:24:35 -05:00
Dessalines a3bf2f1cf1
Auto resolve reports on removing a comment or post. Fixes #4390 (#4402)
* Automatically resolve report when post/comment is removed (#3850)

* Automatically resolve report when post/comment is removed

* also handle apub removes

* Removing auto-resolve report triggers.

* Dont allow creating reports for deleted / removed items.

* Running pgformat.

* Fixing test.

* Addressing PR comments.

* Forgot comment report.

---------

Co-authored-by: Nutomic <me@nutomic.com>
2024-02-15 08:52:04 -05:00
Dessalines 890565ca14
Deleting denied local_users older than a week. Fixes #4434 (#4448)
* Deleting denied local_users older than a week. Fixes #4434

* Addressing PR comments.

* Upping rust to 1.76

* Delete the person rows also.
2024-02-15 07:50:53 -05:00
Dessalines 3f7cc07b02
Adding ability to specify a custom post thumbnail. (#4425)
* Adding ability to specify a custom post thumbnail.

- Context: #4204

* Fixing ts-rs serialization.

* Fixing conversion, adding checks.

* Proxying custom_thumbnail. Fixed logic for update.

* Only generate metadata thumbnail is theres no custom thumbnail.
2024-02-15 10:42:23 +01:00
Dessalines 33989f5518
Blocking an instance also hides private messages from their users. (#4447)
* Blocking an instance also hides private messages from their users.

- Fixes #4444

* Separating private message tests.
2024-02-14 10:49:55 +01:00
Lcchy 8a6a86c1bb
Add support for RSS media enclosures in feeds (#4442)
* Add support for RSS media enclosures in feeds

* Use post.url_content_type
2024-02-13 10:46:46 +01:00
dullbananas 677d54ae57
Allow better query plans (#4424)
* Update utils.rs

* Create bind_if_some.rs

* limit connection age

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

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs

* Update utils.rs
2024-02-12 16:44:29 +01:00
SleeplessOne1917 300869d397
Make request models derive PartialEq, Eq, and Hash (#4443)
* Make request models derive PartialEq, Eq, and Hash

* Fix clippy error

---------

Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-02-11 00:32:14 -05:00
Dessalines 609de3e9e2
Remove front end size. Fixes #4437 (#4440) 2024-02-09 10:42:12 +01:00
Elara 3c5b1ac6dd
Use the Accept-Language header to set new users' language (#4435)
* Use the Accept-Language header to set new users' language

* Implement clippy suggestions

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-02-08 10:53:03 +01:00
dullbananas f631f43024
Run analyze in db_perf (#4420) 2024-02-07 22:37:44 -05:00
Dessalines 9367cbdb00
Upgrading from postgres 15 -> 16-alpine. (#4426)
- Includes an upgrade script.
- Fixes #4406
2024-02-07 11:21:02 +01:00
Dessalines 3647a46e86
Remove ansible tagging. (#4417)
- See https://github.com/LemmyNet/lemmy-ansible/issues/215
2024-01-31 10:46:13 +01:00
Elara 328d48ef7e
Remove invalid XML characters from RSS feeds (#4416)
* Remove all characters that are disallowed by XML

* Combine contiguous unicode ranges into one range
2024-01-30 15:55:45 +01:00
Nutomic a09027c4c0
Silence warnings from ts-rs (#4415)
* Silence warnings from ts-rs

https://github.com/Aleph-Alpha/ts-rs/issues/108

* fmt
2024-01-29 09:22:53 -05:00
Elara 0e9924a2b3
Add media:content thumbnail to RSS feed (#4413)
* Add media:content thumbnail to RSS feed

* Run formatter

* Add media namespace definition

* Add comment linking to media-rss documentation
2024-01-29 08:56:35 -05:00
Dessalines eb0dc2fda4
Moving from yarn to pnpm. (#4414)
* Moving from yarn to pnpm.

* Prettier check.
2024-01-29 11:38:39 +01:00
Dessalines 9a2fb8e7c2
Fix image_mode for docker lemmy.hjson (#4403) 2024-01-26 10:51:59 +01:00
dullbananas f481a607d0
Show server output if federation tests fail in CI (#4389)
* Show server output if federation tests fail in CI

* dummy failure

* Revert dummy failure

* Update private_message.rs

* Fix errors

* Update private_message.rs

* Update private_message.rs

* Update .woodpecker.yml

* correct exit code

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update .woodpecker.yml

* Update prepare-drone-federation-test.sh

* Update .woodpecker.yml

* Update prepare-drone-federation-test.sh

* Update .woodpecker.yml

* Update prepare-drone-federation-test.sh

* Update private_message.rs

* Update lib.rs

* Update lib.rs

* Update session_middleware.rs

* Update session_middleware.rs

* Update .woodpecker.yml
2024-01-26 10:38:15 +01:00
dullbananas ade1cb1495
Remove outdated comment from .woodpecker.yml (#4404) 2024-01-26 10:33:03 +01:00
Nutomic 0f414a95d5
Local only community (#4350)
* Add support for local only community (fixes #1576)

* add filters and tests to db views

* dont federate local only community

* test get apub community http

* tests

* more checks

* wip

* api test

* fix tests

* change community.local_only column to visibility enum
(for private communities)

* sql fmt

* rename vars

* clippy

* fix tests

* update lib

* review

* fix js client version

* update client
2024-01-25 11:04:25 -05:00
Dessalines 8cde452fca
Add a comment to clarify the ban expires field. (#4400)
* Add a comment to clarify the ban expires field.

* Add comment about simpler client implementation.

* Better language.
2024-01-25 16:45:42 +01:00
Nutomic dadf8f28f9
Send purges to federated instances (fixes #4119) (#4398)
* Send purges to federated instances (fixes #4119)

* clippy

* review

* remove unused function

* clippy
2024-01-25 09:24:09 -05:00
Nutomic e8a52d3a5c
Rewrite images to use local proxy (#4035)
* Add markdown rule to add rel=nofollow for all links

* Add markdown image rule to add local image proxy (fixes #1036)

* comments

* rewrite markdown image links working

* add comment

* perform markdown image processing in api/apub receivers

* clippy

* add db table to validate proxied links

* rewrite link fields for avatar, banner etc

* sql fmt

* proxy links received over federation

* add config option

* undo post.url rewriting, move http route definition

* add tests

* proxy images through pictrs

* testing

* cleanup request.rs file

* more cleanup (fixes #2611)

* include url content type when sending post over apub (fixes #2611)

* store post url content type in db

* should be media_type

* get rid of cache_remote_thumbnails setting, instead automatically
take thumbnail from federation data if available.

* fix tests

* add setting disable_external_link_previews

* federate post url as image depending on mime type

* change setting again

* machete

* invert

* support custom emoji

* clippy

* update defaults

* add image proxy test, fix test

* fix test

* clippy

* revert accidental changes

* address review

* clippy

* Markdown link rule-dess (#4356)

* Extracting opengraph_data to its own type.

* A few additions for markdown-link-rule.

---------

Co-authored-by: Nutomic <me@nutomic.com>

* fix setting

* use enum for image proxy setting

* fix test configs

* add config backwards compat

* clippy

* machete

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-01-25 09:22:11 -05:00
Dessalines 1782aafd10
Upgrading deps. (#4401) 2024-01-25 10:24:07 +01:00
dullbananas d8f9e8a64c
Post view: move cursor pagination to separate library, add backward pagination to PostQuery (#4320)
* stuff

* stuff

* crates.io

* Update up.sql

* Rerun federation tests

* Update post_view.rs

* Update post_view.rs

* Update up.sql

* Update utils.rs

* Fix precision loss

* Update up.sql

* Update down.sql

* remove unwrap

* Update post_view.rs

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-01-24 10:50:11 -05:00
dullbananas 759f6d8a9a
Better query plan viewing experience (#4285)
* 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

* replace get(0) with first()

* as_slice

* Update series.rs

* Update db_perf.sh

* Update and rename crates/db_schema/src/utils/series.rs to crates/db_perf/src/series.rs

* Update utils.rs

* Update main.rs

* Update main.rs

* Update .woodpecker.yml

* fmt main.rs

* Update .woodpecker.yml

* Instance::delete at end

* Update main.rs

* Update Cargo.toml

---------

Co-authored-by: Nutomic <me@nutomic.com>
2024-01-24 10:22:33 -05:00
İsmail Karslı 8670403a67
Add local_subscribers field to CommunityAggregates. Fixes #4144 (#4166)
* Add upload timeout to PictrsConfig

* Bad space 🤔

* Update PictrsConfig upload timeout to include units.

* Add local_subscribers field to CommunityAggregates
struct and schema

* sql format

* local_subscribers test

* fix local_subscribers test

* Revert "fix local_subscribers test"

This reverts commit 4bbac5ce4a.

* Revert "local_subscribers test"

This reverts commit 735107e1f7.

* Create trigger for local_subscribers

* Rename variable

* re-trigger ci

* re-trigger ci

* Add local_subscribers count to follow.spec.ts

* Rename local_subscribers to subscribers_local

* Add subscribers_local to community_aggregates

* added subscribers_local to the aggregate tests

* Check if person exists on community_follower trigger

* Delete community follows before deleting person

* Update lemmy-js-client in api_tests

* Refactor local_subscriber migration

* fix format

* Move migration files date to now

* Fix test to wait for aggregates to federate

* re-trigger ci

---------

Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
2024-01-24 10:22:05 -05:00
Dessalines 922ec7c2cd
Forgot to add skip_serializing_none to persondetailsres. (#4399) 2024-01-24 09:58:58 -05:00
jim-taylor-business f43d2eca70
make inner type pub so it can be read and created easily in other crates (#4397)
this is for use in SSR pagination in Lemmy-UI-Leptos
2024-01-24 09:03:31 -05:00
Dessalines 20fd4b5869
Clippy fixes for 1.77.0 nightly (#4395)
* A few 1.77.0-nightly clippy fixes.

* Dead code warnings.

* More fixes.
2024-01-24 10:34:09 +01:00
Dessalines eb56d9253c
Adding site to GetPersonDetails. Fixes #4373 (#4394)
* Adding site to GetPersonDetails. Fixes #4373

* Removing the conditioned site return.
2024-01-24 10:32:14 +01:00
SleeplessOne1917 4b4b99aa78
Allow community mods to see votes in addition to admins (#4392)
* Allow community mods to see votes in addition to admins

* Use Post instead of PostView

---------

Co-authored-by: SleeplessOne1917 <insomnia-void@protonmail.com>
2024-01-23 18:47:28 -05:00
Dessalines 2133bcea4e Version 0.19.3 2024-01-22 08:56:08 -05:00
Nutomic 0868910570
Add secondary sort by published date for post view (fixes #4383) (#4384) 2024-01-22 08:52:21 -05:00
Dessalines e78fe5a34c
Removing group from woodpecker, as its deprecated. (#4387)
* Removing group from woodpecker, as its deprecated.

* Removing meltwater drone cache.
2024-01-22 10:57:07 +01:00
428 changed files with 18069 additions and 8589 deletions

4
.github/CODEOWNERS vendored
View file

@ -1,3 +1,3 @@
* @Nutomic @dessalines @phiresky
* @Nutomic @dessalines @phiresky @dullbananas @SleeplessOne1917
crates/apub/ @Nutomic
migrations/ @dessalines @phiresky
migrations/ @dessalines @phiresky @dullbananas

View file

@ -20,6 +20,8 @@ body:
required: true
- label: Is this only a single bug? Do not put multiple bugs in one issue.
required: true
- label: Do you agree to follow the rules in our [Code of Conduct](https://join-lemmy.org/docs/code_of_conduct.html)?
required: true
- label: Is this a backend issue? Use the [lemmy-ui](https://github.com/LemmyNet/lemmy-ui) repo for UI / frontend issues.
required: true
- type: textarea

View file

@ -20,6 +20,8 @@ body:
required: true
- label: Is this a backend issue? Use the [lemmy-ui](https://github.com/LemmyNet/lemmy-ui) repo for UI / frontend issues.
required: true
- label: Do you agree to follow the rules in our [Code of Conduct](https://join-lemmy.org/docs/code_of_conduct.html)?
required: true
- type: textarea
id: problem
attributes:

View file

@ -2,33 +2,40 @@
# See https://github.com/woodpecker-ci/woodpecker/issues/1677
variables:
- &rust_image "rust:1.75"
- &rust_image "rust:1.77"
- &rust_nightly_image "rustlang/rust:nightly"
- &install_pnpm "corepack enable pnpm"
- &slow_check_paths
- path:
# rust source code
- "crates/**"
- "src/**"
- "**/Cargo.toml"
- "Cargo.lock"
# database migrations
- "migrations/**"
# typescript tests
- "api_tests/**"
# config files and scripts used by ci
- ".woodpecker.yml"
- ".rustfmt.toml"
- "scripts/update_config_defaults.sh"
- "diesel.toml"
- ".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
- event: pull_request
path:
include: [
# rust source code
"crates/**",
"src/**",
"**/Cargo.toml",
"Cargo.lock",
# database migrations
"migrations/**",
# typescript tests
"api_tests/**",
# config files and scripts used by ci
".woodpecker.yml",
".rustfmt.toml",
"scripts/update_config_defaults.sh",
"diesel.toml",
".gitmodules",
]
- install_binstall: &install_binstall
- 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_diesel_cli: &install_diesel_cli
- apt update && apt install -y lsb-release build-essential
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
- apt update && apt install -y postgresql-client-16
- cargo install diesel_cli --no-default-features --features postgres
- export PATH="$CARGO_HOME/bin:$PATH"
steps:
prepare_repo:
@ -37,79 +44,58 @@ steps:
- apk add git
- git submodule init
- git submodule update
when:
- event: [pull_request, tag]
prettier_check:
group: format
image: tmknom/prettier:3.0.0
commands:
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations'
- prettier -c . '!**/volumes' '!**/dist' '!target' '!**/translations' '!api_tests/pnpm-lock.yaml'
when:
- event: pull_request
toml_fmt:
group: format
image: tamasfe/taplo:0.8.1
commands:
- taplo format --check
when:
- event: pull_request
sql_fmt:
group: format
image: backplane/pgformatter:latest
image: backplane/pgformatter
commands:
- ./scripts/sql_format_check.sh
when:
- event: pull_request
cargo_fmt:
group: format
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
commands:
# need make existing toolchain available
- rustup component add rustfmt
- cargo +nightly fmt -- --check
when:
- event: pull_request
cargo_machete:
group: format
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:
- event: pull_request
ignored_files:
group: format
image: alpine:3
commands:
- apk add git
- IGNORED=$(git ls-files --cached -i --exclude-standard)
- if [[ "$IGNORED" ]]; then echo "Ignored files present:\n$IGNORED\n"; exit 1; fi
restore-cache:
image: meltwater/drone-cache:v1
pull: true
settings:
restore: true
endpoint:
from_secret: MINIO_ENDPOINT
access-key:
from_secret: MINIO_WRITE_USER
secret-key:
from_secret: MINIO_WRITE_PASSWORD
bucket:
from_secret: MINIO_BUCKET
region: us-east-1
cache_key: "rust-cache"
path-style: true
backend_operation_timeout: 30m
compression_level: 0
exit_code: true
mount:
- ".cargo_home"
- "target"
- "api_tests/node_modules"
secrets:
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
when: *slow_check_paths
when:
- event: pull_request
# make sure api builds with default features (used by other crates relying on lemmy api)
check_api_common_default_features:
@ -148,24 +134,27 @@ 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
check_db_perf_tool:
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
commands:
- diesel migration run
- diesel migration redo
# same as scripts/db_perf.sh but without creating a new database server
- export LEMMY_CONFIG_LOCATION=config/config.hjson
- cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1
when: *slow_check_paths
cargo_clippy:
@ -173,7 +162,6 @@ steps:
environment:
CARGO_HOME: .cargo_home
commands:
# when adding new clippy lints, make sure to also add them in scripts/lint.sh
- rustup component add clippy
- cargo clippy --workspace --tests --all-targets --features console -- -D warnings
when: *slow_check_paths
@ -188,7 +176,6 @@ steps:
when: *slow_check_paths
cargo_test:
group: tests
image: *rust_image
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmy
@ -199,48 +186,67 @@ steps:
- 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
# Dump schema to before.sqldump (PostgreSQL apt repo is used to prevent pg_dump version mismatch error)
- apt update && apt install -y lsb-release
- sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
- wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
- apt update && apt install -y postgresql-client-16
- 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:
group: tests
image: node:20-bookworm-slim
environment:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432
DO_WRITE_HOSTS_FILE: "1"
commands:
- *install_pnpm
- apt update && apt install -y bash curl postgresql-client
- bash api_tests/prepare-drone-federation-test.sh
- cd api_tests/
- yarn
- yarn api-test
- pnpm i
- pnpm api-test
when: *slow_check_paths
rebuild-cache:
image: meltwater/drone-cache:v1
pull: true
settings:
rebuild: true
endpoint:
from_secret: MINIO_ENDPOINT
access-key:
from_secret: MINIO_WRITE_USER
secret-key:
from_secret: MINIO_WRITE_PASSWORD
bucket:
from_secret: MINIO_BUCKET
cache_key: "rust-cache"
region: us-east-1
path-style: true
backend_operation_timeout: 60m
compression_level: 0
exit_code: true
mount:
- ".cargo_home"
- "target"
- "api_tests/node_modules"
secrets:
[MINIO_ENDPOINT, MINIO_WRITE_USER, MINIO_WRITE_PASSWORD, MINIO_BUCKET]
federation_tests_server_output:
image: alpine:3
commands:
# `|| true` prevents this step from appearing to fail if the server output files don't exist
- cat target/log/lemmy_*.out || true
- "# If you can't see all output, then use the download button"
when:
- event: push
branch: main
- event: pull_request
status: failure
publish_release_docker:
image: woodpeckerci/plugin-docker-buildx
@ -253,7 +259,7 @@ steps:
- RUST_RELEASE_MODE=release
tag: ${CI_COMMIT_TAG}
when:
event: tag
- event: tag
nightly_build:
image: woodpeckerci/plugin-docker-buildx
@ -266,20 +272,20 @@ steps:
- RUST_RELEASE_MODE=release
tag: dev
when:
event: cron
- event: cron
# using https://github.com/pksunkara/cargo-workspaces
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
- event: tag
notify_on_failure:
image: alpine:3
@ -287,7 +293,8 @@ steps:
- apk add curl
- "curl -d'Lemmy CI build failed: ${CI_PIPELINE_URL}' ntfy.sh/lemmy_drone_ci"
when:
status: [failure]
- event: [pull_request, tag]
status: failure
notify_on_tag_deploy:
image: alpine:3
@ -295,11 +302,11 @@ steps:
- apk add curl
- "curl -d'lemmy:${CI_COMMIT_TAG} deployed' ntfy.sh/lemmy_drone_ci"
when:
event: tag
- event: tag
services:
database:
image: postgres:15.2-alpine
image: postgres:16-alpine
environment:
POSTGRES_USER: lemmy
POSTGRES_PASSWORD: password

View file

@ -1,3 +0,0 @@
# Contributing
See [here](https://join-lemmy.org/docs/en/contributors/01-overview.html) for contributing Instructions.

1903
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
[workspace.package]
version = "0.19.3-rc.1"
version = "0.19.4-beta.7"
edition = "2021"
description = "A link aggregator for the fediverse"
license = "AGPL-3.0"
@ -37,6 +37,8 @@ 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",
@ -54,6 +56,7 @@ members = [
"crates/api_common",
"crates/apub",
"crates/utils",
"crates/db_perf",
"crates/db_schema",
"crates/db_views",
"crates/db_views_actor",
@ -85,25 +88,26 @@ unused_self = "deny"
unwrap_used = "deny"
[workspace.dependencies]
lemmy_api = { version = "=0.19.3-rc.1", path = "./crates/api" }
lemmy_api_crud = { version = "=0.19.3-rc.1", path = "./crates/api_crud" }
lemmy_apub = { version = "=0.19.3-rc.1", path = "./crates/apub" }
lemmy_utils = { version = "=0.19.3-rc.1", path = "./crates/utils" }
lemmy_db_schema = { version = "=0.19.3-rc.1", path = "./crates/db_schema" }
lemmy_api_common = { version = "=0.19.3-rc.1", path = "./crates/api_common" }
lemmy_routes = { version = "=0.19.3-rc.1", path = "./crates/routes" }
lemmy_db_views = { version = "=0.19.3-rc.1", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.3-rc.1", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.3-rc.1", path = "./crates/db_views_moderator" }
activitypub_federation = { version = "0.5.1-beta.1", default-features = false, features = [
lemmy_api = { version = "=0.19.4-beta.7", path = "./crates/api" }
lemmy_api_crud = { version = "=0.19.4-beta.7", path = "./crates/api_crud" }
lemmy_apub = { version = "=0.19.4-beta.7", path = "./crates/apub" }
lemmy_utils = { version = "=0.19.4-beta.7", path = "./crates/utils", default-features = false }
lemmy_db_schema = { version = "=0.19.4-beta.7", path = "./crates/db_schema" }
lemmy_api_common = { version = "=0.19.4-beta.7", path = "./crates/api_common" }
lemmy_routes = { version = "=0.19.4-beta.7", path = "./crates/routes" }
lemmy_db_views = { version = "=0.19.4-beta.7", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.4-beta.7", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.4-beta.7", path = "./crates/db_views_moderator" }
lemmy_federate = { version = "=0.19.4-beta.7", path = "./crates/federate" }
activitypub_federation = { version = "0.5.6", 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.193", features = ["derive"] }
serde_with = "3.4.0"
actix-web = { version = "4.4.0", default-features = false, features = [
serde = { version = "1.0.199", features = ["derive"] }
serde_with = "3.8.1"
actix-web = { version = "4.5.1", default-features = false, features = [
"macros",
"rustls",
"compress-brotli",
@ -112,50 +116,56 @@ actix-web = { version = "4.4.0", default-features = false, features = [
"cookies",
] }
tracing = "0.1.40"
tracing-actix-web = { version = "0.7.9", default-features = false }
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.22", features = ["json", "blocking", "gzip"] }
reqwest-middleware = "0.2.4"
reqwest-tracing = "0.4.6"
reqwest = { version = "0.11.27", features = ["json", "blocking", "gzip"] }
reqwest-middleware = "0.2.5"
reqwest-tracing = "0.4.8"
clokwerk = "0.4.0"
doku = { version = "0.21.1", features = ["url-2"] }
bcrypt = "0.15.0"
chrono = { version = "0.4.31", features = ["serde"], default-features = false }
serde_json = { version = "1.0.108", features = ["preserve_order"] }
base64 = "0.21.5"
uuid = { version = "1.6.1", features = ["serde", "v4"] }
async-trait = "0.1.74"
bcrypt = "0.15.1"
chrono = { version = "0.4.38", features = ["serde"], default-features = false }
serde_json = { version = "1.0.116", features = ["preserve_order"] }
base64 = "0.22.1"
uuid = { version = "1.8.0", features = ["serde", "v4"] }
async-trait = "0.1.80"
captcha = "0.0.9"
anyhow = { version = "1.0.75", features = [
anyhow = { version = "1.0.82", features = [
"backtrace",
] } # backtrace is on by default on nightly, but not stable rust
diesel_ltree = "0.3.0"
typed-builder = "0.18.0"
diesel_ltree = "0.3.1"
typed-builder = "0.18.2"
serial_test = "2.0.0"
tokio = { version = "1.35.0", features = ["full"] }
regex = "1.10.2"
tokio = { version = "1.37.0", features = ["full"] }
regex = "1.10.4"
once_cell = "1.19.0"
diesel-derive-newtype = "2.1.0"
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.0"
futures = "0.3.29"
http = "0.2.11"
percent-encoding = "2.3.1"
itertools = "0.12.1"
futures = "0.3.30"
http = "0.2.12"
rosetta-i18n = "0.1.3"
opentelemetry = { version = "0.19.0", features = ["rt-tokio"] }
tracing-opentelemetry = { version = "0.19.0" }
ts-rs = { version = "7.0.0", features = ["serde-compat", "chrono-impl"] }
rustls = { version = "0.21.10", features = ["dangerous_configuration"] }
futures-util = "0.3.29"
ts-rs = { version = "7.1.1", features = [
"serde-compat",
"chrono-impl",
"no-serde-warnings",
] }
rustls = { version = "0.23.5", features = ["ring"] }
futures-util = "0.3.30"
tokio-postgres = "0.7.10"
tokio-postgres-rustls = "0.10.0"
tokio-postgres-rustls = "0.12.0"
urlencoding = "2.1.3"
enum-map = "2.7"
moka = { version = "0.12.1", features = ["future"] }
moka = { version = "0.12.7", features = ["future"] }
i-love-jesus = { version = "0.1.0" }
clap = { version = "4.5.4", features = ["derive"] }
pretty_assertions = "1.4.0"
[dependencies]
@ -166,7 +176,7 @@ lemmy_utils = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_api_common = { workspace = true }
lemmy_routes = { workspace = true }
lemmy_federate = { version = "0.19.3-rc.1", path = "crates/federate" }
lemmy_federate = { workspace = true }
activitypub_federation = { workspace = true }
diesel = { workspace = true }
diesel-async = { workspace = true }
@ -186,14 +196,14 @@ 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.0-rc.2", optional = true }
pict-rs = { version = "0.5.13", optional = true }
tokio.workspace = true
actix-cors = "0.6.5"
futures-util = { workspace = true }
chrono = { workspace = true }
prometheus = { version = "0.13.3", features = ["process"] }
serial_test = { workspace = true }
clap = { version = "4.4.11", features = ["derive"] }
clap = { workspace = true }
actix-web-prom = "0.7.0"
[dev-dependencies]

View file

@ -107,7 +107,6 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins
- NSFW post / community support.
- High performance.
- Server is written in rust.
- Front end is `~80kB` gzipped.
- Supports arm64 / Raspberry Pi.
## Installation
@ -122,6 +121,8 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins
Lemmy is free, open-source software, meaning no advertising, monetizing, or venture capital, ever. Your donations directly support full-time development of the project.
Lemmy is made possible by a generous grant from the [NLnet foundation](https://nlnet.nl/).
- [Support on Liberapay](https://liberapay.com/Lemmy).
- [Support on Patreon](https://www.patreon.com/dessalines).
- [Support on OpenCollective](https://opencollective.com/lemmy).
@ -132,21 +133,25 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent
- bitcoin: `1Hefs7miXS5ff5Ck5xvmjKjXf5242KzRtK`
- ethereum: `0x400c96c96acbC6E7B3B43B1dc1BB446540a88A01`
- monero: `41taVyY6e1xApqKyMVDRVxJ76sPkfZhALLTjRvVKpaAh2pBd4wv9RgYj1tSPrx8wc6iE1uWUfjtQdTmTy2FGMeChGVKPQuV`
- cardano: `addr1q858t89l2ym6xmrugjs0af9cslfwvnvsh2xxp6x4dcez7pf5tushkp4wl7zxfhm2djp6gq60dk4cmc7seaza5p3slx0sakjutm`
## Contributing
Read the following documentation to setup the development environment and start coding:
- [Contributing instructions](https://join-lemmy.org/docs/contributors/01-overview.html)
- [Docker Development](https://join-lemmy.org/docs/contributors/03-docker-development.html)
- [Local Development](https://join-lemmy.org/docs/contributors/02-local-development.html)
When working on an issue or pull request, you can comment with any questions you may have so that maintainers can answer them. You can also join the [Matrix Development Chat](https://matrix.to/#/#lemmydev:matrix.org) for general assistance.
### Translations
- If you want to help with translating, take a look at [Weblate](https://weblate.join-lemmy.org/projects/lemmy/). You can also help by [translating the documentation](https://github.com/LemmyNet/lemmy-docs#adding-a-new-language).
## Contact
## Community
- [Mastodon](https://mastodon.social/@LemmyDev)
- [Matrix Space](https://matrix.to/#/#lemmy-space:matrix.org)
- [Lemmy Forum](https://lemmy.ml/c/lemmy)
- [Lemmy Support Forum](https://lemmy.ml/c/lemmy_support)
## Code Mirrors

1
api_tests/.npmrc Normal file
View file

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

View file

@ -6,6 +6,7 @@
"repository": "https://github.com/LemmyNet/lemmy",
"author": "Dessalines",
"license": "AGPL-3.0",
"packageManager": "pnpm@9.0.6",
"scripts": {
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src && prettier --check 'src/**/*.ts'",
"fix": "prettier --write src && eslint --fix src",
@ -19,17 +20,17 @@
"api-test-image": "jest -i image.spec.ts"
},
"devDependencies": {
"@types/jest": "^29.5.11",
"@types/node": "^20.10.4",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.12.4",
"@typescript-eslint/eslint-plugin": "^7.5.0",
"@typescript-eslint/parser": "^7.5.0",
"download-file-sync": "^1.0.4",
"eslint": "^8.55.0",
"eslint-plugin-prettier": "^5.0.1",
"eslint": "^8.57.0",
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.5.0",
"lemmy-js-client": "0.19.0",
"prettier": "^3.1.1",
"lemmy-js-client": "0.19.4-alpha.18",
"prettier": "^3.2.5",
"ts-jest": "^29.1.0",
"typescript": "^5.3.3"
"typescript": "^5.4.4"
}
}

3449
api_tests/pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

View file

@ -3,13 +3,18 @@
# it is expected that this script is called by run-federation-test.sh script.
set -e
if [ -n "$LEMMY_LOG_LEVEL" ];
then
LEMMY_LOG_LEVEL=warn
fi
export RUST_BACKTRACE=1
export RUST_LOG="warn,lemmy_server=debug,lemmy_federate=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug"
#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 "pict-rs" ]; then
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
chmod +x api_tests/pict-rs
fi
@ -46,32 +51,35 @@ fi
echo "$PWD"
LOG_DIR=target/log
mkdir -p $LOG_DIR
echo "start alpha"
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_alpha.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_alpha" \
target/lemmy_server >/tmp/lemmy_alpha.out 2>&1 &
target/lemmy_server >$LOG_DIR/lemmy_alpha.out 2>&1 &
echo "start beta"
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_beta.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_beta" \
target/lemmy_server >/tmp/lemmy_beta.out 2>&1 &
target/lemmy_server >$LOG_DIR/lemmy_beta.out 2>&1 &
echo "start gamma"
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_gamma.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_gamma" \
target/lemmy_server >/tmp/lemmy_gamma.out 2>&1 &
target/lemmy_server >$LOG_DIR/lemmy_gamma.out 2>&1 &
echo "start delta"
# An instance with only an allowlist for beta
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_delta.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \
target/lemmy_server >/tmp/lemmy_delta.out 2>&1 &
target/lemmy_server >$LOG_DIR/lemmy_delta.out 2>&1 &
echo "start epsilon"
# An instance who has a blocklist, with lemmy-alpha blocked
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_epsilon" \
target/lemmy_server >/tmp/lemmy_epsilon.out 2>&1 &
target/lemmy_server >$LOG_DIR/lemmy_epsilon.out 2>&1 &
echo "wait for all instances to start"
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'lemmy-alpha:8541/api/v3/site')" != "200" ]]; do sleep 1; done

View file

@ -10,8 +10,8 @@ killall -s1 lemmy_server || true
./api_tests/prepare-drone-federation-test.sh
popd
yarn
yarn api-test || true
pnpm i
pnpm api-test || true
killall -s1 lemmy_server || true
killall -s1 pict-rs || true

View file

@ -53,9 +53,7 @@ beforeAll(async () => {
}
});
afterAll(() => {
unfollows();
});
afterAll(unfollows);
function assertCommentFederation(
commentOne?: CommentView,
@ -128,8 +126,9 @@ test("Update a comment", async () => {
});
test("Delete a comment", async () => {
let post = await createPost(alpha, betaCommunity!.community.id);
// creating a comment on alpha (remote from home of community)
let commentRes = await createComment(alpha, postOnAlphaRes.post_view.post.id);
let commentRes = await createComment(alpha, post.post_view.post.id);
// Find the comment on beta (home of community)
let betaComment = (
@ -157,6 +156,7 @@ test("Delete a comment", async () => {
commentRes.comment_view.comment.id,
);
expect(deleteCommentRes.comment_view.comment.deleted).toBe(true);
expect(deleteCommentRes.comment_view.comment.content).toBe("");
// Make sure that comment is undefined on beta
await waitUntil(
@ -255,6 +255,16 @@ test("Remove a comment from admin and community on different instance", async ()
betaComment.comment.id,
);
expect(removeCommentRes.comment_view.comment.removed).toBe(true);
expect(removeCommentRes.comment_view.comment.content).toBe("");
// Comment text is also hidden from list
let listComments = await getComments(
beta,
removeCommentRes.comment_view.post.id,
);
expect(listComments.comments.length).toBe(1);
expect(listComments.comments[0].comment.removed).toBe(true);
expect(listComments.comments[0].comment.content).toBe("");
// Make sure its not removed on alpha
let refetchedPostComments = await getComments(

View file

@ -29,13 +29,14 @@ import {
delta,
betaAllowedInstances,
searchPostLocal,
resolveBetaCommunity,
longDelay,
delay,
editCommunity,
unfollows,
} from "./shared";
import { EditSite } from "lemmy-js-client";
import { EditCommunity, EditSite } from "lemmy-js-client";
beforeAll(setupLogins);
afterAll(unfollows);
function assertCommunityFederation(
communityOne?: CommunityView,
@ -241,7 +242,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);
@ -451,7 +452,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,3 +512,24 @@ test("Fetch community, includes posts", async () => {
expect(post_listing.posts.length).toBe(1);
expect(post_listing.posts[0].post.ap_id).toBe(postRes.post_view.post.ap_id);
});
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 = {
community_id: communityRes.id,
visibility: "LocalOnly",
};
await editCommunity(alpha, form);
// cant resolve the community from another instance
await expect(
resolveCommunity(beta, communityRes.actor_id),
).rejects.toStrictEqual(Error("couldnt_find_object"));
// 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"),
);
});

View file

@ -5,40 +5,49 @@ import {
setupLogins,
resolveBetaCommunity,
followCommunity,
unfollowRemotes,
getSite,
waitUntil,
beta,
betaUrl,
registerUser,
unfollows,
} from "./shared";
beforeAll(setupLogins);
afterAll(() => {
unfollowRemotes(alpha);
});
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);
// 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);
});
test("Follow federated community", async () => {
let betaCommunity = (await resolveBetaCommunity(alpha)).community;
// It takes about 1 second for the community aggregates to federate
let betaCommunity = (
await waitUntil(
() => resolveBetaCommunity(alpha),
c =>
c.community?.counts.subscribers === 1 &&
c.community.counts.subscribers_local === 0,
)
).community;
if (!betaCommunity) {
throw "Missing beta community";
}
@ -55,10 +64,12 @@ 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);
// 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);
// Check it from local
let site = await getSite(alpha);
@ -83,4 +94,5 @@ test("Follow federated community", async () => {
// check that unfollow was federated
let communityOnBeta2 = await resolveBetaCommunity(beta);
expect(communityOnBeta2.community?.counts.subscribers).toBe(1);
expect(communityOnBeta2.community?.counts.subscribers_local).toBe(1);
});

View file

@ -7,31 +7,45 @@ import {
PurgePost,
} from "lemmy-js-client";
import {
alpha,
alphaImage,
alphaUrl,
beta,
betaUrl,
createCommunity,
createPost,
deleteAllImages,
epsilon,
followCommunity,
gamma,
getSite,
imageFetchLimit,
registerUser,
resolveBetaCommunity,
resolveCommunity,
resolvePost,
setupLogins,
unfollowRemotes,
waitForPost,
unfollows,
getPost,
waitUntil,
randomString,
createPostWithThumbnail,
} from "./shared";
const downloadFileSync = require("download-file-sync");
beforeAll(setupLogins);
afterAll(() => {
unfollowRemotes(alphaImage);
});
afterAll(unfollows);
test("Upload image and delete it", async () => {
// Upload test image. We use a simple string buffer as pictrs doesnt require an actual image
// 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 doesn't require an actual image
// in testing mode.
const upload_image = Buffer.from("test");
const upload_form: UploadImage = {
image: upload_image,
image: Buffer.from("test"),
};
const upload = await alphaImage.uploadImage(upload_form);
expect(upload.files![0].file).toBeDefined();
@ -43,6 +57,29 @@ test("Upload image and delete it", async () => {
const content = downloadFileSync(upload.url);
expect(content.length).toBeGreaterThan(0);
// Ensure that it comes back with the list_media endpoint
const listMediaRes = await alphaImage.listMedia();
expect(listMediaRes.images.length).toBe(1);
// Ensure that it also comes back with the admin all images
const listAllMediaRes = await alphaImage.listAllMedia({
limit: imageFetchLimit,
});
// This number comes from all the previous thumbnails fetched in other tests.
const previousThumbnails = 1;
expect(listAllMediaRes.images.length).toBe(previousThumbnails);
// The deleteUrl is a combination of the endpoint, delete token, and alias
let firstImage = listMediaRes.images[0];
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,
@ -54,15 +91,24 @@ test("Upload image and delete it", async () => {
// ensure that image is deleted
const content2 = downloadFileSync(upload.url);
expect(content2).toBe("");
// Ensure that it shows the image is deleted
const deletedListMediaRes = await alphaImage.listMedia();
expect(deletedListMediaRes.images.length).toBe(0);
// Ensure that the admin shows its deleted
const deletedListAllMediaRes = await alphaImage.listAllMedia({
limit: imageFetchLimit,
});
expect(deletedListAllMediaRes.images.length).toBe(previousThumbnails - 1);
});
test("Purge user, uploaded image removed", async () => {
let user = await registerUser(alphaImage, alphaUrl);
// upload test image
const upload_image = Buffer.from("test");
const upload_form: UploadImage = {
image: upload_image,
image: Buffer.from("test"),
};
const upload = await user.uploadImage(upload_form);
expect(upload.files![0].file).toBeDefined();
@ -76,10 +122,10 @@ test("Purge user, uploaded image removed", async () => {
// purge user
let site = await getSite(user);
const purge_form: PurgePerson = {
const purgeForm: PurgePerson = {
person_id: site.my_user!.local_user_view.person.id,
};
const delete_ = await alphaImage.purgePerson(purge_form);
const delete_ = await alphaImage.purgePerson(purgeForm);
expect(delete_.success).toBe(true);
// ensure that image is deleted
@ -91,9 +137,8 @@ test("Purge post, linked image removed", async () => {
let user = await registerUser(beta, betaUrl);
// upload test image
const upload_image = Buffer.from("test");
const upload_form: UploadImage = {
image: upload_image,
image: Buffer.from("test"),
};
const upload = await user.uploadImage(upload_form);
expect(upload.files![0].file).toBeDefined();
@ -114,13 +159,158 @@ test("Purge post, linked image removed", async () => {
expect(post.post_view.post.url).toBe(upload.url);
// purge post
const purge_form: PurgePost = {
const purgeForm: PurgePost = {
post_id: post.post_view.post.id,
};
const delete_ = await beta.purgePost(purge_form);
const delete_ = await beta.purgePost(purgeForm);
expect(delete_.success).toBe(true);
// ensure that image is deleted
const content2 = downloadFileSync(upload.url);
expect(content2).toBe("");
});
test("Images in remote post are proxied if setting enabled", async () => {
let user = await registerUser(beta, betaUrl);
let community = await createCommunity(gamma);
const upload_form: UploadImage = {
image: Buffer.from("test"),
};
const upload = await user.uploadImage(upload_form);
let post = await createPost(
gamma,
community.community_view.community.id,
upload.url,
"![](http://example.com/image2.png)",
);
expect(post.post_view.post).toBeDefined();
// remote image gets proxied after upload
expect(
post.post_view.post.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",
),
).toBeTruthy();
let epsilonPost = await resolvePost(epsilon, post.post_view.post);
expect(epsilonPost.post).toBeDefined();
// remote image gets proxied after federation
expect(
epsilonPost.post!.post.url?.startsWith(
"http://lemmy-epsilon:8581/api/v3/image_proxy?url",
),
).toBeTruthy();
expect(
epsilonPost.post!.post.body?.startsWith(
"![](http://lemmy-epsilon:8581/api/v3/image_proxy?url",
),
).toBeTruthy();
});
test("No image proxying if setting is disabled", async () => {
let user = await registerUser(beta, betaUrl);
let community = await createCommunity(alpha);
let betaCommunity = await resolveCommunity(
beta,
community.community_view.community.actor_id,
);
await followCommunity(beta, true, betaCommunity.community!.community.id);
const upload_form: UploadImage = {
image: Buffer.from("test"),
};
const upload = await user.uploadImage(upload_form);
let post = await createPost(
alpha,
community.community_view.community.id,
upload.url,
"![](http://example.com/image2.png)",
);
expect(post.post_view.post).toBeDefined();
// 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)");
let betaPost = await waitForPost(
beta,
post.post_view.post,
res => res?.post.alt_text != null,
);
expect(betaPost.post).toBeDefined();
// 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)");
// 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

@ -18,12 +18,12 @@ import {
resolveBetaCommunity,
createComment,
deletePost,
delay,
removePost,
getPost,
unfollowRemotes,
resolvePerson,
banPersonFromSite,
searchPostLocal,
followCommunity,
banPersonFromCommunity,
reportPost,
@ -37,9 +37,10 @@ import {
waitForPost,
alphaUrl,
loginUser,
createCommunity,
} from "./shared";
import { PostView } from "lemmy-js-client/dist/types/PostView";
import { ResolveObject } from "lemmy-js-client";
import { EditSite, ResolveObject } from "lemmy-js-client";
let betaCommunity: CommunityView | undefined;
@ -50,11 +51,20 @@ beforeAll(async () => {
await unfollows();
});
afterAll(() => {
unfollows();
});
afterAll(unfollows);
async function assertPostFederation(postOne: PostView, postTwo: PostView) {
// 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,
);
function assertPostFederation(postOne?: PostView, postTwo?: PostView) {
expect(postOne?.post.ap_id).toBe(postTwo?.post.ap_id);
expect(postOne?.post.name).toBe(postTwo?.post.name);
expect(postOne?.post.body).toBe(postTwo?.post.body);
@ -72,6 +82,16 @@ 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);
editSiteForm.allowed_instances = [];
editSiteForm.blocked_instances = ["lemmy-alpha"];
await epsilon.editSite(editSiteForm);
if (!betaCommunity) {
throw "Missing beta community";
}
@ -98,7 +118,7 @@ test("Create a post", async () => {
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(1);
assertPostFederation(betaPost, postRes.post_view);
await assertPostFederation(betaPost, postRes.post_view);
// Delta only follows beta, so it should not see an alpha ap_id
await expect(
@ -109,6 +129,12 @@ test("Create a post", async () => {
await expect(
resolvePost(epsilon, postRes.post_view.post),
).rejects.toStrictEqual(Error("couldnt_find_object"));
// remove added allow/blocklists
editSiteForm.allowed_instances = [];
editSiteForm.blocked_instances = [];
await delta.editSite(editSiteForm);
await epsilon.editSite(editSiteForm);
});
test("Create a post in a non-existent community", async () => {
@ -140,7 +166,7 @@ test("Unlike a post", async () => {
expect(betaPost?.community.local).toBe(true);
expect(betaPost?.creator.local).toBe(false);
expect(betaPost?.counts.score).toBe(0);
assertPostFederation(betaPost, postRes.post_view);
await assertPostFederation(betaPost, postRes.post_view);
});
test("Update a post", async () => {
@ -161,7 +187,7 @@ test("Update a post", async () => {
expect(betaPost.community.local).toBe(true);
expect(betaPost.creator.local).toBe(false);
expect(betaPost.post.name).toBe(updatedName);
assertPostFederation(betaPost, updatedPost.post_view);
await assertPostFederation(betaPost, updatedPost.post_view);
// Make sure lemmy beta cannot update the post
await expect(editPost(beta, betaPost.post)).rejects.toStrictEqual(
@ -203,12 +229,35 @@ test("Sticky a post", async () => {
if (!gammaPost) {
throw "Missing gamma post";
}
let gammaTrySticky = await featurePost(gamma, true, gammaPost.post);
// This has been failing occasionally
await featurePost(gamma, true, gammaPost.post);
let betaPost3 = (await resolvePost(beta, postRes.post_view.post)).post;
expect(gammaTrySticky.post_view.post.featured_community).toBe(true);
// expect(gammaTrySticky.post_view.post.featured_community).toBe(true);
expect(betaPost3?.post.featured_community).toBe(false);
});
test("Collection of featured posts gets federated", async () => {
// create a new community and feature a post
let community = await createCommunity(alpha);
let post = await createPost(alpha, community.community_view.community.id);
let featuredPost = await featurePost(alpha, true, post.post_view.post);
expect(featuredPost.post_view.post.featured_community).toBe(true);
// fetch the community, ensure that post is also fetched and marked as featured
let betaCommunity = await resolveCommunity(
beta,
community.community_view.community.actor_id,
);
expect(betaCommunity).toBeDefined();
const betaPost = await waitForPost(
beta,
post.post_view.post,
post => post?.post.featured_community === true,
);
expect(betaPost).toBeDefined();
});
test("Lock a post", async () => {
if (!betaCommunity) {
throw "Missing beta community";
@ -232,8 +281,10 @@ test("Lock a post", async () => {
post => !!post && post.post.locked,
);
// Try to make a new comment there, on alpha
await expect(createComment(alpha, alphaPost1.post.id)).rejects.toStrictEqual(
// Try to make a new comment there, on alpha. For this we need to create a normal
// user account because admins/mods can comment in locked posts.
let user = await registerUser(alpha, alphaUrl);
await expect(createComment(user, alphaPost1.post.id)).rejects.toStrictEqual(
Error("locked"),
);
@ -252,7 +303,7 @@ test("Lock a post", async () => {
expect(alphaPost2.post.locked).toBe(false);
// Try to create a new comment, on alpha
let commentAlpha = await createComment(alpha, alphaPost1.post.id);
let commentAlpha = await createComment(user, alphaPost1.post.id);
expect(commentAlpha).toBeDefined();
});
@ -287,7 +338,7 @@ test("Delete a post", async () => {
throw "Missing beta post 2";
}
expect(betaPost2.post.deleted).toBe(false);
assertPostFederation(betaPost2, undeletedPost.post_view);
await assertPostFederation(betaPost2, undeletedPost.post_view);
// Make sure lemmy beta cannot delete the post
await expect(deletePost(beta, true, betaPost2.post)).rejects.toStrictEqual(
@ -330,7 +381,7 @@ test("Remove a post from admin and community on different instance", async () =>
// Make sure lemmy beta sees post is undeleted
let betaPost2 = (await resolvePost(beta, postRes.post_view.post)).post;
expect(betaPost2?.post.removed).toBe(false);
assertPostFederation(betaPost2, undeletedPost.post_view);
await assertPostFederation(betaPost2!, undeletedPost.post_view);
});
test("Remove a post from admin and community on same instance", async () => {
@ -361,7 +412,7 @@ 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);
assertPostFederation(alphaPost.post_view, removePostRes.post_view);
await assertPostFederation(alphaPost.post_view, removePostRes.post_view);
// Undelete
let undeletedPost = await removePost(beta, false, betaPost.post);
@ -374,7 +425,7 @@ test("Remove a post from admin and community on same instance", async () => {
p => !!p && !p.post.removed,
);
expect(alphaPost2.post.removed).toBe(false);
assertPostFederation(alphaPost2, undeletedPost.post_view);
await assertPostFederation(alphaPost2, undeletedPost.post_view);
await unfollowRemotes(alpha);
});
@ -390,30 +441,34 @@ test("Search for a post", async () => {
expect(betaPost?.post.name).toBeDefined();
});
test("Enforce site ban for federated user", async () => {
test("Enforce site ban federation for local user", async () => {
if (!betaCommunity) {
throw "Missing beta community";
}
// create a test user
let alpha_user = await registerUser(alpha, alphaUrl);
let alphaUserPerson = (await getSite(alpha_user)).my_user?.local_user_view
let alphaUserHttp = await registerUser(alpha, alphaUrl);
let alphaUserPerson = (await getSite(alphaUserHttp)).my_user?.local_user_view
.person;
let alphaUserActorId = alphaUserPerson?.actor_id;
if (!alphaUserActorId) {
throw "Missing alpha user actor id";
}
expect(alphaUserActorId).toBeDefined();
let alphaPerson = (await resolvePerson(alpha_user, alphaUserActorId!)).person;
await followBeta(alphaUserHttp);
let alphaPerson = (await resolvePerson(alphaUserHttp, alphaUserActorId!))
.person;
if (!alphaPerson) {
throw "Missing alpha person";
}
expect(alphaPerson).toBeDefined();
// alpha makes post in beta community, it federates to beta instance
let postRes1 = await createPost(alpha_user, betaCommunity.community.id);
let postRes1 = await createPost(alphaUserHttp, betaCommunity.community.id);
let searchBeta1 = await waitForPost(beta, postRes1.post_view.post);
// ban alpha from its instance
// ban alpha from its own instance
let banAlpha = await banPersonFromSite(
alpha,
alphaPerson.person.id,
@ -430,10 +485,11 @@ test("Enforce site ban for federated user", async () => {
expect(alphaUserOnBeta1.person?.person.banned).toBe(true);
// existing alpha post should be removed on beta
await waitUntil(
let betaBanRes = await waitUntil(
() => getPost(beta, searchBeta1.post.id),
s => s.post_view.post.removed,
);
expect(betaBanRes.post_view.post.removed).toBe(true);
// Unban alpha
let unBanAlpha = await banPersonFromSite(
@ -449,21 +505,84 @@ test("Enforce site ban for federated user", async () => {
throw "Missing alpha person";
}
let newAlphaUserJwt = await loginUser(alpha, alphaUserPerson.name);
alpha_user.setHeaders({
alphaUserHttp.setHeaders({
Authorization: "Bearer " + newAlphaUserJwt.jwt ?? "",
});
// alpha makes new post in beta community, it federates
let postRes2 = await createPost(alpha_user, betaCommunity!.community.id);
let postRes2 = await createPost(alphaUserHttp, betaCommunity!.community.id);
await waitForPost(beta, postRes2.post_view.post);
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId!);
expect(alphaUserOnBeta2.person?.person.banned).toBe(false);
await unfollowRemotes(alpha);
});
test.skip("Enforce community ban for federated user", async () => {
test("Enforce site ban federation for federated user", async () => {
if (!betaCommunity) {
throw "Missing beta community";
}
// create a test user
let alphaUserHttp = await registerUser(alpha, alphaUrl);
let alphaUserPerson = (await getSite(alphaUserHttp)).my_user?.local_user_view
.person;
let alphaUserActorId = alphaUserPerson?.actor_id;
if (!alphaUserActorId) {
throw "Missing alpha user actor id";
}
expect(alphaUserActorId).toBeDefined();
await followBeta(alphaUserHttp);
let alphaUserOnBeta2 = await resolvePerson(beta, alphaUserActorId!);
expect(alphaUserOnBeta2.person?.person.banned).toBe(false);
if (!alphaUserOnBeta2.person) {
throw "Missing alpha person";
}
// alpha makes post in beta community, it federates to beta instance
let postRes1 = await createPost(alphaUserHttp, betaCommunity.community.id);
let searchBeta1 = await waitForPost(beta, postRes1.post_view.post);
expect(searchBeta1.post).toBeDefined();
// Now ban and remove their data from beta
let banAlphaOnBeta = await banPersonFromSite(
beta,
alphaUserOnBeta2.person.person.id,
true,
true,
);
expect(banAlphaOnBeta.banned).toBe(true);
// The beta site ban should NOT be federated to alpha
let alphaPerson2 = (await getSite(alphaUserHttp)).my_user!.local_user_view
.person;
expect(alphaPerson2.banned).toBe(false);
// existing alpha post should be removed on beta
let betaBanRes = await waitUntil(
() => getPost(beta, searchBeta1.post.id),
s => s.post_view.post.removed,
);
expect(betaBanRes.post_view.post.removed).toBe(true);
// existing alpha's post to the beta community should be removed on alpha
let alphaPostAfterRemoveOnBeta = await waitUntil(
() => getPost(alpha, postRes1.post_view.post.id),
s => s.post_view.post.removed,
);
expect(betaBanRes.post_view.post.removed).toBe(true);
expect(alphaPostAfterRemoveOnBeta.post_view.post.removed).toBe(true);
expect(
alphaPostAfterRemoveOnBeta.post_view.creator_banned_from_community,
).toBe(true);
await unfollowRemotes(alpha);
});
test("Enforce community ban for federated user", async () => {
if (!betaCommunity) {
throw "Missing beta community";
}
await followBeta(alpha);
let alphaShortname = `@lemmy_alpha@lemmy-alpha:8541`;
let alphaPerson = (await resolvePerson(beta, alphaShortname)).person;
if (!alphaPerson) {
@ -473,38 +592,46 @@ test.skip("Enforce community ban for federated user", async () => {
// make a post in beta, it goes through
let postRes1 = await createPost(alpha, betaCommunity.community.id);
let searchBeta1 = await searchPostLocal(beta, postRes1.post_view.post);
expect(searchBeta1.posts[0]).toBeDefined();
let searchBeta1 = await waitForPost(beta, postRes1.post_view.post);
expect(searchBeta1.post).toBeDefined();
// ban alpha from beta community
let banAlpha = await banPersonFromCommunity(
beta,
alphaPerson.person.id,
2,
searchBeta1.community.id,
true,
true,
);
expect(banAlpha.banned).toBe(true);
// ensure that the post by alpha got removed
await expect(getPost(alpha, searchBeta1.posts[0].post.id)).rejects.toBe(
Error("unknown"),
let removePostRes = await waitUntil(
() => getPost(alpha, postRes1.post_view.post.id),
s => s.post_view.post.removed,
);
expect(removePostRes.post_view.post.removed).toBe(true);
expect(removePostRes.post_view.creator_banned_from_community).toBe(true);
expect(removePostRes.community_view.banned_from_community).toBe(true);
// Alpha tries to make post on beta, but it fails because of ban
await expect(createPost(alpha, betaCommunity.community.id)).rejects.toBe(
Error("banned_from_community"),
);
await expect(
createPost(alpha, betaCommunity.community.id),
).rejects.toStrictEqual(Error("banned_from_community"));
// Unban alpha
let unBanAlpha = await banPersonFromCommunity(
beta,
alphaPerson.person.id,
2,
searchBeta1.community.id,
false,
false,
);
expect(unBanAlpha.banned).toBe(false);
// Need to re-follow the community
await followBeta(alpha);
let postRes3 = await createPost(alpha, betaCommunity.community.id);
expect(postRes3.post_view.post).toBeDefined();
expect(postRes3.post_view.community.local).toBe(false);
@ -512,57 +639,86 @@ test.skip("Enforce community ban for federated user", async () => {
expect(postRes3.post_view.counts.score).toBe(1);
// Make sure that post makes it to beta community
let searchBeta2 = await searchPostLocal(beta, postRes3.post_view.post);
expect(searchBeta2.posts[0]).toBeDefined();
let postRes4 = await waitForPost(beta, postRes3.post_view.post);
expect(postRes4.post).toBeDefined();
expect(postRes4.creator_banned_from_community).toBe(false);
await unfollowRemotes(alpha);
});
test("A and G subscribe to B (center) A posts, it gets announced to G", async () => {
if (!betaCommunity) {
throw "Missing beta community";
}
await followBeta(alpha);
let postRes = await createPost(alpha, betaCommunity.community.id);
expect(postRes.post_view.post).toBeDefined();
let betaPost = (await resolvePost(gamma, postRes.post_view.post)).post;
expect(betaPost?.post.name).toBeDefined();
await unfollowRemotes(alpha);
});
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";
}
let postRes = await createPost(beta, betaCommunity.community.id);
// Create post from alpha
let alphaCommunity = (await resolveBetaCommunity(alpha)).community!;
await followBeta(alpha);
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 () => {
await followBeta(alpha);
let alphaPost = await createPost(alpha, betaCommunity!.community.id);
expect(alphaPost.post_view.post).toBeDefined();
// Make sure that post is liked on beta
@ -583,4 +739,47 @@ test("Fetch post via redirect", async () => {
let gammaPost = await gamma.resolveObject(form);
expect(gammaPost).toBeDefined();
expect(gammaPost.post?.post.ap_id).toBe(alphaPost.post_view.post.ap_id);
await unfollowRemotes(alpha);
});
test("Block post that contains banned URL", async () => {
let editSiteForm: EditSite = {
blocked_urls: ["https://evil.com/"],
};
await epsilon.editSite(editSiteForm);
await delay(500);
if (!betaCommunity) {
throw "Missing beta community";
}
expect(
createPost(epsilon, betaCommunity.community.id, "https://evil.com"),
).rejects.toStrictEqual(Error("blocked_url"));
// Later tests need this to be empty
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

@ -8,9 +8,9 @@ import {
editPrivateMessage,
listPrivateMessages,
deletePrivateMessage,
unfollowRemotes,
waitUntil,
reportPrivateMessage,
unfollows,
} from "./shared";
let recipient_id: number;
@ -21,9 +21,7 @@ beforeAll(async () => {
recipient_id = 3;
});
afterAll(() => {
unfollowRemotes(alpha);
});
afterAll(unfollows);
test("Create a private message", async () => {
let pmRes = await createPrivateMessage(alpha, recipient_id);

View file

@ -5,6 +5,8 @@ import {
BlockInstanceResponse,
CommunityId,
CreatePrivateMessageReport,
DeleteImage,
EditCommunity,
GetReplies,
GetRepliesResponse,
GetUnreadCountResponse,
@ -78,6 +80,7 @@ import { GetPersonDetails } from "lemmy-js-client/dist/types/GetPersonDetails";
import { ListingType } from "lemmy-js-client/dist/types/ListingType";
export const fetchFunction = fetch;
export const imageFetchLimit = 50;
export let alphaUrl = "http://127.0.0.1:8541";
export let betaUrl = "http://127.0.0.1:8551";
@ -177,13 +180,6 @@ export async function setupLogins() {
];
await gamma.editSite(editSiteForm);
editSiteForm.allowed_instances = ["lemmy-beta"];
await delta.editSite(editSiteForm);
editSiteForm.allowed_instances = [];
editSiteForm.blocked_instances = ["lemmy-alpha"];
await epsilon.editSite(editSiteForm);
// Create the main alpha/beta communities
// Ignore thrown errors of duplicates
try {
@ -203,15 +199,19 @@ export async function createPost(
api: LemmyHttp,
community_id: number,
url: string = "https://example.com/",
body = randomString(10),
// 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 body = randomString(10);
let form: CreatePost = {
name,
url,
body,
alt_text,
community_id,
custom_thumbnail,
};
return api.createPost(form);
}
@ -228,6 +228,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,
@ -400,7 +415,7 @@ export async function banPersonFromSite(
let form: BanPerson = {
person_id,
ban,
remove_data: remove_data,
remove_data,
};
return api.banPerson(form);
}
@ -528,7 +543,7 @@ export async function likeComment(
export async function createCommunity(
api: LemmyHttp,
name_: string = randomString(5),
name_: string = randomString(10),
): Promise<CommunityResponse> {
let description = "a sample description";
let form: CreateCommunity = {
@ -539,6 +554,13 @@ export async function createCommunity(
return api.createCommunity(form);
}
export async function editCommunity(
api: LemmyHttp,
form: EditCommunity,
): Promise<CommunityResponse> {
return api.editCommunity(form);
}
export async function getCommunity(
api: LemmyHttp,
id: number,
@ -862,9 +884,25 @@ export function randomString(length: number): string {
return result;
}
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.local_image.pictrs_delete_token,
filename: image.local_image.pictrs_alias,
};
await api.deleteImage(form);
}
}
export async function unfollows() {
await Promise.all([
unfollowRemotes(alpha),
unfollowRemotes(beta),
unfollowRemotes(gamma),
unfollowRemotes(delta),
unfollowRemotes(epsilon),

View file

@ -19,11 +19,14 @@ import {
getPost,
getComments,
fetchFunction,
alphaImage,
unfollows,
} from "./shared";
import { LemmyHttp, SaveUserSettings } from "lemmy-js-client";
import { LemmyHttp, SaveUserSettings, UploadImage } from "lemmy-js-client";
import { GetPosts } from "lemmy-js-client/dist/types/GetPosts";
beforeAll(setupLogins);
afterAll(unfollows);
let apShortname: string;
@ -45,7 +48,7 @@ test("Create user", async () => {
if (!site.my_user) {
throw "Missing site user";
}
apShortname = `@${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
apShortname = `${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
});
test("Set some user settings, check that they are federated", async () => {
@ -68,7 +71,7 @@ test("Delete user", async () => {
let user = await registerUser(alpha, alphaUrl);
// make a local post and comment
let alphaCommunity = (await resolveCommunity(user, "!main@lemmy-alpha:8541"))
let alphaCommunity = (await resolveCommunity(user, "main@lemmy-alpha:8541"))
.community;
if (!alphaCommunity) {
throw "Missing alpha community";
@ -134,8 +137,59 @@ test("Create user with Arabic name", async () => {
if (!site.my_user) {
throw "Missing site user";
}
apShortname = `@${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
apShortname = `${site.my_user.local_user_view.person.name}@lemmy-alpha:8541`;
let alphaPerson = (await resolvePerson(alpha, apShortname)).person;
expect(alphaPerson).toBeDefined();
});
test("Create user with accept-language", async () => {
let lemmy_http = new LemmyHttp(alphaUrl, {
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Language#syntax
headers: { "Accept-Language": "fr-CH, en;q=0.8, de;q=0.7, *;q=0.5" },
});
let user = await registerUser(lemmy_http, alphaUrl);
let site = await getSite(user);
expect(site.my_user).toBeDefined();
expect(site.my_user?.local_user_view.local_user.interface_language).toBe(
"fr",
);
let langs = site.all_languages
.filter(a => site.my_user?.discussion_languages.includes(a.id))
.map(l => l.code);
// should have languages from accept header, as well as "undetermined"
// which is automatically enabled by backend
expect(langs).toStrictEqual(["und", "de", "en", "fr"]);
});
test("Set a new avatar, old avatar is deleted", async () => {
const listMediaRes = await alphaImage.listMedia();
expect(listMediaRes.images.length).toBe(0);
const upload_form1: UploadImage = {
image: Buffer.from("test1"),
};
const upload1 = await alphaImage.uploadImage(upload_form1);
expect(upload1.url).toBeDefined();
let form1 = {
avatar: upload1.url,
};
await saveUserSettings(alpha, form1);
const listMediaRes1 = await alphaImage.listMedia();
expect(listMediaRes1.images.length).toBe(1);
const upload_form2: UploadImage = {
image: Buffer.from("test2"),
};
const upload2 = await alphaImage.uploadImage(upload_form2);
expect(upload2.url).toBeDefined();
let form2 = {
avatar: upload1.url,
};
await saveUserSettings(alpha, form2);
// make sure only the new avatar is kept
const listMediaRes2 = await alphaImage.listMedia();
expect(listMediaRes2.images.length).toBe(1);
});

File diff suppressed because it is too large Load diff

86
cliff.toml Normal file
View file

@ -0,0 +1,86 @@
# git-cliff ~ configuration file
# https://git-cliff.org/docs/configuration
[remote.github]
owner = "LemmyNet"
repo = "lemmy"
# token = ""
[changelog]
# template for the changelog body
# https://keats.github.io/tera/docs/#introduction
body = """
## What's Changed
{%- if version %} in {{ version }}{%- endif -%}
{% for commit in commits %}
{% if commit.github.pr_title -%}
{%- set commit_message = commit.github.pr_title -%}
{%- else -%}
{%- set commit_message = commit.message -%}
{%- endif -%}
* {{ commit_message | split(pat="\n") | first | trim }}\
{% if commit.github.username %} by @{{ commit.github.username }}{%- endif -%}
{% if commit.github.pr_number %} in \
[#{{ commit.github.pr_number }}]({{ self::remote_url() }}/pull/{{ commit.github.pr_number }}) \
{%- endif %}
{%- endfor -%}
{% if github.contributors | filter(attribute="is_first_time", value=true) | length != 0 %}
{% raw %}\n{% endraw -%}
## New Contributors
{%- endif %}\
{% for contributor in github.contributors | filter(attribute="is_first_time", value=true) %}
* @{{ contributor.username }} made their first contribution
{%- if contributor.pr_number %} in \
[#{{ contributor.pr_number }}]({{ self::remote_url() }}/pull/{{ contributor.pr_number }}) \
{%- endif %}
{%- endfor -%}
{% if version %}
{% if previous.version %}
**Full Changelog**: {{ self::remote_url() }}/compare/{{ previous.version }}...{{ version }}
{% endif %}
{% else -%}
{% raw %}\n{% endraw %}
{% endif %}
{%- macro remote_url() -%}
https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}
{%- endmacro -%}
"""
# remove the leading and trailing whitespace from the template
trim = true
# changelog footer
footer = """
<!-- generated by git-cliff -->
"""
# postprocessors
postprocessors = []
[git]
# parse the commits based on https://www.conventionalcommits.org
conventional_commits = false
# filter out the commits that are not conventional
filter_unconventional = true
# process each line of a commit as an individual commit
split_commits = false
# regex for preprocessing the commit messages
commit_preprocessors = [
# remove issue numbers from commits
{ pattern = '\((\w+\s)?#([0-9]+)\)', replace = "" },
]
# 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
filter_commits = false
# regex for matching git tags
tag_pattern = "[0-9].*"
# regex for skipping tags
skip_tags = "beta|alpha"
# regex for ignoring tags
ignore_tags = "rc"
# sort the tags topologically
topo_order = false
# sort the commits inside sections by oldest/newest order
sort_commits = "newest"

View file

@ -36,22 +36,41 @@
# Maximum number of active sql connections
pool_size: 30
}
# Settings related to activitypub federation
# Pictrs image server configuration.
pictrs: {
# Address where pictrs is available (for image hosting)
url: "http://localhost:8080/"
# Set a custom pictrs API key. ( Required for deleting images )
api_key: "string"
# By default the thumbnails for external links are stored in pict-rs. This ensures that they
# can be reliably retrieved and can be resized using pict-rs APIs. However it also increases
# storage usage. In case this is disabled, the Opengraph image is directly returned as
# thumbnail.
# Backwards compatibility with 0.18.1. False is equivalent to `image_mode: None`, true is
# equivalent to `image_mode: StoreLinkPreviews`.
#
# In some countries it is forbidden to copy preview images from newspaper articles and only
# hotlinking is allowed. If that is the case for your instance, make sure that this setting is
# disabled.
# 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.
image_mode:
# Leave images unchanged, don't generate any local thumbnails for post urls. Instead the
# Opengraph image is directly returned as thumbnail
"None"
# or
# Generate thumbnails for external post urls and store them persistently in pict-rs. This
# ensures that they can be reliably retrieved and can be resized using pict-rs APIs. However
# it also increases storage usage.
#
# This is the default behaviour, and also matches Lemmy 0.18.
"StoreLinkPreviews"
# 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.
#
# Requires pict-rs 0.5
"ProxyAllImages"
# Timeout for uploading images to pictrs (in seconds)
upload_timeout: 30
}

View file

@ -34,8 +34,8 @@ tracing = { workspace = true }
chrono = { workspace = true }
url = { workspace = true }
wav = "1.0.0"
sitemap-rs = "0.2.0"
totp-rs = { version = "5.4.0", features = ["gen_secret", "otpauth"] }
sitemap-rs = "0.2.1"
totp-rs = { version = "5.5.1", features = ["gen_secret", "otpauth"] }
actix-web-httpauth = "0.8.1"
[dev-dependencies]

View file

@ -9,15 +9,17 @@ 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, None)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_user_action(
&local_user_view.person,
@ -54,7 +56,8 @@ pub async fn distinguish_comment(
data.comment_id,
Some(local_user_view.person.id),
)
.await?;
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
Ok(Json(CommentResponse {
comment_view,

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,9 @@ 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, None)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_user_action(
&local_user_view.person,
@ -46,9 +48,10 @@ 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
if let Ok(Some(local_recipient)) =
LocalUserView::read_person(&mut context.pool(), recipient_id).await
{
recipient_ids.push(local_recipient.local_user.id);
}
@ -75,12 +78,12 @@ pub async fn like_comment(
}
ActivityChannel::submit_activity(
SendActivityData::LikePostOrComment(
orig_comment.comment.ap_id,
local_user_view.person.clone(),
orig_comment.community,
data.score,
),
SendActivityData::LikePostOrComment {
object_id: orig_comment.comment.ap_id,
actor: local_user_view.person.clone(),
community: orig_comment.community,
score: data.score,
},
&context,
)
.await?;

View file

@ -2,10 +2,10 @@ use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
comment::{ListCommentLikes, ListCommentLikesResponse},
context::LemmyContext,
utils::is_admin,
utils::is_mod_or_admin,
};
use lemmy_db_views::structs::{LocalUserView, VoteView};
use lemmy_utils::error::LemmyError;
use lemmy_db_views::structs::{CommentView, LocalUserView, VoteView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
/// Lists likes for a comment
#[tracing::instrument(skip(context))]
@ -13,9 +13,21 @@ pub async fn list_comment_likes(
data: Query<ListCommentLikes>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListCommentLikesResponse>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
) -> LemmyResult<Json<ListCommentLikesResponse>> {
let comment_view = CommentView::read(
&mut context.pool(),
data.comment_id,
Some(local_user_view.person.id),
)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
comment_view.community.id,
)
.await?;
let comment_likes =
VoteView::list_for_comment(&mut context.pool(), data.comment_id, data.page, data.limit).await?;

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,
@ -33,7 +33,9 @@ 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(person_id))
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
Ok(Json(CommentResponse {
comment_view,

View file

@ -5,7 +5,11 @@ use lemmy_api_common::{
comment::{CommentReportResponse, CreateCommentReport},
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_user_action, send_new_report_email_to_admins},
utils::{
check_comment_deleted_or_removed,
check_community_user_action,
send_new_report_email_to_admins,
},
};
use lemmy_db_schema::{
source::{
@ -15,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))]
@ -23,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();
@ -31,7 +35,9 @@ 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, None)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
check_community_user_action(
&local_user_view.person,
@ -40,6 +46,9 @@ pub async fn create_comment_report(
)
.await?;
// Don't allow creating reports for removed / deleted comments
check_comment_deleted_or_removed(&comment_view.comment)?;
let report_form = CommentReportForm {
creator_id: person_id,
comment_id,
@ -51,8 +60,9 @@ pub async fn create_comment_report(
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let comment_report_view =
CommentReportView::read(&mut context.pool(), report.id, person_id).await?;
let comment_report_view = CommentReportView::read(&mut context.pool(), report.id, person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
// Email the admins
if local_site.reports_email_admins {
@ -66,12 +76,12 @@ pub async fn create_comment_report(
}
ActivityChannel::submit_activity(
SendActivityData::CreateReport(
comment_view.comment.ap_id.inner().clone(),
local_user_view.person,
comment_view.community,
data.reason.clone(),
),
SendActivityData::CreateReport {
object_id: comment_view.comment.ap_id.inner().clone(),
actor: local_user_view.person,
community: comment_view.community,
reason: data.reason.clone(),
},
&context,
)
.await?;

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,8 +14,9 @@ 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();
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
@ -24,6 +25,7 @@ pub async fn list_comment_reports(
let limit = data.limit;
let comment_reports = CommentReportQuery {
community_id,
comment_id,
unresolved_only,
page,
limit,

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,10 +14,12 @@ 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?;
let report = CommentReportView::read(&mut context.pool(), report_id, person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
let person_id = local_user_view.person.id;
check_community_mod_action(
@ -39,8 +41,9 @@ pub async fn resolve_comment_report(
}
let report_id = data.report_id;
let comment_report_view =
CommentReportView::read(&mut context.pool(), report_id, person_id).await?;
let comment_report_view = CommentReportView::read(&mut context.pool(), report_id, person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReport)?;
Ok(Json(CommentReportResponse {
comment_report_view,

View file

@ -15,14 +15,14 @@ use lemmy_db_schema::{
};
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 +33,23 @@ pub async fn add_mod_to_community(
&mut context.pool(),
)
.await?;
let community = Community::read(&mut context.pool(), community_id).await?;
let community = Community::read(&mut context.pool(), community_id)
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
// 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)?
let is_mod = CommunityModeratorView::is_community_moderator(
&mut context.pool(),
community.id,
local_user_view.person.id,
)
.await?;
if !is_mod {
Err(LemmyErrorType::NotAModerator)?
}
}
// Update in local database
@ -69,12 +83,12 @@ pub async fn add_mod_to_community(
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id).await?;
ActivityChannel::submit_activity(
SendActivityData::AddModToCommunity(
local_user_view.person,
data.community_id,
data.person_id,
data.added,
),
SendActivityData::AddModToCommunity {
moderator: local_user_view.person,
community_id: data.community_id,
target: data.person_id,
added: data.added,
},
&context,
)
.await?;

View file

@ -21,7 +21,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,7 +30,7 @@ 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)?;
@ -89,15 +89,17 @@ pub async fn ban_from_community(
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
ActivityChannel::submit_activity(
SendActivityData::BanFromCommunity(
local_user_view.person,
data.community_id,
person_view.person.clone(),
data.0.clone(),
),
SendActivityData::BanFromCommunity {
moderator: local_user_view.person,
community_id: data.community_id,
target: person_view.person.clone(),
data: data.0.clone(),
},
&context,
)
.await?;

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 {
@ -51,7 +51,9 @@ pub async fn block_community(
}
let community_view =
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false).await?;
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
ActivityChannel::submit_activity(
SendActivityData::FollowCommunity(

View file

@ -15,15 +15,17 @@ 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> {
let community = Community::read(&mut context.pool(), data.community_id).await?;
) -> LemmyResult<Json<CommunityResponse>> {
let community = Community::read(&mut context.pool(), data.community_id)
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let mut community_follower_form = CommunityFollowerForm {
community_id: community.id,
person_id: local_user_view.person.id,
@ -62,7 +64,10 @@ 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?;
CommunityView::read(&mut context.pool(), community_id, Some(person_id), false)
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
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?;
@ -79,8 +79,8 @@ pub async fn transfer_community(
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)?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let community_id = data.community_id;
let moderators = CommunityModeratorView::for_community(&mut context.pool(), community_id)

View file

@ -1,16 +1,32 @@
use activitypub_federation::config::Data;
use actix_web::{http::header::Header, HttpRequest};
use actix_web_httpauth::headers::authorization::{Authorization, Bearer};
use base64::{engine::general_purpose::STANDARD_NO_PAD as base64, Engine};
use captcha::Captcha;
use lemmy_api_common::{
claims::Claims,
community::BanFromCommunity,
context::LemmyContext,
utils::{check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_expire_time, check_user_valid, local_site_to_slur_regex, AUTH_COOKIE_NAME},
};
use lemmy_db_schema::{
source::{
community::{
CommunityFollower,
CommunityFollowerForm,
CommunityPersonBan,
CommunityPersonBanForm,
},
local_site::LocalSite,
moderator::{ModBanFromCommunity, ModBanFromCommunityForm},
person::Person,
},
traits::{Bannable, Crud, Followable},
};
use lemmy_db_schema::source::local_site::LocalSite;
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;
@ -28,7 +44,7 @@ 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
@ -62,7 +78,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)?;
@ -75,7 +91,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()))
@ -119,11 +135,7 @@ pub(crate) fn generate_totp_2fa_secret() -> String {
Secret::generate_secret().to_string()
}
pub(crate) fn build_totp_2fa(
site_name: &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()
@ -135,37 +147,130 @@ pub(crate) fn build_totp_2fa(
1,
30,
sec_bytes,
Some(site_name.to_string()),
Some(hostname.to_string()),
username.to_string(),
)
.with_lemmy_type(LemmyErrorType::CouldntGenerateTotp)
}
/// Site bans are only federated for local users.
/// This is a problem, because site-banning non-local users will still leave content
/// they've posted to our local communities, on other servers.
///
/// So when doing a site ban for a non-local user, you need to federate/send a
/// community ban for every local community they've participated in.
/// See https://github.com/LemmyNet/lemmy/issues/4118
#[tracing::instrument(skip_all)]
pub(crate) async fn ban_nonlocal_user_from_local_communities(
local_user_view: &LocalUserView,
target: &Person,
ban: bool,
reason: &Option<String>,
remove_data: &Option<bool>,
expires: &Option<i64>,
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
// Only run this code for federated users
if !target.local {
let ids = Person::list_local_community_ids(&mut context.pool(), target.id).await?;
for community_id in ids {
let expires_dt = check_expire_time(*expires)?;
// Ban / unban them from our local communities
let community_user_ban_form = CommunityPersonBanForm {
community_id,
person_id: target.id,
expires: Some(expires_dt),
};
if ban {
// Ignore all errors for these
CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form)
.await
.ok();
// Also unsubscribe them from the community, if they are subscribed
let community_follower_form = CommunityFollowerForm {
community_id,
person_id: target.id,
pending: false,
};
CommunityFollower::unfollow(&mut context.pool(), &community_follower_form)
.await
.ok();
} else {
CommunityPersonBan::unban(&mut context.pool(), &community_user_ban_form)
.await
.ok();
}
// Mod tables
let form = ModBanFromCommunityForm {
mod_person_id: local_user_view.person.id,
other_person_id: target.id,
community_id,
reason: reason.clone(),
banned: Some(ban),
expires: expires_dt,
};
ModBanFromCommunity::create(&mut context.pool(), &form).await?;
// Federate the ban from community
let ban_from_community = BanFromCommunity {
community_id,
person_id: target.id,
ban,
reason: reason.clone(),
remove_data: *remove_data,
expires: *expires,
};
ActivityChannel::submit_activity(
SendActivityData::BanFromCommunity {
moderator: local_user_view.person.clone(),
community_id,
target: target.clone(),
data: ban_from_community,
},
context,
)
.await?;
}
}
Ok(())
}
#[tracing::instrument(skip_all)]
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)?;
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id).await?;
let local_user_view = LocalUserView::read(&mut context.pool(), local_user_id)
.await?
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
check_user_valid(&local_user_view.person)?;
Ok(local_user_view)
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
#[allow(clippy::indexing_slicing)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use super::*;
#[test]
fn test_build_totp() {
let generated_secret = generate_totp_2fa_secret();
let totp = build_totp_2fa("lemmy", "my_name", &generated_secret);
let totp = build_totp_2fa("lemmy.ml", "my_name", &generated_secret);
assert!(totp.is_ok());
}
}

View file

@ -13,23 +13,23 @@ 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)?;
// 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)?;
.await?
.ok_or(LemmyErrorType::ObjectNotLocal)?;
let added_admin = LocalUser::update(
LocalUser::update(
&mut context.pool(),
added_local_user.local_user.id,
&LocalUserUpdateForm {
@ -43,7 +43,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

@ -1,3 +1,4 @@
use crate::ban_nonlocal_user_from_local_communities;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
@ -17,7 +18,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,
};
@ -26,7 +27,7 @@ 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)?;
@ -47,8 +48,8 @@ pub async fn ban_from_site(
.with_lemmy_type(LemmyErrorType::CouldntUpdateUser)?;
// if its a local user, invalidate logins
let local_user = LocalUserView::read_person(&mut context.pool(), data.person_id).await;
if let Ok(local_user) = local_user {
let local_user = LocalUserView::read_person(&mut context.pool(), person.id).await;
if let Ok(Some(local_user)) = local_user {
LoginToken::invalidate_all(&mut context.pool(), local_user.local_user.id).await?;
}
@ -61,7 +62,7 @@ pub async fn ban_from_site(
// Mod tables
let form = ModBanForm {
mod_person_id: local_user_view.person.id,
other_person_id: data.person_id,
other_person_id: person.id,
reason: data.reason.clone(),
banned: Some(data.ban),
expires,
@ -69,14 +70,30 @@ pub async fn ban_from_site(
ModBan::create(&mut context.pool(), &form).await?;
let person_view = PersonView::read(&mut context.pool(), data.person_id).await?;
let person_view = PersonView::read(&mut context.pool(), person.id)
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
ban_nonlocal_user_from_local_communities(
&local_user_view,
&person,
data.ban,
&data.reason,
&data.remove_data,
&data.expires,
&context,
)
.await?;
ActivityChannel::submit_activity(
SendActivityData::BanFromSite(
local_user_view.person,
person_view.person.clone(),
data.0.clone(),
),
SendActivityData::BanFromSite {
moderator: local_user_view.person,
banned_user: person_view.person.clone(),
reason: data.reason.clone(),
remove_data: data.remove_data,
ban: data.ban,
expires: data.expires,
},
&context,
)
.await?;

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,12 @@ 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()
.flatten();
if target_user.is_some_and(|t| t.local_user.admin) {
Err(LemmyErrorType::CantBlockAdmin)?
}
@ -45,7 +49,9 @@ pub async fn block_person(
.with_lemmy_type(LemmyErrorType::PersonBlockAlreadyExists)?;
}
let person_view = PersonView::read(&mut context.pool(), target_id).await?;
let person_view = PersonView::read(&mut context.pool(), target_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
Ok(Json(BlockPersonResponse {
person_view,
blocked: data.block,

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

View file

@ -10,18 +10,19 @@ 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)?;
.await?
.ok_or(LemmyErrorType::TokenNotFound)?
.local_user_id;
password_length_check(&data.password)?;

View file

@ -1,17 +1,10 @@
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_db_schema::{
source::local_user::{LocalUser, LocalUserUpdateForm},
traits::Crud,
};
use lemmy_api_common::{context::LemmyContext, person::GenerateTotpSecretResponse};
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyError, LemmyErrorType};
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.
@ -19,8 +12,10 @@ 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_view = SiteView::read_local(&mut context.pool())
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
if local_user_view.local_user.totp_2fa_enabled {
return Err(LemmyErrorType::TotpAlreadyEnabled)?;
@ -42,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

@ -2,12 +2,12 @@ use actix_web::web::{Data, Json};
use lemmy_api_common::context::LemmyContext;
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<Vec<LoginToken>>> {
let logins = LoginToken::list(&mut context.pool(), local_user_view.local_user.id).await?;
Ok(Json(logins))

View file

@ -0,0 +1,25 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
person::{ListMedia, ListMediaResponse},
};
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,
) -> LemmyResult<Json<ListMediaResponse>> {
let page = data.page;
let limit = data.limit;
let images = LocalImageView::get_all_paged_by_local_user_id(
&mut context.pool(),
local_user_view.local_user.id,
page,
limit,
)
.await?;
Ok(Json(ListMediaResponse { images }))
}

View file

@ -1,4 +1,4 @@
use crate::check_totp_2fa_valid;
use crate::{check_totp_2fa_valid, local_user::check_email_verified};
use actix_web::{
web::{Data, Json},
HttpRequest,
@ -16,22 +16,24 @@ use lemmy_db_schema::{
RegistrationMode,
};
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> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
) -> LemmyResult<Json<LoginResponse>> {
let site_view = SiteView::read_local(&mut context.pool())
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
// 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)?;
.await?
.ok_or(LemmyErrorType::IncorrectLogin)?;
// Verify the password
let valid: bool = verify(
@ -43,22 +45,18 @@ pub async fn login(
Err(LemmyErrorType::IncorrectLogin)?
}
check_user_valid(&local_user_view.person)?;
// Check if the user's email is verified if email verification is turned on
// However, skip checking verification if the user is an admin
if !local_user_view.local_user.admin
&& site_view.local_site.require_email_verification
&& !local_user_view.local_user.email_verified
{
Err(LemmyErrorType::EmailNotVerified)?
}
check_email_verified(&local_user_view, &site_view)?;
check_registration_application(&local_user_view, &site_view.local_site, &mut context.pool())
.await?;
// Check the totp if enabled
if local_user_view.local_user.totp_2fa_enabled {
check_totp_2fa_valid(&local_user_view, &data.totp_2fa_token, &site_view.site.name)?;
check_totp_2fa_valid(
&local_user_view,
&data.totp_2fa_token,
&context.settings().hostname,
)?;
}
let jwt = Claims::generate(local_user_view.local_user.id, req, &context).await?;
@ -74,7 +72,7 @@ async fn check_registration_application(
local_user_view: &LocalUserView,
local_site: &LocalSite,
pool: &mut DbPool<'_>,
) -> Result<(), LemmyError> {
) -> LemmyResult<()> {
if (local_site.registration_mode == RegistrationMode::RequireApplication
|| local_site.registration_mode == RegistrationMode::Closed)
&& !local_user_view.local_user.accepted_application
@ -83,7 +81,9 @@ async fn check_registration_application(
// 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?;
let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id)
.await?
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
if registration.admin_id.is_some() {
Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))?
} else {

View file

@ -1,3 +1,6 @@
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
pub mod add_admin;
pub mod ban_person;
pub mod block;
@ -7,6 +10,7 @@ pub mod generate_totp_secret;
pub mod get_captcha;
pub mod list_banned;
pub mod list_logins;
pub mod list_media;
pub mod login;
pub mod logout;
pub mod notifications;
@ -16,3 +20,15 @@ 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,16 +9,18 @@ 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?;
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
if local_user_view.person.id != read_person_mention.recipient_id {
Err(LemmyErrorType::CouldntUpdateComment)?
@ -37,7 +39,9 @@ pub async fn mark_person_mention_as_read(
let person_mention_id = read_person_mention.id;
let person_id = local_user_view.person.id;
let person_mention_view =
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)).await?;
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id))
.await?
.ok_or(LemmyErrorType::CouldntFindPersonMention)?;
Ok(Json(PersonMentionResponse {
person_mention_view,

View file

@ -9,16 +9,18 @@ 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?;
let read_comment_reply = CommentReply::read(&mut context.pool(), comment_reply_id)
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
if local_user_view.person.id != read_comment_reply.recipient_id {
Err(LemmyErrorType::CouldntUpdateComment)?
@ -38,7 +40,9 @@ pub async fn mark_reply_as_read(
let comment_reply_id = read_comment_reply.id;
let person_id = local_user_view.person.id;
let comment_reply_view =
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id)).await?;
CommentReplyView::read(&mut context.pool(), comment_reply_id, Some(person_id))
.await?
.ok_or(LemmyErrorType::CouldntFindCommentReply)?;
Ok(Json(CommentReplyResponse { comment_reply_view }))
}

View file

@ -2,13 +2,13 @@ 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?;

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,3 +1,4 @@
use crate::local_user::check_email_verified;
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
@ -6,8 +7,8 @@ use lemmy_api_common::{
SuccessResponse,
};
use lemmy_db_schema::source::password_reset_request::PasswordResetRequest;
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn reset_password(
@ -17,8 +18,8 @@ pub async fn reset_password(
// Fetch that email
let email = data.email.to_lowercase();
let local_user_view = LocalUserView::find_by_email(&mut context.pool(), &email)
.await
.with_lemmy_type(LemmyErrorType::IncorrectLogin)?;
.await?
.ok_or(LemmyErrorType::IncorrectLogin)?;
// Check for too many attempts (to limit potential abuse)
let recent_resets_count = PasswordResetRequest::get_recent_password_resets_count(
@ -29,6 +30,10 @@ pub async fn reset_password(
if recent_resets_count >= 3 {
Err(LemmyErrorType::PasswordResetLimitReached)?
}
let site_view = SiteView::read_local(&mut context.pool())
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
check_email_verified(&local_user_view, &site_view)?;
// Email the pure token to the user.
send_password_reset_email(&local_user_view, &mut context.pool(), context.settings()).await?;

View file

@ -1,36 +1,55 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
person::SaveUserSettings,
utils::send_verification_email,
request::replace_image,
utils::{
get_url_blocklist,
local_site_to_slur_regex,
process_markdown_opt,
proxy_image_link_opt_api,
send_verification_email,
},
SuccessResponse,
};
use lemmy_db_schema::{
source::{
actor_language::LocalUserLanguage,
local_user::{LocalUser, LocalUserUpdateForm},
local_user_vote_display_mode::{LocalUserVoteDisplayMode, LocalUserVoteDisplayModeUpdateForm},
person::{Person, PersonUpdateForm},
},
traits::Crud,
utils::{diesel_option_overwrite, diesel_option_overwrite_to_url},
utils::diesel_option_overwrite,
};
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> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
) -> LemmyResult<Json<SuccessResponse>> {
let site_view = SiteView::read_local(&mut context.pool())
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let avatar = diesel_option_overwrite_to_url(&data.avatar)?;
let banner = diesel_option_overwrite_to_url(&data.banner)?;
let bio = diesel_option_overwrite(data.bio.clone());
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?,
);
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 email_deref = data.email.as_deref().map(str::to_lowercase);
@ -39,7 +58,7 @@ pub async fn save_user_settings(
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 previous_email.deref() != email {
if LocalUser::is_email_taken(&mut context.pool(), email).await? {
return Err(LemmyErrorType::EmailAlreadyExists)?;
}
@ -123,11 +142,17 @@ 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 {
score: data.show_scores,
upvotes: data.show_upvotes,
downvotes: data.show_downvotes,
upvote_percentage: data.show_upvote_percentage,
};
LocalUserVoteDisplayMode::update(&mut context.pool(), local_user_id, &vote_display_modes_form)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -4,12 +4,9 @@ use lemmy_api_common::{
context::LemmyContext,
person::{UpdateTotp, UpdateTotpResponse},
};
use lemmy_db_schema::{
source::local_user::{LocalUser, LocalUserUpdateForm},
traits::Crud,
};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::LemmyError;
use lemmy_db_schema::source::local_user::{LocalUser, LocalUserUpdateForm};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult;
/// Enable or disable two-factor-authentication. The current setting is determined from
/// [LocalUser.totp_2fa_enabled].
@ -24,13 +21,11 @@ pub async fn update_totp(
data: Json<UpdateTotp>,
local_user_view: LocalUserView,
context: Data<LemmyContext>,
) -> Result<Json<UpdateTotpResponse>, LemmyError> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
) -> LemmyResult<Json<UpdateTotpResponse>> {
check_totp_2fa_valid(
&local_user_view,
&Some(data.totp_token.clone()),
&site_view.site.name,
&context.settings().hostname,
)?;
// toggle the 2fa setting

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

@ -9,23 +9,23 @@ use lemmy_db_schema::{
source::{
email_verification::EmailVerification,
local_user::{LocalUser, LocalUserUpdateForm},
person::Person,
},
traits::Crud,
RegistrationMode,
};
use lemmy_db_views::structs::SiteView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
use lemmy_db_views::structs::{LocalUserView, SiteView};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
pub async fn verify_email(
data: Json<VerifyEmail>,
context: Data<LemmyContext>,
) -> LemmyResult<Json<SuccessResponse>> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let site_view = SiteView::read_local(&mut context.pool())
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let token = data.token.clone();
let verification = EmailVerification::read_for_token(&mut context.pool(), &token)
.await
.with_lemmy_type(LemmyErrorType::TokenNotFound)?;
.await?
.ok_or(LemmyErrorType::TokenNotFound)?;
let form = LocalUserUpdateForm {
// necessary in case this is a new signup
@ -36,7 +36,7 @@ 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?;
@ -44,9 +44,16 @@ pub async fn verify_email(
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?;
let local_user = LocalUserView::read(&mut context.pool(), local_user_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
send_new_applicant_email_to_admins(
&local_user.person.name,
&mut context.pool(),
context.settings(),
)
.await?;
}
Ok(Json(SuccessResponse::default()))

View file

@ -16,16 +16,18 @@ use lemmy_db_schema::{
PostFeatureType,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
#[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?;
let orig_post = Post::read(&mut context.pool(), post_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_mod_action(
&local_user_view.person,

View file

@ -2,16 +2,16 @@ use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
post::{GetSiteMetadata, GetSiteMetadataResponse},
request::fetch_site_metadata,
request::fetch_link_metadata,
};
use lemmy_utils::error::LemmyError;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn get_link_metadata(
data: Query<GetSiteMetadata>,
context: Data<LemmyContext>,
) -> Result<Json<GetSiteMetadataResponse>, LemmyError> {
let metadata = fetch_site_metadata(context.client(), &data.url).await?;
) -> LemmyResult<Json<GetSiteMetadataResponse>> {
let metadata = fetch_link_metadata(&data.url, &context).await?;
Ok(Json(GetSiteMetadataResponse { metadata }))
}

View file

@ -0,0 +1,34 @@
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::{LemmyErrorExt, LemmyErrorType, LemmyResult, MAX_API_PARAM_ELEMENTS};
use std::collections::HashSet;
#[tracing::instrument(skip(context))]
pub async fn hide_post(
data: Json<HidePost>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<SuccessResponse>> {
let post_ids = HashSet::from_iter(data.post_ids.clone());
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
Err(LemmyErrorType::TooManyItems)?;
}
let person_id = local_user_view.person.id;
// Mark the post as hidden / unhidden
if data.hide {
PostHide::hide(&mut context.pool(), post_ids, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntHidePost)?;
} else {
PostHide::unhide(&mut context.pool(), post_ids, person_id)
.await
.with_lemmy_type(LemmyErrorType::CouldntHidePost)?;
}
Ok(Json(SuccessResponse::default()))
}

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
@ -38,7 +38,9 @@ pub async fn like_post(
// Check for a community ban
let post_id = data.post_id;
let post = Post::read(&mut context.pool(), post_id).await?;
let post = Post::read(&mut context.pool(), post_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_user_action(
&local_user_view.person,
@ -69,13 +71,17 @@ pub async fn like_post(
// 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?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
ActivityChannel::submit_activity(
SendActivityData::LikePostOrComment(
post.ap_id,
local_user_view.person.clone(),
Community::read(&mut context.pool(), post.community_id).await?,
data.score,
),
SendActivityData::LikePostOrComment {
object_id: post.ap_id,
actor: local_user_view.person.clone(),
community,
score: data.score,
},
&context,
)
.await?;

View file

@ -2,10 +2,11 @@ use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
post::{ListPostLikes, ListPostLikesResponse},
utils::is_admin,
utils::is_mod_or_admin,
};
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, LemmyErrorType};
/// Lists likes for a post
#[tracing::instrument(skip(context))]
@ -13,9 +14,16 @@ pub async fn list_post_likes(
data: Query<ListPostLikes>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<ListPostLikesResponse>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
) -> LemmyResult<Json<ListPostLikesResponse>> {
let post = Post::read(&mut context.pool(), data.post_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
is_mod_or_admin(
&mut context.pool(),
&local_user_view.person,
post.community_id,
)
.await?;
let post_likes =
VoteView::list_for_post(&mut context.pool(), data.post_id, data.page, data.limit).await?;

View file

@ -15,16 +15,18 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
#[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?;
let orig_post = Post::read(&mut context.pool(), post_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_mod_action(
&local_user_view.person,

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,15 +10,8 @@ pub async fn mark_post_as_read(
data: Json<MarkPostAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
let mut post_ids = HashSet::new();
if let Some(post_ids_) = &data.post_ids {
post_ids.extend(post_ids_.iter().cloned());
}
if let Some(post_id) = data.post_id {
post_ids.insert(post_id);
}
) -> LemmyResult<Json<SuccessResponse>> {
let post_ids = HashSet::from_iter(data.post_ids.clone());
if post_ids.len() > MAX_API_PARAM_ELEMENTS {
Err(LemmyErrorType::TooManyItems)?;

View file

@ -1,5 +1,6 @@
pub mod feature;
pub mod get_link_metadata;
pub mod hide;
pub mod like;
pub mod list_post_likes;
pub mod lock;

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,7 +34,9 @@ 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(person_id), false)
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
// Mark the post as read
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;

View file

@ -5,7 +5,11 @@ use lemmy_api_common::{
context::LemmyContext,
post::{CreatePostReport, PostReportResponse},
send_activity::{ActivityChannel, SendActivityData},
utils::{check_community_user_action, send_new_report_email_to_admins},
utils::{
check_community_user_action,
check_post_deleted_or_removed,
send_new_report_email_to_admins,
},
};
use lemmy_db_schema::{
source::{
@ -15,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))]
@ -23,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();
@ -31,7 +35,9 @@ pub async fn create_post_report(
let person_id = local_user_view.person.id;
let post_id = data.post_id;
let post_view = PostView::read(&mut context.pool(), post_id, None, false).await?;
let post_view = PostView::read(&mut context.pool(), post_id, None, false)
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
check_community_user_action(
&local_user_view.person,
@ -40,6 +46,8 @@ pub async fn create_post_report(
)
.await?;
check_post_deleted_or_removed(&post_view.post)?;
let report_form = PostReportForm {
creator_id: person_id,
post_id,
@ -53,7 +61,9 @@ pub async fn create_post_report(
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id).await?;
let post_report_view = PostReportView::read(&mut context.pool(), report.id, person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
// Email the admins
if local_site.reports_email_admins {
@ -67,12 +77,12 @@ pub async fn create_post_report(
}
ActivityChannel::submit_activity(
SendActivityData::CreateReport(
post_view.post.ap_id.inner().clone(),
local_user_view.person,
post_view.community,
data.reason.clone(),
),
SendActivityData::CreateReport {
object_id: post_view.post.ap_id.inner().clone(),
actor: local_user_view.person,
community: post_view.community,
reason: data.reason.clone(),
},
&context,
)
.await?;

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,8 +14,9 @@ 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();
check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?;
@ -24,6 +25,7 @@ pub async fn list_post_reports(
let limit = data.limit;
let post_reports = PostReportQuery {
community_id,
post_id,
unresolved_only,
page,
limit,

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,10 +14,12 @@ 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?;
let report = PostReportView::read(&mut context.pool(), report_id, person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
let person_id = local_user_view.person.id;
check_community_mod_action(
@ -38,7 +40,9 @@ pub async fn resolve_post_report(
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
}
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id).await?;
let post_report_view = PostReportView::read(&mut context.pool(), report_id, person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPostReport)?;
Ok(Json(PostReportResponse { post_report_view }))
}

View file

@ -8,17 +8,19 @@ 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?;
let orig_private_message = PrivateMessage::read(&mut context.pool(), private_message_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
if local_user_view.person.id != orig_private_message.recipient_id {
Err(LemmyErrorType::CouldntUpdatePrivateMessage)?
}
@ -37,7 +39,9 @@ pub async fn mark_pm_as_read(
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePrivateMessage)?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?;
let view = PrivateMessageView::read(&mut context.pool(), private_message_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
Ok(Json(PrivateMessageResponse {
private_message_view: view,
}))

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();
@ -29,7 +29,9 @@ pub async fn create_pm_report(
let person_id = local_user_view.person.id;
let private_message_id = data.private_message_id;
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id).await?;
let private_message = PrivateMessage::read(&mut context.pool(), private_message_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessage)?;
// Make sure that only the recipient of the private message can create a report
if person_id != private_message.recipient_id {
@ -47,8 +49,9 @@ pub async fn create_pm_report(
.await
.with_lemmy_type(LemmyErrorType::CouldntCreateReport)?;
let private_message_report_view =
PrivateMessageReportView::read(&mut context.pool(), report.id).await?;
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report.id)
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
// Email the admins
if local_site.reports_email_admins {

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;
@ -28,8 +28,9 @@ pub async fn resolve_pm_report(
.with_lemmy_type(LemmyErrorType::CouldntResolveReport)?;
}
let private_message_report_view =
PrivateMessageReportView::read(&mut context.pool(), report_id).await?;
let private_message_report_view = PrivateMessageReportView::read(&mut context.pool(), report_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPrivateMessageReport)?;
Ok(Json(PrivateMessageReportResponse {
private_message_report_view,

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

View file

@ -4,6 +4,7 @@ use lemmy_db_schema::{
source::{
actor_language::SiteLanguage,
language::Language,
local_site_url_blocklist::LocalSiteUrlBlocklist,
local_user::{LocalUser, LocalUserUpdateForm},
moderator::{ModAdd, ModAddForm},
tagline::Tagline,
@ -13,15 +14,15 @@ use lemmy_db_schema::{
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView};
use lemmy_db_views_actor::structs::PersonView;
use lemmy_utils::{
error::{LemmyError, LemmyErrorType},
version,
error::{LemmyErrorType, LemmyResult},
VERSION,
};
#[tracing::instrument(skip(context))]
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)
@ -54,7 +55,9 @@ pub async fn leave_admin(
ModAdd::create(&mut context.pool(), &form).await?;
// Reread site and admins
let site_view = SiteView::read_local(&mut context.pool()).await?;
let site_view = SiteView::read_local(&mut context.pool())
.await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?;
let admins = PersonView::admins(&mut context.pool()).await?;
let all_languages = Language::read_all(&mut context.pool()).await?;
@ -62,15 +65,17 @@ pub async fn leave_admin(
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 blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?;
Ok(Json(GetSiteResponse {
site_view,
admins,
version: version::VERSION.to_string(),
version: VERSION.to_string(),
my_user: None,
all_languages,
discussion_languages,
taglines,
custom_emojis,
blocked_urls,
}))
}

View file

@ -0,0 +1,23 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
person::{ListMedia, ListMediaResponse},
utils::is_admin,
};
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,
) -> LemmyResult<Json<ListMediaResponse>> {
// Only let admins view all media
is_admin(&local_user_view)?;
let page = data.page;
let limit = data.limit;
let images = LocalImageView::get_all(&mut context.pool(), page, limit).await?;
Ok(Json(ListMediaResponse { images }))
}

View file

@ -1,6 +1,7 @@
pub mod block;
pub mod federated_instances;
pub mod leave_admin;
pub mod list_all_media;
pub mod mod_log;
pub mod purge;
pub mod registration_applications;

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)?;
@ -55,10 +55,15 @@ pub async fn get_mod_log(
data.mod_person_id
};
let other_person_id = data.other_person_id;
let post_id = data.post_id;
let comment_id = data.comment_id;
let params = ModlogListParams {
community_id,
mod_person_id,
other_person_id,
post_id,
comment_id,
page: data.page,
limit: data.limit,
hide_modlog_names,

View file

@ -1,6 +1,8 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData},
site::PurgeComment,
utils::is_admin,
SuccessResponse,
@ -12,24 +14,26 @@ use lemmy_db_schema::{
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_db_views::structs::{CommentView, LocalUserView};
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
#[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
let comment = Comment::read(&mut context.pool(), comment_id).await?;
// Read the comment to get the post_id and community
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
let post_id = comment.post_id;
let post_id = comment_view.comment.post_id;
// TODO read comments for pictrs images and purge them
@ -41,8 +45,18 @@ pub async fn purge_comment(
reason: data.reason.clone(),
post_id,
};
AdminPurgeComment::create(&mut context.pool(), &form).await?;
ActivityChannel::submit_activity(
SendActivityData::RemoveComment {
comment: comment_view.comment,
moderator: local_user_view.person.clone(),
community: comment_view.community,
reason: data.reason.clone(),
},
&context,
)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -1,7 +1,9 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
request::purge_image_from_pictrs,
send_activity::{ActivityChannel, SendActivityData},
site::PurgeCommunity,
utils::{is_admin, purge_image_posts_for_community},
SuccessResponse,
@ -14,41 +16,51 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
#[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)?;
let community_id = data.community_id;
// Read the community to get its images
let community = Community::read(&mut context.pool(), community_id).await?;
let community = Community::read(&mut context.pool(), data.community_id)
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
if let Some(banner) = community.banner {
purge_image_from_pictrs(&banner, &context).await.ok();
if let Some(banner) = &community.banner {
purge_image_from_pictrs(banner, &context).await.ok();
}
if let Some(icon) = community.icon {
purge_image_from_pictrs(&icon, &context).await.ok();
if let Some(icon) = &community.icon {
purge_image_from_pictrs(icon, &context).await.ok();
}
purge_image_posts_for_community(community_id, &context).await?;
purge_image_posts_for_community(data.community_id, &context).await?;
Community::delete(&mut context.pool(), community_id).await?;
Community::delete(&mut context.pool(), data.community_id).await?;
// Mod tables
let form = AdminPurgeCommunityForm {
admin_person_id: local_user_view.person.id,
reason: data.reason.clone(),
};
AdminPurgeCommunity::create(&mut context.pool(), &form).await?;
ActivityChannel::submit_activity(
SendActivityData::RemoveCommunity {
moderator: local_user_view.person.clone(),
community,
reason: data.reason.clone(),
removed: true,
},
&context,
)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -1,51 +1,53 @@
use actix_web::web::{Data, Json};
use crate::ban_nonlocal_user_from_local_communities;
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
request::delete_image_from_pictrs,
send_activity::{ActivityChannel, SendActivityData},
site::PurgePerson,
utils::is_admin,
utils::{is_admin, purge_user_account},
SuccessResponse,
};
use lemmy_db_schema::{
source::{
image_upload::ImageUpload,
moderator::{AdminPurgePerson, AdminPurgePersonForm},
person::{Person, PersonUpdateForm},
},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
#[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)?;
// Read the person to get their images
let person_id = data.person_id;
if let Ok(local_user) = LocalUserView::read_person(&mut context.pool(), person_id).await {
let pictrs_uploads =
ImageUpload::get_all_by_local_user_id(&mut context.pool(), &local_user.local_user.id).await?;
for upload in pictrs_uploads {
delete_image_from_pictrs(&upload.pictrs_alias, &upload.pictrs_delete_token, &context)
.await
.ok();
}
}
let person = Person::read(&mut context.pool(), data.person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPerson)?;
ban_nonlocal_user_from_local_communities(
&local_user_view,
&person,
true,
&data.reason,
&Some(true),
&None,
&context,
)
.await?;
// Clear profile data.
Person::delete_account(&mut context.pool(), person_id).await?;
purge_user_account(data.person_id, &context).await?;
// Keep person record, but mark as banned to prevent login or refetching from home instance.
Person::update(
let person = Person::update(
&mut context.pool(),
person_id,
data.person_id,
&PersonUpdateForm {
banned: Some(true),
..Default::default()
@ -58,8 +60,20 @@ pub async fn purge_person(
admin_person_id: local_user_view.person.id,
reason: data.reason.clone(),
};
AdminPurgePerson::create(&mut context.pool(), &form).await?;
ActivityChannel::submit_activity(
SendActivityData::BanFromSite {
moderator: local_user_view.person,
banned_user: person,
reason: data.reason.clone(),
remove_data: Some(true),
ban: true,
expires: None,
},
&context,
)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -1,7 +1,9 @@
use actix_web::web::{Data, Json};
use activitypub_federation::config::Data;
use actix_web::web::Json;
use lemmy_api_common::{
context::LemmyContext,
request::purge_image_from_pictrs,
send_activity::{ActivityChannel, SendActivityData},
site::PurgePost,
utils::is_admin,
SuccessResponse,
@ -14,43 +16,51 @@ use lemmy_db_schema::{
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyError;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
#[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)?;
let post_id = data.post_id;
// Read the post to get the community_id
let post = Post::read(&mut context.pool(), post_id).await?;
let post = Post::read(&mut context.pool(), data.post_id)
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
// Purge image
if let Some(url) = post.url {
purge_image_from_pictrs(&url, &context).await.ok();
if let Some(url) = &post.url {
purge_image_from_pictrs(url, &context).await.ok();
}
// Purge thumbnail
if let Some(thumbnail_url) = post.thumbnail_url {
purge_image_from_pictrs(&thumbnail_url, &context).await.ok();
if let Some(thumbnail_url) = &post.thumbnail_url {
purge_image_from_pictrs(thumbnail_url, &context).await.ok();
}
let community_id = post.community_id;
Post::delete(&mut context.pool(), post_id).await?;
Post::delete(&mut context.pool(), data.post_id).await?;
// Mod tables
let form = AdminPurgePostForm {
admin_person_id: local_user_view.person.id,
reason: data.reason.clone(),
community_id,
community_id: post.community_id,
};
AdminPurgePost::create(&mut context.pool(), &form).await?;
ActivityChannel::submit_activity(
SendActivityData::RemovePost {
post,
moderator: local_user_view.person.clone(),
reason: data.reason.clone(),
removed: true,
},
&context,
)
.await?;
Ok(Json(SuccessResponse::default()))
}

View file

@ -13,13 +13,13 @@ use lemmy_db_schema::{
utils::diesel_option_overwrite,
};
use lemmy_db_views::structs::{LocalUserView, RegistrationApplicationView};
use lemmy_utils::error::LemmyError;
use lemmy_utils::{error::LemmyResult, LemmyErrorType};
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
@ -45,8 +45,9 @@ pub async fn approve_registration_application(
LocalUser::update(&mut context.pool(), approved_user_id, &local_user_form).await?;
if data.approve {
let approved_local_user_view =
LocalUserView::read(&mut context.pool(), approved_user_id).await?;
let approved_local_user_view = LocalUserView::read(&mut context.pool(), approved_user_id)
.await?
.ok_or(LemmyErrorType::CouldntFindLocalUser)?;
if approved_local_user_view.local_user.email.is_some() {
send_application_approved_email(&approved_local_user_view, context.settings()).await?;
@ -54,8 +55,9 @@ pub async fn approve_registration_application(
}
// Read the view
let registration_application =
RegistrationApplicationView::read(&mut context.pool(), app_id).await?;
let registration_application = RegistrationApplicationView::read(&mut context.pool(), app_id)
.await?
.ok_or(LemmyErrorType::CouldntFindRegistrationApplication)?;
Ok(Json(RegistrationApplicationResponse {
registration_application,

View file

@ -9,14 +9,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

@ -6,12 +6,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,8 +42,8 @@ pub async fn get_sitemap(context: Data<LemmyContext>) -> LemmyResult<HttpRespons
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
pub(crate) mod tests {
#![allow(clippy::unwrap_used)]
use crate::sitemap::generate_urlset;
use chrono::{DateTime, NaiveDate, Utc};

View file

@ -20,13 +20,12 @@ workspace = true
full = [
"tracing",
"rosetta-i18n",
"lemmy_utils",
"lemmy_db_views/full",
"lemmy_db_views_actor/full",
"lemmy_db_views_moderator/full",
"lemmy_utils/full",
"activitypub_federation",
"percent-encoding",
"encoding",
"encoding_rs",
"reqwest-middleware",
"webpage",
"ts-rs",
@ -37,6 +36,7 @@ full = [
"futures",
"once_cell",
"jsonwebtoken",
"mime",
]
[dependencies]
@ -44,7 +44,7 @@ lemmy_db_views = { workspace = true }
lemmy_db_views_moderator = { workspace = true }
lemmy_db_views_actor = { workspace = true }
lemmy_db_schema = { workspace = true }
lemmy_utils = { workspace = true, optional = true }
lemmy_utils = { workspace = true }
activitypub_federation = { workspace = true, optional = true }
serde = { workspace = true }
serde_with = { workspace = true }
@ -54,22 +54,25 @@ tracing = { workspace = true, optional = true }
reqwest-middleware = { workspace = true, optional = true }
regex = { workspace = true }
rosetta-i18n = { workspace = true, optional = true }
percent-encoding = { workspace = true, optional = true }
webpage = { version = "1.6", default-features = false, features = [
"serde",
], optional = true }
encoding = { version = "0.2.33", optional = true }
futures = { workspace = true, optional = true }
uuid = { workspace = true, optional = true }
tokio = { workspace = true, optional = true }
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 = [
"serde",
], optional = true }
encoding_rs = { version = "0.8.34", optional = true }
jsonwebtoken = { version = "8.3.0", optional = true }
# necessary for wasmt compilation
getrandom = { version = "0.2.11", features = ["js"] }
enum-map = { workspace = true }
getrandom = { version = "0.2.14", features = ["js"] }
[package.metadata.cargo-machete]
ignored = ["getrandom"]

View file

@ -19,15 +19,15 @@ 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},
LemmyErrorType,
};
pub async fn build_comment_response(
@ -35,9 +35,11 @@ pub async fn build_comment_response(
comment_id: CommentId,
local_user_view: Option<LocalUserView>,
recipient_ids: Vec<LocalUserId>,
) -> Result<CommentResponse, LemmyError> {
) -> LemmyResult<CommentResponse> {
let person_id = local_user_view.map(|l| l.person.id);
let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id).await?;
let comment_view = CommentView::read(&mut context.pool(), comment_id, person_id)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
Ok(CommentResponse {
comment_view,
recipient_ids,
@ -48,7 +50,7 @@ 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();
@ -59,7 +61,8 @@ pub async fn build_community_response(
Some(person_id),
is_mod_or_admin,
)
.await?;
.await?
.ok_or(LemmyErrorType::CouldntFindCommunity)?;
let discussion_languages = CommunityLanguage::read(&mut context.pool(), community_id).await?;
Ok(Json(CommunityResponse {
@ -73,7 +76,7 @@ pub async fn build_post_response(
community_id: CommunityId,
person: &Person,
post_id: PostId,
) -> Result<Json<PostResponse>, LemmyError> {
) -> LemmyResult<Json<PostResponse>> {
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), person, community_id)
.await
.is_ok();
@ -83,24 +86,30 @@ pub async fn build_post_response(
Some(person.id),
is_mod_or_admin,
)
.await?;
.await?
.ok_or(LemmyErrorType::CouldntFindPost)?;
Ok(Json(PostResponse { post_view }))
}
// TODO: this function is a mess and should be split up to handle email seperately
// TODO: this function is a mess and should be split up to handle email separately
#[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> {
) -> 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;
// Read the comment view to get extra info
let comment_view = CommentView::read(&mut context.pool(), comment_id, None)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
let comment = comment_view.comment;
let post = comment_view.post;
let community = comment_view.community;
// Send the local mentions
for mention in mentions
@ -109,15 +118,15 @@ pub async fn send_local_notifs(
{
let mention_name = mention.name.clone();
let user_view = LocalUserView::read_from_name(&mut context.pool(), &mention_name).await;
if let Ok(mention_user_view) = user_view {
if let Ok(Some(mention_user_view)) = user_view {
// TODO
// At some point, make it so you can't tag the parent creator either
// This can cause two notifications, one for reply and the other for mention
// 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,
};
@ -144,7 +153,9 @@ pub async fn send_local_notifs(
// Send comment_reply to the parent commenter / poster
if let Some(parent_comment_id) = comment.parent_comment_id() {
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id)
.await?
.ok_or(LemmyErrorType::CouldntFindComment)?;
// Get the parent commenter local_user
let parent_creator_id = parent_comment.creator_id;
@ -152,8 +163,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
@ -162,40 +174,45 @@ pub async fn send_local_notifs(
// Don't send a notif to yourself
if parent_comment.creator_id != person.id && !check_blocks {
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
if let Ok(parent_user_view) = user_view {
recipient_ids.push(parent_user_view.local_user.id);
if let Ok(Some(parent_user_view)) = user_view {
// Don't duplicate notif if already mentioned by checking recipient ids
if !recipient_ids.contains(&parent_user_view.local_user.id) {
recipient_ids.push(parent_user_view.local_user.id);
let comment_reply_form = CommentReplyInsertForm {
recipient_id: parent_user_view.person.id,
comment_id: comment.id,
read: None,
};
let comment_reply_form = CommentReplyInsertForm {
recipient_id: parent_user_view.person.id,
comment_id: comment.id,
read: None,
};
// Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail
CommentReply::create(&mut context.pool(), &comment_reply_form)
.await
.ok();
// Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail
CommentReply::create(&mut context.pool(), &comment_reply_form)
.await
.ok();
if do_send_email {
let lang = get_interface_language(&parent_user_view);
let content = markdown_to_html(&comment.content);
send_email_to_user(
&parent_user_view,
&lang.notification_comment_reply_subject(&person.name),
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
context.settings(),
)
.await
if do_send_email {
let lang = get_interface_language(&parent_user_view);
let content = markdown_to_html(&comment.content);
send_email_to_user(
&parent_user_view,
&lang.notification_comment_reply_subject(&person.name),
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
context.settings(),
)
.await
}
}
}
}
} 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
@ -204,31 +221,33 @@ pub async fn send_local_notifs(
if post.creator_id != person.id && !check_blocks {
let creator_id = post.creator_id;
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
if let Ok(parent_user_view) = parent_user {
recipient_ids.push(parent_user_view.local_user.id);
if let Ok(Some(parent_user_view)) = parent_user {
if !recipient_ids.contains(&parent_user_view.local_user.id) {
recipient_ids.push(parent_user_view.local_user.id);
let comment_reply_form = CommentReplyInsertForm {
recipient_id: parent_user_view.person.id,
comment_id: comment.id,
read: None,
};
let comment_reply_form = CommentReplyInsertForm {
recipient_id: parent_user_view.person.id,
comment_id: comment.id,
read: None,
};
// Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail
CommentReply::create(&mut context.pool(), &comment_reply_form)
.await
.ok();
// Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail
CommentReply::create(&mut context.pool(), &comment_reply_form)
.await
.ok();
if do_send_email {
let lang = get_interface_language(&parent_user_view);
let content = markdown_to_html(&comment.content);
send_email_to_user(
&parent_user_view,
&lang.notification_post_reply_subject(&person.name),
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
context.settings(),
)
.await
if do_send_email {
let lang = get_interface_language(&parent_user_view);
let content = markdown_to_html(&comment.content);
send_email_to_user(
&parent_user_view,
&lang.notification_post_reply_subject(&person.name),
&lang.notification_post_reply_body(&content, &inbox_link, &person.name),
context.settings(),
)
.await
}
}
}
}

View file

@ -1,15 +1,16 @@
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};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
pub struct Claims {
/// local_user_id, standard claim by RFC 7519.
pub sub: String,
@ -40,7 +41,7 @@ impl Claims {
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 +51,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,14 +68,14 @@ 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)]
mod tests {
#![allow(clippy::unwrap_used)]
#![allow(clippy::indexing_slicing)]
use crate::{claims::Claims, context::LemmyContext};
use actix_web::test::TestRequest;
@ -99,7 +100,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(),
@ -124,7 +125,9 @@ mod tests {
.password_encrypted("123456".to_string())
.build();
let inserted_local_user = LocalUser::create(pool, &local_user_form).await.unwrap();
let inserted_local_user = LocalUser::create(pool, &local_user_form, vec![])
.await
.unwrap();
let req = TestRequest::default().to_http_request();
let jwt = Claims::generate(inserted_local_user.id, req, &context)

View file

@ -10,7 +10,7 @@ use serde_with::skip_serializing_none;
use ts_rs::TS;
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Create a comment.
@ -22,7 +22,7 @@ pub struct CreateComment {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Fetch an individual comment.
@ -31,7 +31,7 @@ pub struct GetComment {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Edit a comment.
@ -42,7 +42,7 @@ pub struct EditComment {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Distinguish a comment (IE speak as moderator).
@ -52,7 +52,7 @@ pub struct DistinguishComment {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Delete your own comment.
@ -62,7 +62,7 @@ pub struct DeleteComment {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Remove a comment (only doable by mods).
@ -72,7 +72,7 @@ pub struct RemoveComment {
pub reason: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Save / bookmark a comment.
@ -91,7 +91,7 @@ pub struct CommentResponse {
pub recipient_ids: Vec<LocalUserId>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Like a comment.
@ -102,7 +102,7 @@ pub struct CreateCommentLike {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Get a list of comments.
@ -146,7 +146,7 @@ pub struct CommentReportResponse {
pub comment_report_view: CommentReportView,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Resolve a comment report (only doable by mods).
@ -156,11 +156,12 @@ pub struct ResolveCommentReport {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// List comment reports.
pub struct ListCommentReports {
pub comment_id: Option<CommentId>,
pub page: Option<i64>,
pub limit: Option<i64>,
/// Only shows the unresolved reports
@ -178,7 +179,7 @@ pub struct ListCommentReportsResponse {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// List comment likes. Admins-only.

View file

@ -1,6 +1,7 @@
use lemmy_db_schema::{
newtypes::{CommunityId, LanguageId, PersonId},
source::site::Site,
CommunityVisibility,
ListingType,
SortType,
};
@ -11,7 +12,7 @@ use serde_with::skip_serializing_none;
use ts_rs::TS;
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Get a community. Must provide either an id, or a name.
@ -36,7 +37,7 @@ pub struct GetCommunityResponse {
#[skip_serializing_none]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
/// Create a community.
pub struct CreateCommunity {
/// The unique name.
@ -54,6 +55,7 @@ pub struct CreateCommunity {
/// Whether to restrict posting only to moderators.
pub posting_restricted_to_mods: Option<bool>,
pub discussion_languages: Option<Vec<LanguageId>>,
pub visibility: Option<CommunityVisibility>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
@ -66,7 +68,7 @@ pub struct CommunityResponse {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[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 communities.
@ -87,7 +89,7 @@ pub struct ListCommunitiesResponse {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Ban a user from a community.
@ -97,6 +99,9 @@ pub struct BanFromCommunity {
pub ban: bool,
pub remove_data: Option<bool>,
pub reason: Option<String>,
/// A time that the ban will expire, in unix epoch seconds.
///
/// An i64 unix timestamp is used for a simpler API client implementation.
pub expires: Option<i64>,
}
@ -109,7 +114,7 @@ pub struct BanFromCommunityResponse {
pub banned: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Add a moderator to a community.
@ -128,7 +133,7 @@ pub struct AddModToCommunityResponse {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Edit a community.
@ -147,10 +152,11 @@ pub struct EditCommunity {
/// Whether to restrict posting only to moderators.
pub posting_restricted_to_mods: Option<bool>,
pub discussion_languages: Option<Vec<LanguageId>>,
pub visibility: Option<CommunityVisibility>,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Hide a community from the main view.
@ -161,7 +167,7 @@ pub struct HideCommunity {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Delete your own community.
@ -171,7 +177,7 @@ pub struct DeleteCommunity {
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Remove a community (only doable by moderators).
@ -181,7 +187,7 @@ pub struct RemoveCommunity {
pub reason: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Follow / subscribe to a community.
@ -190,7 +196,7 @@ pub struct FollowCommunity {
pub follow: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Block a community.
@ -209,7 +215,7 @@ pub struct BlockCommunityResponse {
pub blocked: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Transfer a community to a new owner.

View file

@ -1,12 +1,14 @@
use crate::request::client_builder;
use activitypub_federation::config::{Data, FederationConfig};
use lemmy_db_schema::{
source::secret::Secret,
utils::{ActualDbPool, DbPool},
utils::{build_db_pool_for_tests, ActualDbPool, DbPool},
};
use lemmy_utils::{
rate_limit::RateLimitCell,
settings::{structs::Settings, SETTINGS},
};
use reqwest_middleware::ClientWithMiddleware;
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
use std::sync::Arc;
#[derive(Clone)]
@ -49,4 +51,33 @@ impl LemmyContext {
pub fn rate_limit_cell(&self) -> &RateLimitCell {
&self.rate_limit_cell
}
/// 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> {
// call this to run migrations
let pool = build_db_pool_for_tests().await;
let client = client_builder(&SETTINGS).build().expect("build client");
let client = ClientBuilder::new(client).build();
let secret = Secret {
id: 0,
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()
.domain(context.settings().hostname.clone())
.app_data(context)
// Dont allow any network fetches
.http_fetch_limit(0)
.build()
.await
.expect("build federation config");
config.to_request_data()
}
}

View file

@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use ts_rs::TS;
use url::Url;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Create a custom emoji.
@ -18,7 +18,7 @@ pub struct CreateCustomEmoji {
pub keywords: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Edit a custom emoji.
@ -31,7 +31,7 @@ pub struct EditCustomEmoji {
pub keywords: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Delete a custom emoji.

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