Compare commits

...

13 Commits

Author SHA1 Message Date
Dessalines 6cdb4dfc86 Fix test. 4 weeks ago
Dessalines e5f9a3e966 Fixing imports. 4 weeks ago
Dessalines f7592fdf34 Use select exists again. 4 weeks ago
Dessalines 8a0b7c05d3 Refactoring image inserts to use transactions. 4 weeks ago
Dessalines 44014c397d Updating corepack, fixing issue. 4 weeks ago
Dessalines 3c53e1362a Merge remote-tracking branch 'origin/main' into thumbnail_sizes 4 weeks ago
Dessalines 5f2ffeaaca Fix tests. 4 weeks ago
Nutomic b4f9ef24a5
Dont exit early when running only scheduled tasks (#4707)
* Dont exit early when running only scheduled tasks (fixes #4709)

* fix
1 month ago
Nutomic 866d752a3c
Instance.preferred_username should be optional (fixes #4701) (#4713) 1 month ago
Nutomic e0b1d0553d
Add timeout for processing incoming activities (#4708)
* Add timeout for processing incoming activities

* move to const
1 month ago
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
1 month ago
Nutomic cfdc732d3a
On registration set show_nsfw based on site.content_warning (#4616)
Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
1 month ago
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>
1 month ago

4
Cargo.lock generated

@ -16,9 +16,9 @@ checksum = "8f27d075294830fcab6f66e320dab524bc6d048f4a151698e153205559113772"
[[package]]
name = "activitypub_federation"
version = "0.5.5"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e54fe65c4a4b57712d8e436f1fb86ff37e10b56f011f4233fbbfa8c669163e9d"
checksum = "ac8ff2d0151ce9ac02eb29e4a58b41d28693f141f7963d4bfabd2f9d402ecec7"
dependencies = [
"activitystreams-kinds",
"actix-web",

@ -99,7 +99,7 @@ lemmy_db_views = { version = "=0.19.4-beta.6", path = "./crates/db_views" }
lemmy_db_views_actor = { version = "=0.19.4-beta.6", path = "./crates/db_views_actor" }
lemmy_db_views_moderator = { version = "=0.19.4-beta.6", path = "./crates/db_views_moderator" }
lemmy_federate = { version = "=0.19.4-beta.6", path = "./crates/federate" }
activitypub_federation = { version = "0.5.5", default-features = false, features = [
activitypub_federation = { version = "0.5.6", default-features = false, features = [
"actix-web",
] }
diesel = "2.1.6"

@ -6,11 +6,11 @@
"repository": "https://github.com/LemmyNet/lemmy",
"author": "Dessalines",
"license": "AGPL-3.0",
"packageManager": "pnpm@9.0.6",
"packageManager": "pnpm@9.1.0+sha256.22e36fba7f4880ecf749a5ca128b8435da085ecd49575e7fb9e64d6bf4fad394",
"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",
"api-test": "jest -i follow.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts && jest -i private_message.spec.ts && jest -i user.spec.ts && jest -i community.spec.ts && jest -i image.spec.ts",
"api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ",
"api-test-follow": "jest -i follow.spec.ts",
"api-test-comment": "jest -i comment.spec.ts",
"api-test-post": "jest -i post.spec.ts",

@ -13,13 +13,13 @@ importers:
version: 29.5.12
'@types/node':
specifier: ^20.12.4
version: 20.12.7
version: 20.12.4
'@typescript-eslint/eslint-plugin':
specifier: ^7.5.0
version: 7.5.0(@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)
version: 7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.4)
'@typescript-eslint/parser':
specifier: ^7.5.0
version: 7.5.0(eslint@8.57.0)(typescript@5.4.5)
version: 7.5.0(eslint@8.57.0)(typescript@5.4.4)
download-file-sync:
specifier: ^1.0.4
version: 1.0.4
@ -31,7 +31,7 @@ importers:
version: 5.1.3(eslint@8.57.0)(prettier@3.2.5)
jest:
specifier: ^29.5.0
version: 29.7.0(@types/node@20.12.7)
version: 29.7.0(@types/node@20.12.4)
lemmy-js-client:
specifier: 0.19.4-alpha.18
version: 0.19.4-alpha.18
@ -40,10 +40,10 @@ importers:
version: 3.2.5
ts-jest:
specifier: ^29.1.0
version: 29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@20.12.7))(typescript@5.4.5)
version: 29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.4)
typescript:
specifier: ^5.4.4
version: 5.4.5
version: 5.4.4
packages:
@ -398,8 +398,8 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
'@types/node@20.12.7':
resolution: {integrity: sha512-wq0cICSkRLVaf3UGLMGItu/PtdY7oaXaI/RVU+xliKVOtRna3PRY57ZDfztpDL0n11vfymMUnXv8QwYCO7L1wg==}
'@types/node@20.12.4':
resolution: {integrity: sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==}
'@types/semver@7.5.8':
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
@ -1539,8 +1539,8 @@ packages:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
typescript@5.4.5:
resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==}
typescript@5.4.4:
resolution: {integrity: sha512-dGE2Vv8cpVvw28v8HCPqyb08EzbBURxDpuhJvTrusShUfGnhHBafDsLdS1EhhxyL6BJQE+2cT3dDPAv+MQ6oLw==}
engines: {node: '>=14.17'}
hasBin: true
@ -1857,7 +1857,7 @@ snapshots:
'@jest/console@29.7.0':
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
chalk: 4.1.2
jest-message-util: 29.7.0
jest-util: 29.7.0
@ -1870,14 +1870,14 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
ansi-escapes: 4.3.2
chalk: 4.1.2
ci-info: 3.9.0
exit: 0.1.2
graceful-fs: 4.2.11
jest-changed-files: 29.7.0
jest-config: 29.7.0(@types/node@20.12.7)
jest-config: 29.7.0(@types/node@20.12.4)
jest-haste-map: 29.7.0
jest-message-util: 29.7.0
jest-regex-util: 29.6.3
@ -1902,7 +1902,7 @@ snapshots:
dependencies:
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
jest-mock: 29.7.0
'@jest/expect-utils@29.7.0':
@ -1920,7 +1920,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@sinonjs/fake-timers': 10.3.0
'@types/node': 20.12.7
'@types/node': 20.12.4
jest-message-util: 29.7.0
jest-mock: 29.7.0
jest-util: 29.7.0
@ -1942,7 +1942,7 @@ snapshots:
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@jridgewell/trace-mapping': 0.3.22
'@types/node': 20.12.7
'@types/node': 20.12.4
chalk: 4.1.2
collect-v8-coverage: 1.0.2
exit: 0.1.2
@ -2012,7 +2012,7 @@ snapshots:
'@jest/schemas': 29.6.3
'@types/istanbul-lib-coverage': 2.0.6
'@types/istanbul-reports': 3.0.4
'@types/node': 20.12.7
'@types/node': 20.12.4
'@types/yargs': 17.0.32
chalk: 4.1.2
@ -2080,7 +2080,7 @@ snapshots:
'@types/graceful-fs@4.1.9':
dependencies:
'@types/node': 20.12.7
'@types/node': 20.12.4
'@types/istanbul-lib-coverage@2.0.6': {}
@ -2099,7 +2099,7 @@ snapshots:
'@types/json-schema@7.0.15': {}
'@types/node@20.12.7':
'@types/node@20.12.4':
dependencies:
undici-types: 5.26.5
@ -2113,13 +2113,13 @@ snapshots:
dependencies:
'@types/yargs-parser': 21.0.3
'@typescript-eslint/eslint-plugin@7.5.0(@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.5))(eslint@8.57.0)(typescript@5.4.5)':
'@typescript-eslint/eslint-plugin@7.5.0(@typescript-eslint/parser@7.5.0)(eslint@8.57.0)(typescript@5.4.4)':
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/parser': 7.5.0(eslint@8.57.0)(typescript@5.4.4)
'@typescript-eslint/scope-manager': 7.5.0
'@typescript-eslint/type-utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/type-utils': 7.5.0(eslint@8.57.0)(typescript@5.4.4)
'@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.4)
'@typescript-eslint/visitor-keys': 7.5.0
debug: 4.3.4
eslint: 8.57.0
@ -2127,22 +2127,20 @@ snapshots:
ignore: 5.3.1
natural-compare: 1.4.0
semver: 7.6.0
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.4.5
ts-api-utils: 1.3.0(typescript@5.4.4)
typescript: 5.4.4
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.5)':
'@typescript-eslint/parser@7.5.0(eslint@8.57.0)(typescript@5.4.4)':
dependencies:
'@typescript-eslint/scope-manager': 7.5.0
'@typescript-eslint/types': 7.5.0
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5)
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.4)
'@typescript-eslint/visitor-keys': 7.5.0
debug: 4.3.4
eslint: 8.57.0
optionalDependencies:
typescript: 5.4.5
typescript: 5.4.4
transitivePeerDependencies:
- supports-color
@ -2151,21 +2149,20 @@ snapshots:
'@typescript-eslint/types': 7.5.0
'@typescript-eslint/visitor-keys': 7.5.0
'@typescript-eslint/type-utils@7.5.0(eslint@8.57.0)(typescript@5.4.5)':
'@typescript-eslint/type-utils@7.5.0(eslint@8.57.0)(typescript@5.4.4)':
dependencies:
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5)
'@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.5)
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.4)
'@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.4)
debug: 4.3.4
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.4.5
ts-api-utils: 1.3.0(typescript@5.4.4)
typescript: 5.4.4
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@7.5.0': {}
'@typescript-eslint/typescript-estree@7.5.0(typescript@5.4.5)':
'@typescript-eslint/typescript-estree@7.5.0(typescript@5.4.4)':
dependencies:
'@typescript-eslint/types': 7.5.0
'@typescript-eslint/visitor-keys': 7.5.0
@ -2174,20 +2171,19 @@ snapshots:
is-glob: 4.0.3
minimatch: 9.0.3
semver: 7.6.0
ts-api-utils: 1.3.0(typescript@5.4.5)
optionalDependencies:
typescript: 5.4.5
ts-api-utils: 1.3.0(typescript@5.4.4)
typescript: 5.4.4
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@7.5.0(eslint@8.57.0)(typescript@5.4.5)':
'@typescript-eslint/utils@7.5.0(eslint@8.57.0)(typescript@5.4.4)':
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 7.5.0
'@typescript-eslint/types': 7.5.0
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.5)
'@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.4)
eslint: 8.57.0
semver: 7.6.0
transitivePeerDependencies:
@ -2378,13 +2374,13 @@ snapshots:
convert-source-map@2.0.0: {}
create-jest@29.7.0(@types/node@20.12.7):
create-jest@29.7.0(@types/node@20.12.4):
dependencies:
'@jest/types': 29.6.3
chalk: 4.1.2
exit: 0.1.2
graceful-fs: 4.2.11
jest-config: 29.7.0(@types/node@20.12.7)
jest-config: 29.7.0(@types/node@20.12.4)
jest-util: 29.7.0
prompts: 2.4.2
transitivePeerDependencies:
@ -2751,7 +2747,7 @@ snapshots:
'@jest/expect': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
chalk: 4.1.2
co: 4.6.0
dedent: 1.5.1
@ -2771,16 +2767,16 @@ snapshots:
- babel-plugin-macros
- supports-color
jest-cli@29.7.0(@types/node@20.12.7):
jest-cli@29.7.0(@types/node@20.12.4):
dependencies:
'@jest/core': 29.7.0
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
chalk: 4.1.2
create-jest: 29.7.0(@types/node@20.12.7)
create-jest: 29.7.0(@types/node@20.12.4)
exit: 0.1.2
import-local: 3.1.0
jest-config: 29.7.0(@types/node@20.12.7)
jest-config: 29.7.0(@types/node@20.12.4)
jest-util: 29.7.0
jest-validate: 29.7.0
yargs: 17.7.2
@ -2790,11 +2786,12 @@ snapshots:
- supports-color
- ts-node
jest-config@29.7.0(@types/node@20.12.7):
jest-config@29.7.0(@types/node@20.12.4):
dependencies:
'@babel/core': 7.23.9
'@jest/test-sequencer': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.4
babel-jest: 29.7.0(@babel/core@7.23.9)
chalk: 4.1.2
ci-info: 3.9.0
@ -2814,8 +2811,6 @@ snapshots:
pretty-format: 29.7.0
slash: 3.0.0
strip-json-comments: 3.1.1
optionalDependencies:
'@types/node': 20.12.7
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
@ -2844,7 +2839,7 @@ snapshots:
'@jest/environment': 29.7.0
'@jest/fake-timers': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
jest-mock: 29.7.0
jest-util: 29.7.0
@ -2854,7 +2849,7 @@ snapshots:
dependencies:
'@jest/types': 29.6.3
'@types/graceful-fs': 4.1.9
'@types/node': 20.12.7
'@types/node': 20.12.4
anymatch: 3.1.3
fb-watchman: 2.0.2
graceful-fs: 4.2.11
@ -2893,11 +2888,11 @@ snapshots:
jest-mock@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
jest-util: 29.7.0
jest-pnp-resolver@1.2.3(jest-resolve@29.7.0):
optionalDependencies:
dependencies:
jest-resolve: 29.7.0
jest-regex-util@29.6.3: {}
@ -2928,7 +2923,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
chalk: 4.1.2
emittery: 0.13.1
graceful-fs: 4.2.11
@ -2956,7 +2951,7 @@ snapshots:
'@jest/test-result': 29.7.0
'@jest/transform': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
chalk: 4.1.2
cjs-module-lexer: 1.2.3
collect-v8-coverage: 1.0.2
@ -3002,7 +2997,7 @@ snapshots:
jest-util@29.7.0:
dependencies:
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
chalk: 4.1.2
ci-info: 3.9.0
graceful-fs: 4.2.11
@ -3021,7 +3016,7 @@ snapshots:
dependencies:
'@jest/test-result': 29.7.0
'@jest/types': 29.6.3
'@types/node': 20.12.7
'@types/node': 20.12.4
ansi-escapes: 4.3.2
chalk: 4.1.2
emittery: 0.13.1
@ -3030,17 +3025,17 @@ snapshots:
jest-worker@29.7.0:
dependencies:
'@types/node': 20.12.7
'@types/node': 20.12.4
jest-util: 29.7.0
merge-stream: 2.0.0
supports-color: 8.1.1
jest@29.7.0(@types/node@20.12.7):
jest@29.7.0(@types/node@20.12.4):
dependencies:
'@jest/core': 29.7.0
'@jest/types': 29.6.3
import-local: 3.1.0
jest-cli: 29.7.0(@types/node@20.12.7)
jest-cli: 29.7.0(@types/node@20.12.4)
transitivePeerDependencies:
- '@types/node'
- babel-plugin-macros
@ -3362,26 +3357,23 @@ snapshots:
dependencies:
is-number: 7.0.0
ts-api-utils@1.3.0(typescript@5.4.5):
ts-api-utils@1.3.0(typescript@5.4.4):
dependencies:
typescript: 5.4.5
typescript: 5.4.4
ts-jest@29.1.2(@babel/core@7.23.9)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@20.12.7))(typescript@5.4.5):
ts-jest@29.1.2(@babel/core@7.23.9)(jest@29.7.0)(typescript@5.4.4):
dependencies:
'@babel/core': 7.23.9
bs-logger: 0.2.6
fast-json-stable-stringify: 2.1.0
jest: 29.7.0(@types/node@20.12.7)
jest: 29.7.0(@types/node@20.12.4)
jest-util: 29.7.0
json5: 2.2.3
lodash.memoize: 4.1.2
make-error: 1.3.6
semver: 7.5.4
typescript: 5.4.5
typescript: 5.4.4
yargs-parser: 21.1.1
optionalDependencies:
'@babel/core': 7.23.9
'@jest/types': 29.6.3
babel-jest: 29.7.0(@babel/core@7.23.9)
tslib@2.6.2: {}
@ -3395,7 +3387,7 @@ snapshots:
type-fest@0.21.3: {}
typescript@5.4.5: {}
typescript@5.4.4: {}
undici-types@5.26.5: {}

@ -45,7 +45,6 @@ let postOnAlphaRes: PostResponse;
beforeAll(async () => {
await setupLogins();
await unfollows();
await Promise.all([followBeta(alpha), followBeta(gamma)]);
betaCommunity = (await resolveBetaCommunity(alpha)).community;
if (betaCommunity) {

@ -380,8 +380,8 @@ test("User blocks instance, communities are hidden", async () => {
test("Community follower count is federated", async () => {
// Follow the beta community from alpha
let community = await createCommunity(beta);
let community_id = community.community_view.community.actor_id;
let resolved = await resolveCommunity(alpha, community_id);
let communityActorId = community.community_view.community.actor_id;
let resolved = await resolveCommunity(alpha, communityActorId);
if (!resolved.community) {
throw "Missing beta community";
}
@ -389,7 +389,7 @@ test("Community follower count is federated", async () => {
await followCommunity(alpha, true, resolved.community.community.id);
let followed = (
await waitUntil(
() => resolveCommunity(alpha, community_id),
() => resolveCommunity(alpha, communityActorId),
c => c.community?.subscribed === "Subscribed",
)
).community;
@ -398,7 +398,7 @@ test("Community follower count is federated", async () => {
expect(followed?.counts.subscribers).toBe(1);
// Follow the community from gamma
resolved = await resolveCommunity(gamma, community_id);
resolved = await resolveCommunity(gamma, communityActorId);
if (!resolved.community) {
throw "Missing beta community";
}
@ -406,7 +406,7 @@ test("Community follower count is federated", async () => {
await followCommunity(gamma, true, resolved.community.community.id);
followed = (
await waitUntil(
() => resolveCommunity(gamma, community_id),
() => resolveCommunity(gamma, communityActorId),
c => c.community?.subscribed === "Subscribed",
)
).community;
@ -415,7 +415,7 @@ test("Community follower count is federated", async () => {
expect(followed?.counts?.subscribers).toBe(2);
// Follow the community from delta
resolved = await resolveCommunity(delta, community_id);
resolved = await resolveCommunity(delta, communityActorId);
if (!resolved.community) {
throw "Missing beta community";
}
@ -423,7 +423,7 @@ test("Community follower count is federated", async () => {
await followCommunity(delta, true, resolved.community.community.id);
followed = (
await waitUntil(
() => resolveCommunity(delta, community_id),
() => resolveCommunity(delta, communityActorId),
c => c.community?.subscribed === "Subscribed",
)
).community;

@ -36,7 +36,10 @@ const downloadFileSync = require("download-file-sync");
beforeAll(setupLogins);
afterAll(unfollows);
afterAll(async () => {
await unfollows();
await deleteAllImages(alpha);
});
test("Upload image and delete it", async () => {
// Before running this test, you need to delete all previous images in the DB
@ -159,7 +162,6 @@ test("Purge post, linked image removed", async () => {
expect(post.post_view.post.url).toBe(upload.url);
// purge post
const purgeForm: PurgePost = {
post_id: post.post_view.post.id,
};

@ -48,7 +48,6 @@ beforeAll(async () => {
await setupLogins();
betaCommunity = (await resolveBetaCommunity(alpha)).community;
expect(betaCommunity).toBeDefined();
await unfollows();
});
afterAll(unfollows);
@ -83,10 +82,7 @@ async function assertPostFederation(postOne: PostView, postTwo: PostView) {
test("Create a post", async () => {
// Setup some allowlists and blocklists
let editSiteForm: EditSite = {
allowed_instances: ["lemmy-beta"],
};
await delta.editSite(editSiteForm);
let editSiteForm: EditSite = {};
editSiteForm.allowed_instances = [];
editSiteForm.blocked_instances = ["lemmy-alpha"];

@ -182,6 +182,10 @@ export async function setupLogins() {
];
await gamma.editSite(editSiteForm);
// Setup delta allowed instance
editSiteForm.allowed_instances = ["lemmy-beta"];
await delta.editSite(editSiteForm);
// Create the main alpha/beta communities
// Ignore thrown errors of duplicates
try {
@ -762,6 +766,7 @@ export async function unfollowRemotes(
await Promise.all(
remoteFollowed.map(cu => followCommunity(api, false, cu.community.id)),
);
let siteRes = await getSite(api);
return siteRes;
}
@ -909,6 +914,25 @@ export async function unfollows() {
unfollowRemotes(delta),
unfollowRemotes(epsilon),
]);
await purgeAllPosts(alpha);
await purgeAllPosts(beta);
await purgeAllPosts(gamma);
await purgeAllPosts(delta);
await purgeAllPosts(epsilon);
}
export async function purgeAllPosts(api: LemmyHttp) {
// The best way to get all federated items, is to find the posts
let res = await api.getPosts({ type_: "All", limit: 50 });
await Promise.all(
res.posts
.map(p => p.post.id)
// Unique
.filter((v, i, a) => a.indexOf(v) == i)
.map(post_id => api.purgePost({ post_id }))
// Ignore errors
.map(p => p.catch(e => e)),
);
}
export function getCommentParentId(comment: Comment): number | undefined {

@ -40,7 +40,7 @@ pub struct Register {
pub username: String,
pub password: Sensitive<String>,
pub password_verify: Sensitive<String>,
pub show_nsfw: bool,
pub show_nsfw: Option<bool>,
/// email is mandatory if email verification is enabled on the server
pub email: Option<Sensitive<String>>,
/// The UUID of the captcha item.

@ -11,7 +11,7 @@ use encoding_rs::{Encoding, UTF_8};
use lemmy_db_schema::{
newtypes::DbUrl,
source::{
images::{ImageDetails, ImageDetailsForm, LocalImage, LocalImageForm},
images::{ImageDetailsForm, LocalImage, LocalImageForm},
local_site::LocalSite,
post::{Post, PostUpdateForm},
},
@ -346,14 +346,12 @@ async fn generate_pictrs_thumbnail(image_url: &Url, context: &LemmyContext) -> L
pictrs_alias: image.file.clone(),
pictrs_delete_token: image.delete_token.clone(),
};
LocalImage::create(&mut context.pool(), &form).await?;
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?;
// Also store the details for the image
let details_form = image.details.build_image_details_form(&thumbnail_url);
ImageDetails::create(&mut context.pool(), &details_form).await?;
LocalImage::create(&mut context.pool(), &form, &details_form).await?;
Ok(thumbnail_url)
} else {

@ -16,7 +16,7 @@ use lemmy_db_schema::{
community::{Community, CommunityModerator, CommunityUpdateForm},
community_block::CommunityBlock,
email_verification::{EmailVerification, EmailVerificationForm},
images::{ImageDetails, RemoteImage},
images::RemoteImage,
instance::Instance,
instance_block::InstanceBlock,
local_site::LocalSite,
@ -938,15 +938,13 @@ pub async fn process_markdown(
// Create images and image detail rows
for link in links {
RemoteImage::create(&mut context.pool(), &link).await?;
// Insert image details for the remote image
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
if let Ok(details) = details_res {
let proxied =
build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
let details_form = details.build_image_details_form(&proxied);
ImageDetails::create(&mut context.pool(), &details_form).await?;
RemoteImage::create(&mut context.pool(), &details_form).await?;
}
}
Ok(text)
@ -984,14 +982,12 @@ async fn proxy_image_link_internal(
} else if image_mode == PictrsImageMode::ProxyAllImages {
let proxied = build_proxied_image_url(&link, &context.settings().get_protocol_and_hostname())?;
RemoteImage::create(&mut context.pool(), &link).await?;
// This should fail softly, since pictrs might not even be running
let details_res = fetch_pictrs_proxied_image_details(&link, context).await;
if let Ok(details) = details_res {
let details_form = details.build_image_details_form(&proxied);
ImageDetails::create(&mut context.pool(), &details_form).await?;
RemoteImage::create(&mut context.pool(), &details_form).await?;
}
Ok(proxied.into())
@ -1132,10 +1128,13 @@ mod tests {
"https://lemmy-alpha/api/v3/image_proxy?url=http%3A%2F%2Flemmy-beta%2Fimage.png",
proxied.as_str()
);
// This fails, because the details can't be fetched without pictrs running,
// And a remote image won't be inserted.
assert!(
RemoteImage::validate(&mut context.pool(), remote_image.into())
.await
.is_ok()
.is_err()
);
}

@ -142,12 +142,17 @@ pub async fn register(
.map(|lang_str| lang_str.split('-').next().unwrap_or_default().to_string())
.collect();
// Show nsfw content if param is true, or if content_warning exists
let show_nsfw = data
.show_nsfw
.unwrap_or(site_view.site.content_warning.is_some());
// Create the local user
let local_user_form = LocalUserInsertForm::builder()
.person_id(inserted_person.id)
.email(data.email.as_deref().map(str::to_lowercase))
.password_encrypted(data.password.to_string())
.show_nsfw(Some(data.show_nsfw))
.show_nsfw(Some(show_nsfw))
.accepted_application(accepted_application)
.default_listing_type(Some(local_site.default_post_listing_type))
.post_listing_mode(Some(local_site.default_post_listing_mode))

@ -0,0 +1,49 @@
{
"@context": ["https://www.w3.org/ns/activitystreams"],
"id": "https://pfefferle.org/lemmy-part-4/#activity#activity",
"type": "Announce",
"audience": "https://pfefferle.org/@pfefferle.org",
"published": "2024-05-03T12:32:29Z",
"updated": "2024-05-06T08:20:33Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://pfefferle.org/wp-json/activitypub/1.0/actors/1/followers"
],
"cc": [],
"object": {
"id": "https://pfefferle.org/lemmy-part-4/#activity",
"type": "Update",
"audience": "https://pfefferle.org/@pfefferle.org",
"published": "2024-05-03T12:32:29Z",
"updated": "2024-05-06T08:20:33Z",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://pfefferle.org/wp-json/activitypub/1.0/actors/1/followers"
],
"cc": [],
"object": {
"id": "https://pfefferle.org/lemmy-part-4/",
"type": "Article",
"attachment": [],
"attributedTo": "https://pfefferle.org/author/pfefferle/",
"audience": "https://pfefferle.org/@pfefferle.org",
"content": "\u003Cp\u003EIdentifies one or more entities that represent the total population of entities for which the object can considered to be relevant. Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant. \u003C/p\u003E",
"contentMap": {
"en": "\u003Cp\u003EIdentifies one or more entities that represent the total population of entities for which the object can considered to be relevant. Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant. \u003C/p\u003E"
},
"name": "Lemmy (Part 4)",
"published": "2024-05-03T12:32:29Z",
"summary": "Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant. Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object can considered to be relevant.Identifies one or more entities that represent the total population of entities for which the object [...]",
"tag": [],
"updated": "2024-05-06T08:20:33Z",
"url": "https://pfefferle.org/lemmy-part-4/",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://pfefferle.org/wp-json/activitypub/1.0/actors/1/followers"
],
"cc": []
},
"actor": "https://pfefferle.org/author/pfefferle/"
},
"actor": "https://pfefferle.org/@pfefferle.org"
}

@ -0,0 +1,66 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
"https://purl.archive.org/socialweb/webfinger",
{
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
"webfinger": "https://webfinger.net/#",
"lemmy": "https://join-lemmy.org/ns#",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"Hashtag": "as:Hashtag",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"moderators": {
"@id": "lemmy:moderators",
"@type": "@id"
},
"postingRestrictedToMods": "lemmy:postingRestrictedToMods",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable",
"resource": "webfinger:resource"
}
],
"id": "https://pfefferle.org/@pfefferle.org",
"type": "Group",
"attachment": [],
"attributedTo": "https://pfefferle.org/wp-json/activitypub/1.0/collections/moderators",
"name": "Matthias Pfefferle",
"icon": {
"type": "Image",
"url": "https://pfefferle.org/wp-content/uploads/2023/06/cropped-BeLItBV-_400x400.jpg"
},
"published": "2024-04-03T16:58:22Z",
"summary": "<p>Webworker, blogger und podcaster</p>\n",
"tag": [],
"url": "https://pfefferle.org/@pfefferle.org",
"inbox": "https://pfefferle.org/wp-json/activitypub/1.0/users/0/inbox",
"outbox": "https://pfefferle.org/wp-json/activitypub/1.0/users/0/outbox",
"following": "https://pfefferle.org/wp-json/activitypub/1.0/users/0/following",
"followers": "https://pfefferle.org/wp-json/activitypub/1.0/users/0/followers",
"preferredUsername": "pfefferle.org",
"endpoints": {
"sharedInbox": "https://pfefferle.org/wp-json/activitypub/1.0/inbox"
},
"publicKey": {
"id": "https://pfefferle.org/@pfefferle.org#main-key",
"owner": "https://pfefferle.org/@pfefferle.org",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuq8xeLMFcaCwPFBhgMRE\n/dDh2XKoNXFXnixctmK8BXSuuLMxucm3I/8NyhIvb3LqU+uP1fO8F0ecUbk2sN+x\nKag5vIV6yKXzJ8ILMWQ9AaELpXDmMZqL0zal0LUJRAOkDgPDovDAoq6tx++yDoV0\njdVbf9CoZKit1cz2ZrEuE5dswq3J/z9+c6POkhCkWEX5TPJzkOrmnjkvrXxGHUJ2\nA3+P+VaZhd5cmvqYosSpYNJshxCdev12pIF78OnYLiYiyXlgGHU+7uQR0M4tTcij\n6cUdLkms9m+b6H3ctXntPn410e5YLFPldjAYzQB5wHVdFZsWtyrbqfYdCa+KkKpA\nvwIDAQAB\n-----END PUBLIC KEY-----\n"
},
"manuallyApprovesFollowers": false,
"featured": "https://pfefferle.org/wp-json/activitypub/1.0/users/0/collections/featured",
"moderators": "https://pfefferle.org/wp-json/activitypub/1.0/collections/moderators",
"discoverable": true,
"indexable": true,
"webfinger": "pfefferle.org@pfefferle.org",
"postingRestrictedToMods": true
}

@ -0,0 +1,24 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"Hashtag": "as:Hashtag"
}
],
"id": "https://pfefferle.org?c=148",
"type": "Note",
"attributedTo": "https://pfefferle.org/author/pfefferle/",
"content": "<p>Nice! Hello from WordPress!</p>",
"contentMap": {
"en": "<p>Nice! Hello from WordPress!</p>"
},
"inReplyTo": "https://socialhub.activitypub.rocks/ap/object/ce040f1ead95964f6dbbf1084b81432d",
"published": "2024-04-30T15:21:13Z",
"tag": [],
"url": "https://pfefferle.org?c=148",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://pfefferle.org/wp-json/activitypub/1.0/users/0/followers"
],
"cc": []
}

@ -0,0 +1,26 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
{
"Hashtag": "as:Hashtag"
}
],
"id": "https://pfefferle.org/this-is-a-test-federation/",
"type": "Article",
"attachment": [],
"attributedTo": "https://pfefferle.org/author/pfefferle/",
"content": "<p>with Discource!</p>",
"contentMap": {
"en": "<p>with Discource!</p>"
},
"name": "This is a test-federation",
"published": "2024-04-30T15:16:41Z",
"summary": "with Discource! [...]",
"tag": [],
"url": "https://pfefferle.org/this-is-a-test-federation/",
"to": [
"https://www.w3.org/ns/activitystreams#Public",
"https://pfefferle.org/wp-json/activitypub/1.0/users/1/followers"
],
"cc": []
}

@ -0,0 +1,74 @@
{
"@context": [
"https://www.w3.org/ns/activitystreams",
"https://w3id.org/security/v1",
"https://purl.archive.org/socialweb/webfinger",
{
"schema": "http://schema.org#",
"toot": "http://joinmastodon.org/ns#",
"webfinger": "https://webfinger.net/#",
"lemmy": "https://join-lemmy.org/ns#",
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"PropertyValue": "schema:PropertyValue",
"value": "schema:value",
"Hashtag": "as:Hashtag",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"featuredTags": {
"@id": "toot:featuredTags",
"@type": "@id"
},
"moderators": {
"@id": "lemmy:moderators",
"@type": "@id"
},
"postingRestrictedToMods": "lemmy:postingRestrictedToMods",
"discoverable": "toot:discoverable",
"indexable": "toot:indexable",
"resource": "webfinger:resource"
}
],
"id": "https://pfefferle.org/author/pfefferle/",
"type": "Person",
"attachment": [
{
"type": "PropertyValue",
"name": "Blog",
"value": "<a rel=\"me\" title=\"https://pfefferle.org/\" target=\"_blank\" href=\"https://pfefferle.org/\">pfefferle.org</a>"
},
{
"type": "PropertyValue",
"name": "Profile",
"value": "<a rel=\"me\" title=\"https://pfefferle.org/author/pfefferle/\" target=\"_blank\" href=\"https://pfefferle.org/author/pfefferle/\">pfefferle.org</a>"
}
],
"name": "Matthias Pfefferle",
"icon": {
"type": "Image",
"url": "https://secure.gravatar.com/avatar/a2bdca7870e859658cece96c044b3be5?s=120&#038;d=mm&#038;r=g"
},
"published": "2014-02-10T15:23:08Z",
"summary": "<p>Ich arbeite als Open Web Lead für Automattic.</p>\n",
"tag": [],
"url": "https://pfefferle.org/author/pfefferle/",
"inbox": "https://pfefferle.org/wp-json/activitypub/1.0/users/1/inbox",
"outbox": "https://pfefferle.org/wp-json/activitypub/1.0/users/1/outbox",
"following": "https://pfefferle.org/wp-json/activitypub/1.0/users/1/following",
"followers": "https://pfefferle.org/wp-json/activitypub/1.0/users/1/followers",
"preferredUsername": "matthias",
"endpoints": {
"sharedInbox": "https://pfefferle.org/wp-json/activitypub/1.0/inbox"
},
"publicKey": {
"id": "https://pfefferle.org/author/pfefferle/#main-key",
"owner": "https://pfefferle.org/author/pfefferle/",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvTA5RA40nOsso04RSwyX\nHXTojRPUMlIlArDcSy3M5GUJp9/xbxSUOdBjqd31KKB1GIi3vrLmD1Qi/ZqS95Qy\nw2Zd3xOsCg+o9bsyOG+O6Y8Lu+HEB5JKLUbNHdiSviakJ8wGadH9Wm4WIiN20y+q\n/u6lgxgiWfZ2CFCN6SOc28fUKi9NmKvXK+M12BhFfy1tC5KWXKDm0UbfI1+dmqhR\n3Ffe6vEsCI/YIVVdWxQ9kouOd0XSHOGdslktkepRO7IP9i9TdwyeCa0WWRoeO5Wa\ntVpc1Y0WuNbTM2ksIXTg0G+rO1/6KO/hrHnGu3RCfb/ZIHK5L/aWYb9B3PG3LyKV\n+wIDAQAB\n-----END PUBLIC KEY-----\n"
},
"manuallyApprovesFollowers": false,
"featured": "https://pfefferle.org/wp-json/activitypub/1.0/users/1/collections/featured",
"discoverable": true,
"indexable": true,
"webfinger": "matthias@pfefferle.org"
}

@ -20,7 +20,8 @@ use lemmy_db_schema::{
};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use serde::{Deserialize, Serialize};
use std::ops::Deref;
use std::{ops::Deref, time::Duration};
use tokio::time::timeout;
use url::Url;
mod comment;
@ -30,13 +31,22 @@ mod post;
pub mod routes;
pub mod site;
const INCOMING_ACTIVITY_TIMEOUT: Duration = Duration::from_secs(9);
pub async fn shared_inbox(
request: HttpRequest,
body: Bytes,
data: Data<LemmyContext>,
) -> LemmyResult<HttpResponse> {
receive_activity::<SharedInboxActivities, UserOrCommunity, LemmyContext>(request, body, &data)
let receive_fut =
receive_activity::<SharedInboxActivities, UserOrCommunity, LemmyContext>(request, body, &data);
// Set a timeout shorter than `REQWEST_TIMEOUT` for processing incoming activities. This is to
// avoid taking a long time to process an incoming activity when a required data fetch times out.
// In this case our own instance would timeout and be marked as dead by the sender. Better to
// consider the activity broken and move on.
timeout(INCOMING_ACTIVITY_TIMEOUT, receive_fut)
.await
.map_err(|_| LemmyErrorType::InboxTimeout)?
}
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub

@ -100,7 +100,7 @@ impl Object for ApubSite {
kind: ApplicationType::Application,
id: self.id().into(),
name: self.name.clone(),
preferred_username: data.domain().to_string(),
preferred_username: Some(data.domain().to_string()),
content: self.sidebar.as_ref().map(|d| markdown_to_html(d)),
source: self.sidebar.clone().map(Source::new),
summary: self.description.clone(),

@ -96,4 +96,10 @@ mod tests {
test_json::<Report>("assets/mbin/activities/flag.json")?;
Ok(())
}
#[test]
fn test_parse_wordpress_activities() -> LemmyResult<()> {
test_json::<AnnounceActivity>("assets/wordpress/activities/announce.json")?;
Ok(())
}
}

@ -22,7 +22,7 @@ pub struct Instance {
/// site name
pub(crate) name: String,
/// instance domain, necessary for mastodon authorized fetch
pub(crate) preferred_username: String,
pub(crate) preferred_username: Option<String>,
pub(crate) inbox: Url,
/// mandatory field in activitypub, lemmy currently serves an empty outbox
pub(crate) outbox: Url,

@ -206,4 +206,13 @@ mod tests {
test_json::<Person>("assets/nodebb/objects/person.json")?;
Ok(())
}
#[test]
fn test_parse_object_wordpress() -> LemmyResult<()> {
test_json::<Group>("assets/wordpress/objects/group.json")?;
test_json::<Page>("assets/wordpress/objects/page.json")?;
test_json::<Person>("assets/wordpress/objects/person.json")?;
test_json::<Note>("assets/wordpress/objects/note.json")?;
Ok(())
}
}

@ -233,6 +233,10 @@ impl ActivityHandler for Page {
#[async_trait::async_trait]
impl InCommunity for Page {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
if let Some(audience) = &self.audience {
return audience.dereference(context).await;
}
let community = match &self.attributed_to {
AttributedTo::Lemmy(_) => {
let mut iter = self.to.iter().merge(self.cc.iter());
@ -243,7 +247,7 @@ impl InCommunity for Page {
break c;
}
} else {
Err(LemmyErrorType::NoCommunityFoundInCc)?
Err(LemmyErrorType::CouldntFindCommunity)?;
}
}
}
@ -251,11 +255,12 @@ impl InCommunity for Page {
p.iter()
.find(|a| a.kind == PersonOrGroupType::Group)
.map(|a| ObjectId::<ApubCommunity>::from(a.id.clone().into_inner()))
.ok_or(LemmyErrorType::PageDoesNotSpecifyGroup)?
.ok_or(LemmyErrorType::CouldntFindCommunity)?
.dereference(context)
.await?
}
};
if let Some(audience) = &self.audience {
verify_community_matches(audience, community.actor_id.clone())?;
}

@ -12,22 +12,37 @@ use crate::{
utils::{get_conn, DbPool},
};
use diesel::{
dsl::exists,
insert_into,
result::Error,
select,
ExpressionMethods,
NotFound,
OptionalExtension,
QueryDsl,
};
use diesel_async::RunQueryDsl;
use url::Url;
use diesel_async::{AsyncPgConnection, RunQueryDsl};
impl LocalImage {
pub async fn create(pool: &mut DbPool<'_>, form: &LocalImageForm) -> Result<Self, Error> {
pub async fn create(
pool: &mut DbPool<'_>,
form: &LocalImageForm,
image_details_form: &ImageDetailsForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(local_image::table)
.values(form)
.get_result::<Self>(conn)
conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
let local_insert = insert_into(local_image::table)
.values(form)
.get_result::<Self>(conn)
.await;
ImageDetails::create(conn, image_details_form).await?;
local_insert
}) as _
})
.await
}
@ -45,26 +60,38 @@ impl LocalImage {
}
impl RemoteImage {
pub async fn create(pool: &mut DbPool<'_>, link_: &Url) -> Result<usize, Error> {
pub async fn create(pool: &mut DbPool<'_>, form: &ImageDetailsForm) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
let form = RemoteImageForm {
link: link_.clone().into(),
};
insert_into(remote_image::table)
.values(form)
.on_conflict_do_nothing()
.execute(conn)
conn
.build_transaction()
.run(|conn| {
Box::pin(async move {
let remote_image_form = RemoteImageForm {
link: form.link.clone(),
};
let remote_insert = insert_into(remote_image::table)
.values(remote_image_form)
.on_conflict_do_nothing()
.execute(conn)
.await;
ImageDetails::create(conn, form).await?;
remote_insert
}) as _
})
.await
}
pub async fn validate(pool: &mut DbPool<'_>, link_: DbUrl) -> Result<(), Error> {
let conn = &mut get_conn(pool).await?;
let res = remote_image::table
.find(link_)
.first::<RemoteImage>(conn)
.await
.optional()?;
if res.is_some() {
let exists = select(exists(
remote_image::table.filter(remote_image::link.eq(link_)),
))
.get_result::<bool>(conn)
.await?;
if exists {
Ok(())
} else {
Err(NotFound)
@ -73,12 +100,14 @@ impl RemoteImage {
}
impl ImageDetails {
pub async fn create(pool: &mut DbPool<'_>, form: &ImageDetailsForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
pub(crate) async fn create(
conn: &mut AsyncPgConnection,
form: &ImageDetailsForm,
) -> Result<Self, Error> {
insert_into(image_details::table)
.values(form)
.on_conflict_do_nothing()
.get_result::<Self>(conn)
.get_result::<ImageDetails>(conn)
.await
}
}

@ -315,7 +315,6 @@ diesel::table! {
width -> Int4,
height -> Int4,
content_type -> Text,
published -> Timestamptz,
}
}

@ -71,7 +71,6 @@ pub struct ImageDetails {
pub width: i32,
pub height: i32,
pub content_type: String,
pub published: DateTime<Utc>,
}
#[derive(Debug, Clone, TypedBuilder)]

@ -10,7 +10,10 @@ use actix_web::{
HttpResponse,
};
use futures::stream::{Stream, StreamExt};
use lemmy_api_common::{context::LemmyContext, request::PictrsFileDetails};
use lemmy_api_common::{
context::LemmyContext,
request::{PictrsFileDetails, PictrsResponse},
};
use lemmy_db_schema::source::{
images::{LocalImage, LocalImageForm, RemoteImage},
local_site::LocalSite,
@ -19,7 +22,7 @@ use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::{error::LemmyResult, rate_limit::RateLimitCell, REQWEST_TIMEOUT};
use reqwest::Body;
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
use serde::{Deserialize, Serialize};
use serde::Deserialize;
use std::time::Duration;
use url::Url;
use urlencoding::decode;
@ -42,18 +45,6 @@ pub fn config(
.service(web::resource("/pictrs/image/delete/{token}/{filename}").route(web::get().to(delete)));
}
#[derive(Debug, Serialize, Deserialize)]
struct Image {
file: String,
delete_token: String,
}
#[derive(Debug, Serialize, Deserialize)]
struct Images {
msg: String,
files: Option<Vec<Image>>,
}
#[derive(Deserialize)]
struct PictrsGetParams {
format: Option<String>,
@ -114,15 +105,21 @@ async fn upload(
.await?;
let status = res.status();
let images = res.json::<Images>().await?;
let images = res.json::<PictrsResponse>().await?;
if let Some(images) = &images.files {
for uploaded_image in images {
for image in images {
let form = LocalImageForm {
local_user_id: Some(local_user_view.local_user.id),
pictrs_alias: uploaded_image.file.to_string(),
pictrs_delete_token: uploaded_image.delete_token.to_string(),
pictrs_alias: image.file.to_string(),
pictrs_delete_token: image.delete_token.to_string(),
};
LocalImage::create(&mut context.pool(), &form).await?;
let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let thumbnail_url = image.thumbnail_url(&protocol_and_hostname)?;
// Also store the details for the image
let details_form = image.details.build_image_details_form(&thumbnail_url);
LocalImage::create(&mut context.pool(), &form, &details_form).await?;
}
}

@ -99,8 +99,6 @@ pub enum LemmyErrorType {
PersonIsBannedFromSite(String),
InvalidVoteValue,
PageDoesNotSpecifyCreator,
PageDoesNotSpecifyGroup,
NoCommunityFoundInCc,
NoEmailSetup,
LocalSiteNotSetup,
EmailSmtpServerNeedsAPort,
@ -177,6 +175,7 @@ pub enum LemmyErrorType {
InvalidBotAction,
CantBlockLocalInstance,
UrlWithoutDomain,
InboxTimeout,
Unknown(String),
}

@ -10,7 +10,6 @@ CREATE TABLE image_details (
link text PRIMARY KEY,
width integer NOT NULL,
height integer NOT NULL,
content_type text NOT NULL,
published timestamptz DEFAULT now() NOT NULL
content_type text NOT NULL
);

@ -4,39 +4,39 @@ set -e
echo "Do not stop in the middle of this upgrade, wait until you see the message: Upgrade complete."
echo "Stopping lemmy and all services..."
sudo docker-compose stop
sudo docker compose stop
echo "Make sure postgres is started..."
sudo docker-compose up -d postgres
sudo docker compose up -d postgres
echo "Waiting..."
sleep 20s
echo "Exporting the Database to 15_16.dump.sql ..."
sudo docker-compose exec -T postgres pg_dumpall -c -U lemmy > 15_16_dump.sql
sudo docker compose exec -T postgres pg_dumpall -c -U lemmy | sudo tee 15_16_dump.sql > /dev/null
echo "Done."
echo "Stopping postgres..."
sudo docker-compose stop postgres
sudo docker compose stop postgres
echo "Waiting..."
sleep 20s
echo "Removing the old postgres folder"
sudo rm -rf volumes/postgres
echo "Updating docker-compose to use postgres version 16."
sed -i "s/image: postgres:.*/image: postgres:16-alpine/" ./docker-compose.yml
echo "Updating docker compose to use postgres version 16."
sudo sed -i "s/image: .*postgres:.*/image: docker.io/postgres:16-alpine/" ./docker-compose.yml
echo "Starting up new postgres..."
sudo docker-compose up -d postgres
sudo docker compose up -d postgres
echo "Waiting..."
sleep 20s
echo "Importing the database...."
cat 15_16_dump.sql | sudo docker-compose exec -T postgres psql -U lemmy
sudo cat 15_16_dump.sql | sudo docker compose exec -T postgres psql -U lemmy
echo "Done."
echo "Starting up lemmy..."
sudo docker-compose up -d
sudo docker compose up -d
echo "A copy of your old database is at 15_16.dump.sql . You can delete this file if the upgrade went smoothly."
echo "Upgrade complete."

@ -160,10 +160,10 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> {
rate_limit_cell.clone(),
);
if !args.disable_scheduled_tasks {
let scheduled_tasks = (!args.disable_scheduled_tasks).then(|| {
// Schedules various cleanup tasks for the DB
let _scheduled_tasks = tokio::task::spawn(scheduled_tasks::setup(context.clone()));
}
tokio::task::spawn(scheduled_tasks::setup(context.clone()))
});
if let Some(prometheus) = SETTINGS.prometheus.clone() {
serve_prometheus(prometheus, context.clone())?;
@ -218,7 +218,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> {
let mut interrupt = tokio::signal::unix::signal(SignalKind::interrupt())?;
let mut terminate = tokio::signal::unix::signal(SignalKind::terminate())?;
if server.is_some() || federate.is_some() {
if server.is_some() || federate.is_some() || scheduled_tasks.is_some() {
tokio::select! {
_ = tokio::signal::ctrl_c() => {
tracing::warn!("Received ctrl-c, shutting down gracefully...");

Loading…
Cancel
Save