Adding a few more apub tests. (#1096)
* Adding a few more apub tests. * Fixing travis build, adding a get_post function.
This commit is contained in:
parent
e007006daf
commit
dbf231865d
13 changed files with 341 additions and 33 deletions
6
docker/federation-test/run-tests.sh
vendored
6
docker/federation-test/run-tests.sh
vendored
|
@ -13,8 +13,8 @@ pushd ../../ui
|
||||||
yarn
|
yarn
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p volumes/pictrs_{alpha,beta,gamma}
|
mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
|
||||||
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma}
|
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
|
||||||
|
|
||||||
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
|
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
|
||||||
|
|
||||||
|
@ -28,6 +28,8 @@ echo "Waiting for Lemmy to start..."
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
yarn api-test || true
|
yarn api-test || true
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
|
4
docker/federation-test/servers.sh
vendored
4
docker/federation-test/servers.sh
vendored
|
@ -12,8 +12,8 @@ pushd ../../ui
|
||||||
yarn
|
yarn
|
||||||
popd
|
popd
|
||||||
|
|
||||||
mkdir -p volumes/pictrs_{alpha,beta,gamma}
|
mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
|
||||||
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma}
|
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
|
||||||
|
|
||||||
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
|
sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
|
||||||
|
|
||||||
|
|
2
docker/federation-test/tests.sh
vendored
2
docker/federation-test/tests.sh
vendored
|
@ -6,5 +6,7 @@ echo "Waiting for Lemmy to start..."
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
yarn api-test || true
|
yarn api-test || true
|
||||||
popd
|
popd
|
||||||
|
|
76
docker/federation/docker-compose.yml
vendored
76
docker/federation/docker-compose.yml
vendored
|
@ -7,16 +7,20 @@ services:
|
||||||
- "8540:8540"
|
- "8540:8540"
|
||||||
- "8550:8550"
|
- "8550:8550"
|
||||||
- "8560:8560"
|
- "8560:8560"
|
||||||
|
- "8570:8570"
|
||||||
|
- "8580:8580"
|
||||||
volumes:
|
volumes:
|
||||||
# Hack to make this work from both docker/federation/ and docker/federation-test/
|
# Hack to make this work from both docker/federation/ and docker/federation-test/
|
||||||
- ../federation/nginx.conf:/etc/nginx/nginx.conf
|
- ../federation/nginx.conf:/etc/nginx/nginx.conf
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
- lemmy-alpha
|
|
||||||
- pictrs
|
- pictrs
|
||||||
|
- iframely
|
||||||
|
- lemmy-alpha
|
||||||
- lemmy-beta
|
- lemmy-beta
|
||||||
- lemmy-gamma
|
- lemmy-gamma
|
||||||
- iframely
|
- lemmy-delta
|
||||||
|
- lemmy-epsilon
|
||||||
|
|
||||||
pictrs:
|
pictrs:
|
||||||
restart: always
|
restart: always
|
||||||
|
@ -34,7 +38,7 @@ services:
|
||||||
- LEMMY_FRONT_END_DIR=/app/dist
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
- LEMMY_FEDERATION__ENABLED=true
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
- LEMMY_FEDERATION__TLS_ENABLED=false
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon
|
||||||
- LEMMY_PORT=8540
|
- LEMMY_PORT=8540
|
||||||
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
|
||||||
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
@ -64,7 +68,7 @@ services:
|
||||||
- LEMMY_FRONT_END_DIR=/app/dist
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
- LEMMY_FEDERATION__ENABLED=true
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
- LEMMY_FEDERATION__TLS_ENABLED=false
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon
|
||||||
- LEMMY_PORT=8550
|
- LEMMY_PORT=8550
|
||||||
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
|
||||||
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
@ -94,7 +98,7 @@ services:
|
||||||
- LEMMY_FRONT_END_DIR=/app/dist
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
- LEMMY_FEDERATION__ENABLED=true
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
- LEMMY_FEDERATION__TLS_ENABLED=false
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon
|
||||||
- LEMMY_PORT=8560
|
- LEMMY_PORT=8560
|
||||||
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
|
||||||
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
@ -115,6 +119,68 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/postgres_gamma:/var/lib/postgresql/data
|
- ./volumes/postgres_gamma:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
# An instance with only an allowlist for beta
|
||||||
|
lemmy-delta:
|
||||||
|
image: lemmy-federation:latest
|
||||||
|
environment:
|
||||||
|
- LEMMY_HOSTNAME=lemmy-delta:8570
|
||||||
|
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy
|
||||||
|
- LEMMY_JWT_SECRET=changeme
|
||||||
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta
|
||||||
|
- LEMMY_PORT=8570
|
||||||
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta
|
||||||
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
- LEMMY_SETUP__SITE_NAME=lemmy-delta
|
||||||
|
- LEMMY_RATE_LIMIT__POST=99999
|
||||||
|
- LEMMY_RATE_LIMIT__REGISTER=99999
|
||||||
|
- LEMMY_CAPTCHA__ENABLED=false
|
||||||
|
- RUST_BACKTRACE=1
|
||||||
|
- RUST_LOG=debug
|
||||||
|
depends_on:
|
||||||
|
- postgres_delta
|
||||||
|
postgres_delta:
|
||||||
|
image: postgres:12-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=lemmy
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
- POSTGRES_DB=lemmy
|
||||||
|
volumes:
|
||||||
|
- ./volumes/postgres_delta:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
# An instance who has a blocklist, with lemmy-alpha blocked
|
||||||
|
lemmy-epsilon:
|
||||||
|
image: lemmy-federation:latest
|
||||||
|
environment:
|
||||||
|
- LEMMY_HOSTNAME=lemmy-epsilon:8580
|
||||||
|
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy
|
||||||
|
- LEMMY_JWT_SECRET=changeme
|
||||||
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
|
- LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha
|
||||||
|
- LEMMY_PORT=8580
|
||||||
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon
|
||||||
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
- LEMMY_SETUP__SITE_NAME=lemmy-epsilon
|
||||||
|
- LEMMY_RATE_LIMIT__POST=99999
|
||||||
|
- LEMMY_RATE_LIMIT__REGISTER=99999
|
||||||
|
- LEMMY_CAPTCHA__ENABLED=false
|
||||||
|
- RUST_BACKTRACE=1
|
||||||
|
- RUST_LOG=debug
|
||||||
|
depends_on:
|
||||||
|
- postgres_epsilon
|
||||||
|
postgres_epsilon:
|
||||||
|
image: postgres:12-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=lemmy
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
- POSTGRES_DB=lemmy
|
||||||
|
volumes:
|
||||||
|
- ./volumes/postgres_epsilon:/var/lib/postgresql/data
|
||||||
|
|
||||||
iframely:
|
iframely:
|
||||||
image: dogbin/iframely:latest
|
image: dogbin/iframely:latest
|
||||||
volumes:
|
volumes:
|
||||||
|
|
62
docker/federation/nginx.conf
vendored
62
docker/federation/nginx.conf
vendored
|
@ -95,4 +95,66 @@ http {
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8570;
|
||||||
|
server_name 127.0.0.1;
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# Upload limit for pictshare
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://lemmy-delta:8570;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# Cuts off the trailing slash on URLs to make them valid
|
||||||
|
rewrite ^(.+)/+$ $1 permanent;
|
||||||
|
|
||||||
|
# WebSocket support
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /iframely/ {
|
||||||
|
proxy_pass http://iframely:80/;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 8580;
|
||||||
|
server_name 127.0.0.1;
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
# Upload limit for pictshare
|
||||||
|
client_max_body_size 50M;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://lemmy-epsilon:8580;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
# Cuts off the trailing slash on URLs to make them valid
|
||||||
|
rewrite ^(.+)/+$ $1 permanent;
|
||||||
|
|
||||||
|
# WebSocket support
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
|
||||||
|
location /iframely/ {
|
||||||
|
proxy_pass http://iframely:80/;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
2
docker/federation/run-federation-test.bash
vendored
2
docker/federation/run-federation-test.bash
vendored
|
@ -20,7 +20,7 @@ popd || exit
|
||||||
|
|
||||||
sudo docker build ../../ --file Dockerfile -t lemmy-federation:latest
|
sudo docker build ../../ --file Dockerfile -t lemmy-federation:latest
|
||||||
|
|
||||||
for Item in alpha beta gamma ; do
|
for Item in alpha beta gamma delta epsilon ; do
|
||||||
sudo mkdir -p volumes/pictrs_$Item
|
sudo mkdir -p volumes/pictrs_$Item
|
||||||
sudo chown -R 991:991 volumes/pictrs_$Item
|
sudo chown -R 991:991 volumes/pictrs_$Item
|
||||||
done
|
done
|
||||||
|
|
76
docker/travis/docker-compose.yml
vendored
76
docker/travis/docker-compose.yml
vendored
|
@ -7,16 +7,20 @@ services:
|
||||||
- "8540:8540"
|
- "8540:8540"
|
||||||
- "8550:8550"
|
- "8550:8550"
|
||||||
- "8560:8560"
|
- "8560:8560"
|
||||||
|
- "8570:8570"
|
||||||
|
- "8580:8580"
|
||||||
volumes:
|
volumes:
|
||||||
# Hack to make this work from both docker/federation/ and docker/federation-test/
|
# Hack to make this work from both docker/federation/ and docker/federation-test/
|
||||||
- ../federation/nginx.conf:/etc/nginx/nginx.conf
|
- ../federation/nginx.conf:/etc/nginx/nginx.conf
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
depends_on:
|
depends_on:
|
||||||
- lemmy-alpha
|
|
||||||
- pictrs
|
- pictrs
|
||||||
|
- iframely
|
||||||
|
- lemmy-alpha
|
||||||
- lemmy-beta
|
- lemmy-beta
|
||||||
- lemmy-gamma
|
- lemmy-gamma
|
||||||
- iframely
|
- lemmy-delta
|
||||||
|
- lemmy-epsilon
|
||||||
|
|
||||||
pictrs:
|
pictrs:
|
||||||
restart: always
|
restart: always
|
||||||
|
@ -34,7 +38,7 @@ services:
|
||||||
- LEMMY_FRONT_END_DIR=/app/dist
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
- LEMMY_FEDERATION__ENABLED=true
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
- LEMMY_FEDERATION__TLS_ENABLED=false
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta,lemmy-gamma,lemmy-delta,lemmy-epsilon
|
||||||
- LEMMY_PORT=8540
|
- LEMMY_PORT=8540
|
||||||
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
|
||||||
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
@ -64,7 +68,7 @@ services:
|
||||||
- LEMMY_FRONT_END_DIR=/app/dist
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
- LEMMY_FEDERATION__ENABLED=true
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
- LEMMY_FEDERATION__TLS_ENABLED=false
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-gamma,lemmy-delta,lemmy-epsilon
|
||||||
- LEMMY_PORT=8550
|
- LEMMY_PORT=8550
|
||||||
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
|
||||||
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
@ -94,7 +98,7 @@ services:
|
||||||
- LEMMY_FRONT_END_DIR=/app/dist
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
- LEMMY_FEDERATION__ENABLED=true
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
- LEMMY_FEDERATION__TLS_ENABLED=false
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-alpha,lemmy-beta,lemmy-delta,lemmy-epsilon
|
||||||
- LEMMY_PORT=8560
|
- LEMMY_PORT=8560
|
||||||
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
|
||||||
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
@ -115,6 +119,68 @@ services:
|
||||||
volumes:
|
volumes:
|
||||||
- ./volumes/postgres_gamma:/var/lib/postgresql/data
|
- ./volumes/postgres_gamma:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
# An instance with only an allowlist for beta
|
||||||
|
lemmy-delta:
|
||||||
|
image: dessalines/lemmy:travis
|
||||||
|
environment:
|
||||||
|
- LEMMY_HOSTNAME=lemmy-delta:8570
|
||||||
|
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_delta:5432/lemmy
|
||||||
|
- LEMMY_JWT_SECRET=changeme
|
||||||
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
|
- LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy-beta
|
||||||
|
- LEMMY_PORT=8570
|
||||||
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_delta
|
||||||
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
- LEMMY_SETUP__SITE_NAME=lemmy-delta
|
||||||
|
- LEMMY_RATE_LIMIT__POST=99999
|
||||||
|
- LEMMY_RATE_LIMIT__REGISTER=99999
|
||||||
|
- LEMMY_CAPTCHA__ENABLED=false
|
||||||
|
- RUST_BACKTRACE=1
|
||||||
|
- RUST_LOG=debug
|
||||||
|
depends_on:
|
||||||
|
- postgres_delta
|
||||||
|
postgres_delta:
|
||||||
|
image: postgres:12-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=lemmy
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
- POSTGRES_DB=lemmy
|
||||||
|
volumes:
|
||||||
|
- ./volumes/postgres_delta:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
# An instance who has a blocklist, with lemmy-alpha blocked
|
||||||
|
lemmy-epsilon:
|
||||||
|
image: dessalines/lemmy:travis
|
||||||
|
environment:
|
||||||
|
- LEMMY_HOSTNAME=lemmy-epsilon:8580
|
||||||
|
- LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_epsilon:5432/lemmy
|
||||||
|
- LEMMY_JWT_SECRET=changeme
|
||||||
|
- LEMMY_FRONT_END_DIR=/app/dist
|
||||||
|
- LEMMY_FEDERATION__ENABLED=true
|
||||||
|
- LEMMY_FEDERATION__TLS_ENABLED=false
|
||||||
|
- LEMMY_FEDERATION__BLOCKED_INSTANCES=lemmy-alpha
|
||||||
|
- LEMMY_PORT=8580
|
||||||
|
- LEMMY_SETUP__ADMIN_USERNAME=lemmy_epsilon
|
||||||
|
- LEMMY_SETUP__ADMIN_PASSWORD=lemmy
|
||||||
|
- LEMMY_SETUP__SITE_NAME=lemmy-epsilon
|
||||||
|
- LEMMY_RATE_LIMIT__POST=99999
|
||||||
|
- LEMMY_RATE_LIMIT__REGISTER=99999
|
||||||
|
- LEMMY_CAPTCHA__ENABLED=false
|
||||||
|
- RUST_BACKTRACE=1
|
||||||
|
- RUST_LOG=debug
|
||||||
|
depends_on:
|
||||||
|
- postgres_epsilon
|
||||||
|
postgres_epsilon:
|
||||||
|
image: postgres:12-alpine
|
||||||
|
environment:
|
||||||
|
- POSTGRES_USER=lemmy
|
||||||
|
- POSTGRES_PASSWORD=password
|
||||||
|
- POSTGRES_DB=lemmy
|
||||||
|
volumes:
|
||||||
|
- ./volumes/postgres_epsilon:/var/lib/postgresql/data
|
||||||
|
|
||||||
iframely:
|
iframely:
|
||||||
image: dogbin/iframely:latest
|
image: dogbin/iframely:latest
|
||||||
volumes:
|
volumes:
|
||||||
|
|
6
docker/travis/run-tests.sh
vendored
6
docker/travis/run-tests.sh
vendored
|
@ -5,8 +5,8 @@ set -e
|
||||||
sudo docker-compose down
|
sudo docker-compose down
|
||||||
sudo rm -rf volumes
|
sudo rm -rf volumes
|
||||||
|
|
||||||
mkdir -p volumes/pictrs_{alpha,beta,gamma}
|
mkdir -p volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
|
||||||
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma}
|
sudo chown -R 991:991 volumes/pictrs_{alpha,beta,gamma,delta,epsilon}
|
||||||
|
|
||||||
sudo docker build ../../ --file ../prod/Dockerfile --tag dessalines/lemmy:travis
|
sudo docker build ../../ --file ../prod/Dockerfile --tag dessalines/lemmy:travis
|
||||||
|
|
||||||
|
@ -17,6 +17,8 @@ echo "Waiting for Lemmy to start..."
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8570/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
|
while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8580/api/v1/site')" != "200" ]]; do sleep 1; done
|
||||||
yarn
|
yarn
|
||||||
yarn api-test
|
yarn api-test
|
||||||
popd
|
popd
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
api::{
|
api::{
|
||||||
check_community_ban,
|
check_community_ban,
|
||||||
|
get_post,
|
||||||
get_user_from_jwt,
|
get_user_from_jwt,
|
||||||
get_user_from_jwt_opt,
|
get_user_from_jwt_opt,
|
||||||
is_mod_or_admin,
|
is_mod_or_admin,
|
||||||
|
@ -151,7 +152,7 @@ impl Perform for CreateComment {
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
let post = get_post(post_id, context.pool()).await?;
|
||||||
|
|
||||||
check_community_ban(user.id, post.community_id, context.pool()).await?;
|
check_community_ban(user.id, post.community_id, context.pool()).await?;
|
||||||
|
|
||||||
|
@ -283,7 +284,7 @@ impl Perform for EditComment {
|
||||||
|
|
||||||
// Do the mentions / recipients
|
// Do the mentions / recipients
|
||||||
let post_id = orig_comment.post_id;
|
let post_id = orig_comment.post_id;
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
let post = get_post(post_id, context.pool()).await?;
|
||||||
|
|
||||||
let updated_comment_content = updated_comment.content.to_owned();
|
let updated_comment_content = updated_comment.content.to_owned();
|
||||||
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
let mentions = scrape_text_for_mentions(&updated_comment_content);
|
||||||
|
@ -379,7 +380,7 @@ impl Perform for DeleteComment {
|
||||||
|
|
||||||
// Build the recipients
|
// Build the recipients
|
||||||
let post_id = comment_view.post_id;
|
let post_id = comment_view.post_id;
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
let post = get_post(post_id, context.pool()).await?;
|
||||||
let mentions = vec![];
|
let mentions = vec![];
|
||||||
let recipient_ids = send_local_notifs(
|
let recipient_ids = send_local_notifs(
|
||||||
mentions,
|
mentions,
|
||||||
|
@ -476,7 +477,7 @@ impl Perform for RemoveComment {
|
||||||
|
|
||||||
// Build the recipients
|
// Build the recipients
|
||||||
let post_id = comment_view.post_id;
|
let post_id = comment_view.post_id;
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
let post = get_post(post_id, context.pool()).await?;
|
||||||
let mentions = vec![];
|
let mentions = vec![];
|
||||||
let recipient_ids = send_local_notifs(
|
let recipient_ids = send_local_notifs(
|
||||||
mentions,
|
mentions,
|
||||||
|
@ -655,7 +656,7 @@ impl Perform for CreateCommentLike {
|
||||||
.await??;
|
.await??;
|
||||||
|
|
||||||
let post_id = orig_comment.post_id;
|
let post_id = orig_comment.post_id;
|
||||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
let post = get_post(post_id, context.pool()).await?;
|
||||||
check_community_ban(user.id, post.community_id, context.pool()).await?;
|
check_community_ban(user.id, post.community_id, context.pool()).await?;
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
|
|
|
@ -11,6 +11,7 @@ use lemmy_db::{
|
||||||
community::*,
|
community::*,
|
||||||
community_view::*,
|
community_view::*,
|
||||||
moderator::*,
|
moderator::*,
|
||||||
|
post::Post,
|
||||||
site::*,
|
site::*,
|
||||||
user::*,
|
user::*,
|
||||||
user_view::*,
|
user_view::*,
|
||||||
|
@ -73,6 +74,13 @@ pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::api) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
|
||||||
|
match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
|
||||||
|
Ok(post) => Ok(post),
|
||||||
|
Err(_e) => Err(APIError::err("couldnt_find_post").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(in crate::api) async fn get_user_from_jwt(
|
pub(in crate::api) async fn get_user_from_jwt(
|
||||||
jwt: &str,
|
jwt: &str,
|
||||||
pool: &DbPool,
|
pool: &DbPool,
|
||||||
|
|
5
ui/src/api_tests/comment.spec.ts
vendored
5
ui/src/api_tests/comment.spec.ts
vendored
|
@ -57,6 +57,11 @@ test('Create a comment', async () => {
|
||||||
expect(betaComment.score).toBe(1);
|
expect(betaComment.score).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Create a comment in a non-existent post', async () => {
|
||||||
|
let commentRes = await createComment(alpha, -1);
|
||||||
|
expect(commentRes).toStrictEqual({ error: 'couldnt_find_post' });
|
||||||
|
});
|
||||||
|
|
||||||
test('Update a comment', async () => {
|
test('Update a comment', async () => {
|
||||||
let commentRes = await createComment(alpha, postRes.post.id);
|
let commentRes = await createComment(alpha, postRes.post.id);
|
||||||
let updateCommentRes = await updateComment(alpha, commentRes.comment.id);
|
let updateCommentRes = await updateComment(alpha, commentRes.comment.id);
|
||||||
|
|
50
ui/src/api_tests/post.spec.ts
vendored
50
ui/src/api_tests/post.spec.ts
vendored
|
@ -2,6 +2,8 @@ import {
|
||||||
alpha,
|
alpha,
|
||||||
beta,
|
beta,
|
||||||
gamma,
|
gamma,
|
||||||
|
delta,
|
||||||
|
epsilon,
|
||||||
setupLogins,
|
setupLogins,
|
||||||
createPost,
|
createPost,
|
||||||
updatePost,
|
updatePost,
|
||||||
|
@ -22,11 +24,15 @@ beforeAll(async () => {
|
||||||
await setupLogins();
|
await setupLogins();
|
||||||
await followBeta(alpha);
|
await followBeta(alpha);
|
||||||
await followBeta(gamma);
|
await followBeta(gamma);
|
||||||
|
await followBeta(delta);
|
||||||
|
await followBeta(epsilon);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await unfollowRemotes(alpha);
|
await unfollowRemotes(alpha);
|
||||||
await unfollowRemotes(gamma);
|
await unfollowRemotes(gamma);
|
||||||
|
await unfollowRemotes(delta);
|
||||||
|
await unfollowRemotes(epsilon);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Create a post', async () => {
|
test('Create a post', async () => {
|
||||||
|
@ -45,6 +51,19 @@ test('Create a post', async () => {
|
||||||
expect(betaPost.community_local).toBe(true);
|
expect(betaPost.community_local).toBe(true);
|
||||||
expect(betaPost.creator_local).toBe(false);
|
expect(betaPost.creator_local).toBe(false);
|
||||||
expect(betaPost.score).toBe(1);
|
expect(betaPost.score).toBe(1);
|
||||||
|
|
||||||
|
// Delta only follows beta, so it should not see an alpha ap_id
|
||||||
|
let searchDelta = await searchPost(delta, postRes.post);
|
||||||
|
expect(searchDelta.posts[0]).toBeUndefined();
|
||||||
|
|
||||||
|
// Epsilon has alpha blocked, it should not see the alpha post
|
||||||
|
let searchEpsilon = await searchPost(epsilon, postRes.post);
|
||||||
|
expect(searchEpsilon.posts[0]).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Create a post in a non-existent community', async () => {
|
||||||
|
let postRes = await createPost(alpha, -2);
|
||||||
|
expect(postRes).toStrictEqual({ error: 'couldnt_create_post' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unlike a post', async () => {
|
test('Unlike a post', async () => {
|
||||||
|
@ -53,6 +72,10 @@ test('Unlike a post', async () => {
|
||||||
let unlike = await likePost(alpha, 0, postRes.post);
|
let unlike = await likePost(alpha, 0, postRes.post);
|
||||||
expect(unlike.post.score).toBe(0);
|
expect(unlike.post.score).toBe(0);
|
||||||
|
|
||||||
|
// Try to unlike it again, make sure it stays at 0
|
||||||
|
let unlike2 = await likePost(alpha, 0, postRes.post);
|
||||||
|
expect(unlike2.post.score).toBe(0);
|
||||||
|
|
||||||
// Make sure that post is unliked on beta
|
// Make sure that post is unliked on beta
|
||||||
let searchBeta = await searchPost(beta, postRes.post);
|
let searchBeta = await searchPost(beta, postRes.post);
|
||||||
let betaPost = searchBeta.posts[0];
|
let betaPost = searchBeta.posts[0];
|
||||||
|
@ -67,10 +90,22 @@ test('Update a post', async () => {
|
||||||
let search = await searchForBetaCommunity(alpha);
|
let search = await searchForBetaCommunity(alpha);
|
||||||
let postRes = await createPost(alpha, search.communities[0].id);
|
let postRes = await createPost(alpha, search.communities[0].id);
|
||||||
|
|
||||||
|
let updatedName = 'A jest test federated post, updated';
|
||||||
let updatedPost = await updatePost(alpha, postRes.post);
|
let updatedPost = await updatePost(alpha, postRes.post);
|
||||||
expect(updatedPost.post.name).toBe('A jest test federated post, updated');
|
expect(updatedPost.post.name).toBe(updatedName);
|
||||||
expect(updatedPost.post.community_local).toBe(false);
|
expect(updatedPost.post.community_local).toBe(false);
|
||||||
expect(updatedPost.post.creator_local).toBe(true);
|
expect(updatedPost.post.creator_local).toBe(true);
|
||||||
|
|
||||||
|
// Make sure that post is updated on beta
|
||||||
|
let searchBeta = await searchPost(beta, postRes.post);
|
||||||
|
let betaPost = searchBeta.posts[0];
|
||||||
|
expect(betaPost.community_local).toBe(true);
|
||||||
|
expect(betaPost.creator_local).toBe(false);
|
||||||
|
expect(betaPost.name).toBe(updatedName);
|
||||||
|
|
||||||
|
// Make sure lemmy beta cannot update the post
|
||||||
|
let updatedPostBeta = await updatePost(beta, betaPost);
|
||||||
|
expect(updatedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Sticky a post', async () => {
|
test('Sticky a post', async () => {
|
||||||
|
@ -97,6 +132,15 @@ test('Sticky a post', async () => {
|
||||||
expect(betaPost2.community_local).toBe(true);
|
expect(betaPost2.community_local).toBe(true);
|
||||||
expect(betaPost2.creator_local).toBe(false);
|
expect(betaPost2.creator_local).toBe(false);
|
||||||
expect(betaPost2.stickied).toBe(false);
|
expect(betaPost2.stickied).toBe(false);
|
||||||
|
|
||||||
|
// Make sure that gamma cannot sticky the post on beta
|
||||||
|
let searchGamma = await searchPost(gamma, postRes.post);
|
||||||
|
let gammaPost = searchGamma.posts[0];
|
||||||
|
let gammaTrySticky = await stickyPost(gamma, true, gammaPost);
|
||||||
|
let searchBeta3 = await searchPost(beta, postRes.post);
|
||||||
|
let betaPost3 = searchBeta3.posts[0];
|
||||||
|
expect(gammaTrySticky.post.stickied).toBe(true);
|
||||||
|
expect(betaPost3.stickied).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Lock a post', async () => {
|
test('Lock a post', async () => {
|
||||||
|
@ -152,6 +196,10 @@ test('Delete a post', async () => {
|
||||||
// Make sure lemmy beta sees post is undeleted
|
// Make sure lemmy beta sees post is undeleted
|
||||||
let betaPost2 = await getPost(beta, createFakeBetaPostToGetId);
|
let betaPost2 = await getPost(beta, createFakeBetaPostToGetId);
|
||||||
expect(betaPost2.post.deleted).toBe(false);
|
expect(betaPost2.post.deleted).toBe(false);
|
||||||
|
|
||||||
|
// Make sure lemmy beta cannot delete the post
|
||||||
|
let deletedPostBeta = await deletePost(beta, true, betaPost2.post);
|
||||||
|
expect(deletedPostBeta).toStrictEqual({ error: 'no_post_edit_allowed' });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Remove a post from admin and community on different instance', async () => {
|
test('Remove a post from admin and community on different instance', async () => {
|
||||||
|
|
66
ui/src/api_tests/shared.ts
vendored
66
ui/src/api_tests/shared.ts
vendored
|
@ -59,50 +59,96 @@ export let gamma: API = {
|
||||||
url: 'http://localhost:8560',
|
url: 'http://localhost:8560',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export let delta: API = {
|
||||||
|
url: 'http://localhost:8570',
|
||||||
|
};
|
||||||
|
|
||||||
|
export let epsilon: API = {
|
||||||
|
url: 'http://localhost:8580',
|
||||||
|
};
|
||||||
|
|
||||||
export async function setupLogins() {
|
export async function setupLogins() {
|
||||||
let form: LoginForm = {
|
let formAlpha: LoginForm = {
|
||||||
username_or_email: 'lemmy_alpha',
|
username_or_email: 'lemmy_alpha',
|
||||||
password: 'lemmy',
|
password: 'lemmy',
|
||||||
};
|
};
|
||||||
|
|
||||||
let resA: Promise<LoginResponse> = fetch(`${apiUrl(alpha)}/user/login`, {
|
let resAlpha: Promise<LoginResponse> = fetch(`${apiUrl(alpha)}/user/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: wrapper(form),
|
body: wrapper(formAlpha),
|
||||||
}).then(d => d.json());
|
}).then(d => d.json());
|
||||||
|
|
||||||
let formB = {
|
let formBeta = {
|
||||||
username_or_email: 'lemmy_beta',
|
username_or_email: 'lemmy_beta',
|
||||||
password: 'lemmy',
|
password: 'lemmy',
|
||||||
};
|
};
|
||||||
|
|
||||||
let resB: Promise<LoginResponse> = fetch(`${apiUrl(beta)}/user/login`, {
|
let resBeta: Promise<LoginResponse> = fetch(`${apiUrl(beta)}/user/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: wrapper(formB),
|
body: wrapper(formBeta),
|
||||||
}).then(d => d.json());
|
}).then(d => d.json());
|
||||||
|
|
||||||
let formC = {
|
let formGamma = {
|
||||||
username_or_email: 'lemmy_gamma',
|
username_or_email: 'lemmy_gamma',
|
||||||
password: 'lemmy',
|
password: 'lemmy',
|
||||||
};
|
};
|
||||||
|
|
||||||
let resG: Promise<LoginResponse> = fetch(`${apiUrl(gamma)}/user/login`, {
|
let resGamma: Promise<LoginResponse> = fetch(`${apiUrl(gamma)}/user/login`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: wrapper(formC),
|
body: wrapper(formGamma),
|
||||||
}).then(d => d.json());
|
}).then(d => d.json());
|
||||||
|
|
||||||
let res = await Promise.all([resA, resB, resG]);
|
let formDelta = {
|
||||||
|
username_or_email: 'lemmy_delta',
|
||||||
|
password: 'lemmy',
|
||||||
|
};
|
||||||
|
|
||||||
|
let resDelta: Promise<LoginResponse> = fetch(`${apiUrl(delta)}/user/login`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: wrapper(formDelta),
|
||||||
|
}).then(d => d.json());
|
||||||
|
|
||||||
|
let formEpsilon = {
|
||||||
|
username_or_email: 'lemmy_epsilon',
|
||||||
|
password: 'lemmy',
|
||||||
|
};
|
||||||
|
|
||||||
|
let resEpsilon: Promise<LoginResponse> = fetch(
|
||||||
|
`${apiUrl(epsilon)}/user/login`,
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: wrapper(formEpsilon),
|
||||||
|
}
|
||||||
|
).then(d => d.json());
|
||||||
|
|
||||||
|
let res = await Promise.all([
|
||||||
|
resAlpha,
|
||||||
|
resBeta,
|
||||||
|
resGamma,
|
||||||
|
resDelta,
|
||||||
|
resEpsilon,
|
||||||
|
]);
|
||||||
|
|
||||||
alpha.auth = res[0].jwt;
|
alpha.auth = res[0].jwt;
|
||||||
beta.auth = res[1].jwt;
|
beta.auth = res[1].jwt;
|
||||||
gamma.auth = res[2].jwt;
|
gamma.auth = res[2].jwt;
|
||||||
|
delta.auth = res[3].jwt;
|
||||||
|
epsilon.auth = res[4].jwt;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPost(
|
export async function createPost(
|
||||||
|
|
Loading…
Reference in a new issue