install script
This commit is contained in:
@@ -1 +1,2 @@
|
|||||||
/target
|
/target
|
||||||
|
/dist
|
||||||
|
|||||||
Executable
+52
@@ -0,0 +1,52 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Build a release tarball for Debian servers, in a Debian container so the
|
||||||
|
# binary links against Debian's glibc (a binary built on a newer distribution
|
||||||
|
# does not start on an older glibc).
|
||||||
|
#
|
||||||
|
# ./build-release.sh -> dist/movim-<version>-<debian>.tar.gz
|
||||||
|
#
|
||||||
|
# Copy it to the server, then: tar xzf movim-*.tar.gz && cd movim-* && ./install.sh
|
||||||
|
#
|
||||||
|
# Overridable: DEBIAN (default bookworm: Debian 12, also runs on 13 trixie),
|
||||||
|
# RUST_IMAGE (default rust:1-$DEBIAN), DOCKER (default docker; podman works).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DEBIAN=${DEBIAN:-bookworm}
|
||||||
|
RUST_IMAGE=${RUST_IMAGE:-rust:1-$DEBIAN}
|
||||||
|
DOCKER=${DOCKER:-docker}
|
||||||
|
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
[ -f Cargo.toml ] || die "run this from the movim-rs repository"
|
||||||
|
command -v "$DOCKER" >/dev/null || die "$DOCKER not found"
|
||||||
|
"$DOCKER" info >/dev/null 2>&1 || die "cannot reach $DOCKER; start it (sudo systemctl start docker) and make sure you may use it (docker group)"
|
||||||
|
|
||||||
|
version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -n1)
|
||||||
|
name="movim-$version-$DEBIAN"
|
||||||
|
# Separate target and cargo cache per image: incremental rebuilds stay fast and
|
||||||
|
# never mix with host builds in target/release.
|
||||||
|
cache="target/docker-$DEBIAN"
|
||||||
|
mkdir -p "$cache/cargo"
|
||||||
|
|
||||||
|
echo "==> Building $name in $RUST_IMAGE"
|
||||||
|
"$DOCKER" run --rm \
|
||||||
|
--user "$(id -u):$(id -g)" \
|
||||||
|
-v "$PWD:/src" -w /src \
|
||||||
|
-e CARGO_HOME="/src/$cache/cargo" \
|
||||||
|
-e CARGO_TARGET_DIR="/src/$cache" \
|
||||||
|
"$RUST_IMAGE" \
|
||||||
|
sh -c 'rustc --version && cargo build --release --locked'
|
||||||
|
|
||||||
|
echo "==> Packing dist/$name.tar.gz"
|
||||||
|
stage=$(mktemp -d)
|
||||||
|
trap 'rm -rf "$stage"' EXIT
|
||||||
|
mkdir "$stage/$name"
|
||||||
|
cp "$cache/release/movim" install.sh "$stage/$name/"
|
||||||
|
cp -R frontend templates "$stage/$name/"
|
||||||
|
mkdir -p dist
|
||||||
|
tar -C "$stage" -czf "dist/$name.tar.gz" "$name"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "dist/$name.tar.gz ($(du -h "dist/$name.tar.gz" | cut -f1))"
|
||||||
|
echo "On the server: tar xzf $name.tar.gz && cd $name && ./install.sh"
|
||||||
Executable
+286
@@ -0,0 +1,286 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Install Movim as a systemd service (not enabled or started).
|
||||||
|
#
|
||||||
|
# From a release tarball (see build-release.sh), installs the prebuilt binary:
|
||||||
|
# tar xzf movim-*.tar.gz && cd movim-* && ./install.sh
|
||||||
|
# From the repository, compiles first (as a normal user, not root):
|
||||||
|
# ./install.sh
|
||||||
|
# Privileged steps go through sudo when not run as root.
|
||||||
|
#
|
||||||
|
# Overridable: MOVIM_USER (default movim, created if missing), PREFIX
|
||||||
|
# (default /opt/movim), DATA_DIR (default /var/lib/movim), CONF_DIR
|
||||||
|
# (default /etc/movim), XMPP_DOMAIN and WEB_HOST (asked when run in a
|
||||||
|
# terminal and the configuration does not exist yet).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
MOVIM_USER=${MOVIM_USER:-movim}
|
||||||
|
PREFIX=${PREFIX:-/opt/movim}
|
||||||
|
DATA_DIR=${DATA_DIR:-/var/lib/movim}
|
||||||
|
CONF_DIR=${CONF_DIR:-/etc/movim}
|
||||||
|
XMPP_DOMAIN=${XMPP_DOMAIN:-}
|
||||||
|
WEB_HOST=${WEB_HOST:-}
|
||||||
|
UNIT=/etc/systemd/system/movim.service
|
||||||
|
MIN_RUSTC=1.94
|
||||||
|
|
||||||
|
die() { echo "error: $*" >&2; exit 1; }
|
||||||
|
|
||||||
|
valid_host() {
|
||||||
|
printf '%s' "$1" | grep -Eq '^([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?\.)+[A-Za-z]{2,}$'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ask VAR "question" default: prompt on the terminal until a valid hostname.
|
||||||
|
ask() {
|
||||||
|
eval "current=\${$1}"
|
||||||
|
[ -z "$current" ] || { valid_host "$current" || die "$1=$current is not a valid domain name"; return 0; }
|
||||||
|
if [ ! -t 0 ]; then
|
||||||
|
echo " no terminal, $1 defaults to $3 (set $1=... to choose)"
|
||||||
|
eval "$1=\$3"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
while :; do
|
||||||
|
printf '%s [%s]: ' "$2" "$3"
|
||||||
|
read -r answer || die "aborted"
|
||||||
|
answer=${answer:-$3}
|
||||||
|
if valid_host "$answer"; then
|
||||||
|
eval "$1=\$answer"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo " not a valid domain name: $answer"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
[ -d frontend ] && [ -d templates ] || die "run this from the movim-rs repository or an extracted release"
|
||||||
|
command -v systemctl >/dev/null || die "systemd is required"
|
||||||
|
if [ "$(id -u)" -eq 0 ]; then
|
||||||
|
sudo() { "$@"; }
|
||||||
|
else
|
||||||
|
command -v sudo >/dev/null || die "sudo is required (or run as root)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f Cargo.toml ]; then
|
||||||
|
[ "$(id -u)" -ne 0 ] || die "do not compile as root; run as a normal user, privileged steps use sudo"
|
||||||
|
BIN=target/release/movim
|
||||||
|
elif [ -x movim ]; then
|
||||||
|
BIN=./movim
|
||||||
|
echo "==> Prebuilt binary: $(file -b movim 2>/dev/null || echo movim)"
|
||||||
|
else
|
||||||
|
die "no Cargo.toml and no prebuilt ./movim"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Questions (before the build, so nobody has to wait for it) -----------
|
||||||
|
if [ ! -e "$CONF_DIR/movim.env" ]; then
|
||||||
|
ask XMPP_DOMAIN "XMPP domain users log in with (MOVIM_DOMAIN)" example.org
|
||||||
|
fi
|
||||||
|
if [ -d /etc/nginx/sites-available ] && [ ! -e /etc/nginx/sites-available/movim ]; then
|
||||||
|
default_web=movim.${XMPP_DOMAIN:-example.org}
|
||||||
|
ask WEB_HOST "Web hostname serving Movim (nginx server_name)" "$default_web"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Build -----------------------------------------------------------------
|
||||||
|
build() {
|
||||||
|
rustup_help="Distribution packages (e.g. Debian's rustc) are too old. Install a current
|
||||||
|
toolchain for your user with rustup, it does not conflict with apt's rustc:
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
then run this script again."
|
||||||
|
|
||||||
|
# Prefer a rustup toolchain over the distribution's cargo, even if
|
||||||
|
# ~/.cargo/bin is not in PATH (non-login shell, Debian's cargo first).
|
||||||
|
[ ! -x "$HOME/.cargo/bin/cargo" ] || PATH="$HOME/.cargo/bin:$PATH"
|
||||||
|
command -v cargo >/dev/null || die "cargo not found, need Rust >= $MIN_RUSTC.
|
||||||
|
$rustup_help"
|
||||||
|
rustc_version=$(rustc --version | cut -d' ' -f2)
|
||||||
|
lowest=$(printf '%s\n%s\n' "$MIN_RUSTC" "$rustc_version" | sort -V | head -n1)
|
||||||
|
if [ "$lowest" != "$MIN_RUSTC" ]; then
|
||||||
|
if command -v rustup >/dev/null; then
|
||||||
|
die "rustc $rustc_version is too old, need >= $MIN_RUSTC: rustup update stable && rustup default stable"
|
||||||
|
fi
|
||||||
|
die "rustc $rustc_version ($(command -v rustc)) is too old, need >= $MIN_RUSTC.
|
||||||
|
$rustup_help"
|
||||||
|
fi
|
||||||
|
# Linker and C compiler (bundled SQLite, crypto libraries).
|
||||||
|
command -v cc >/dev/null || die "no C compiler; on Debian: sudo apt install build-essential cmake"
|
||||||
|
|
||||||
|
echo "==> Building (rustc $rustc_version)"
|
||||||
|
cargo build --release --locked
|
||||||
|
}
|
||||||
|
[ ! -f Cargo.toml ] || build
|
||||||
|
|
||||||
|
# --- Install ---------------------------------------------------------------
|
||||||
|
echo "==> Installing to $PREFIX (sudo)"
|
||||||
|
|
||||||
|
if ! id "$MOVIM_USER" >/dev/null 2>&1; then
|
||||||
|
nologin=$(command -v nologin || echo /usr/sbin/nologin)
|
||||||
|
echo " creating system user $MOVIM_USER"
|
||||||
|
sudo useradd --system --user-group --home-dir "$DATA_DIR" --no-create-home \
|
||||||
|
--shell "$nologin" "$MOVIM_USER"
|
||||||
|
fi
|
||||||
|
group=$(id -gn "$MOVIM_USER")
|
||||||
|
|
||||||
|
# Code and assets are owned by root and read-only for the service.
|
||||||
|
sudo install -d -m 0755 -o root -g root "$PREFIX"
|
||||||
|
sudo install -m 0755 -o root -g root "$BIN" "$PREFIX/movim.new"
|
||||||
|
for d in frontend templates; do
|
||||||
|
sudo rm -rf "$PREFIX/$d.new"
|
||||||
|
sudo cp -R "$d" "$PREFIX/$d.new"
|
||||||
|
sudo chown -R root:root "$PREFIX/$d.new"
|
||||||
|
sudo chmod -R u=rwX,go=rX "$PREFIX/$d.new"
|
||||||
|
done
|
||||||
|
# Swap in the new files only once everything is copied.
|
||||||
|
sudo mv -f "$PREFIX/movim.new" "$PREFIX/movim"
|
||||||
|
for d in frontend templates; do
|
||||||
|
sudo rm -rf "$PREFIX/$d.old"
|
||||||
|
[ ! -e "$PREFIX/$d" ] || sudo mv "$PREFIX/$d" "$PREFIX/$d.old"
|
||||||
|
sudo mv "$PREFIX/$d.new" "$PREFIX/$d"
|
||||||
|
sudo rm -rf "$PREFIX/$d.old"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Database, VAPID key: only the service user can read them.
|
||||||
|
sudo install -d -m 0750 -o "$MOVIM_USER" -g "$group" "$DATA_DIR"
|
||||||
|
|
||||||
|
# Configuration: never overwrite an existing one.
|
||||||
|
sudo install -d -m 0755 -o root -g root "$CONF_DIR"
|
||||||
|
if [ ! -e "$CONF_DIR/movim.env" ]; then
|
||||||
|
echo " writing $CONF_DIR/movim.env"
|
||||||
|
sudo tee "$CONF_DIR/movim.env" >/dev/null <<EOF
|
||||||
|
# Movim configuration, read by $UNIT
|
||||||
|
MOVIM_ROOT=$PREFIX
|
||||||
|
MOVIM_DATA_DIR=$DATA_DIR
|
||||||
|
# Listen on localhost only, behind a TLS reverse proxy (WebSocket on /ws/).
|
||||||
|
MOVIM_BIND=127.0.0.1:8080
|
||||||
|
# XMPP domain proposed on the login page.
|
||||||
|
MOVIM_DOMAIN=$XMPP_DOMAIN
|
||||||
|
# Comma separated XMPP domains allowed to log in (empty: all).
|
||||||
|
#MOVIM_XMPP_WHITELIST=example.org
|
||||||
|
#MOVIM_DISABLE_REGISTRATION=1
|
||||||
|
#MOVIM_MAX_SESSIONS=0
|
||||||
|
#MOVIM_LOCALE=en
|
||||||
|
#MOVIM_SESSION_TIMEOUT_HOURS=168
|
||||||
|
#MOVIM_BANNER=
|
||||||
|
#MOVIM_INFO=
|
||||||
|
EOF
|
||||||
|
sudo chown root:"$group" "$CONF_DIR/movim.env"
|
||||||
|
sudo chmod 0640 "$CONF_DIR/movim.env"
|
||||||
|
else
|
||||||
|
echo " keeping existing $CONF_DIR/movim.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
bind=$(sudo sed -n 's/^MOVIM_BIND=//p' "$CONF_DIR/movim.env")
|
||||||
|
bind=${bind:-127.0.0.1:8080}
|
||||||
|
|
||||||
|
# nginx site (Debian layout): written to sites-available, never overwritten,
|
||||||
|
# never linked into sites-enabled.
|
||||||
|
nginx_site=
|
||||||
|
if [ -d /etc/nginx/sites-available ]; then
|
||||||
|
nginx_site=/etc/nginx/sites-available/movim
|
||||||
|
if [ ! -e "$nginx_site" ]; then
|
||||||
|
echo " writing $nginx_site"
|
||||||
|
sudo tee "$nginx_site" >/dev/null <<EOF
|
||||||
|
# Movim behind nginx. Get the certificate first, e.g.
|
||||||
|
# sudo certbot certonly --nginx -d $WEB_HOST
|
||||||
|
# or point ssl_certificate* to your own.
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
server_name $WEB_HOST;
|
||||||
|
return 301 https://\$host\$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 443 ssl;
|
||||||
|
listen [::]:443 ssl;
|
||||||
|
http2 on;
|
||||||
|
server_name $WEB_HOST;
|
||||||
|
|
||||||
|
ssl_certificate /etc/letsencrypt/live/$WEB_HOST/fullchain.pem;
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/$WEB_HOST/privkey.pem;
|
||||||
|
|
||||||
|
# HTTP file uploads go through Movim, match your XMPP upload limit.
|
||||||
|
client_max_body_size 100m;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://$bind;
|
||||||
|
proxy_set_header Host \$host;
|
||||||
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host \$host;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws {
|
||||||
|
proxy_pass http://$bind;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade \$http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host \$host;
|
||||||
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host \$host;
|
||||||
|
proxy_read_timeout 1h;
|
||||||
|
proxy_send_timeout 1h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
echo " keeping existing $nginx_site"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo " writing $UNIT"
|
||||||
|
sudo tee "$UNIT" >/dev/null <<EOF
|
||||||
|
[Unit]
|
||||||
|
Description=Movim XMPP web client
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=$MOVIM_USER
|
||||||
|
Group=$group
|
||||||
|
EnvironmentFile=$CONF_DIR/movim.env
|
||||||
|
ExecStart=$PREFIX/movim
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5
|
||||||
|
|
||||||
|
# Sandboxing: the service only writes to its data directory.
|
||||||
|
ReadWritePaths=$DATA_DIR
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=yes
|
||||||
|
PrivateTmp=yes
|
||||||
|
PrivateDevices=yes
|
||||||
|
NoNewPrivileges=yes
|
||||||
|
ProtectKernelTunables=yes
|
||||||
|
ProtectKernelModules=yes
|
||||||
|
ProtectKernelLogs=yes
|
||||||
|
ProtectControlGroups=yes
|
||||||
|
ProtectClock=yes
|
||||||
|
ProtectHostname=yes
|
||||||
|
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
|
||||||
|
RestrictNamespaces=yes
|
||||||
|
RestrictRealtime=yes
|
||||||
|
RestrictSUIDSGID=yes
|
||||||
|
LockPersonality=yes
|
||||||
|
MemoryDenyWriteExecute=yes
|
||||||
|
SystemCallArchitectures=native
|
||||||
|
SystemCallFilter=@system-service
|
||||||
|
SystemCallFilter=~@privileged @resources
|
||||||
|
CapabilityBoundingSet=
|
||||||
|
UMask=0027
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
EOF
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Installed. Not enabled or started; as admin:"
|
||||||
|
echo " 1. check $CONF_DIR/movim.env"
|
||||||
|
echo " 2. sudo systemctl enable --now movim"
|
||||||
|
if [ -n "$nginx_site" ]; then
|
||||||
|
echo " 3. get a certificate for the nginx site ($nginx_site), then:"
|
||||||
|
echo " sudo ln -s $nginx_site /etc/nginx/sites-enabled/movim"
|
||||||
|
echo " sudo nginx -t && sudo systemctl reload nginx"
|
||||||
|
else
|
||||||
|
echo " 3. put a TLS reverse proxy in front of $bind (WebSocket on /ws/)"
|
||||||
|
fi
|
||||||
|
if systemctl is-active --quiet movim; then
|
||||||
|
echo
|
||||||
|
echo "movim is running the previous version: sudo systemctl restart movim"
|
||||||
|
fi
|
||||||
@@ -3,7 +3,10 @@
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::{sqlite::SqlitePoolOptions, AssertSqlSafe, FromRow, SqlitePool};
|
use sqlx::sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous};
|
||||||
|
use sqlx::{AssertSqlSafe, FromRow, SqlitePool};
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
pub type Db = SqlitePool;
|
pub type Db = SqlitePool;
|
||||||
|
|
||||||
@@ -153,8 +156,14 @@ CREATE TABLE IF NOT EXISTS push_subscriptions (
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
pub async fn connect(url: &str) -> Result<Db> {
|
pub async fn connect(url: &str) -> Result<Db> {
|
||||||
let pool = SqlitePoolOptions::new().max_connections(8).connect(url).await?;
|
// WAL: readers never block the writer. synchronous=NORMAL is safe in WAL
|
||||||
sqlx::query("PRAGMA journal_mode=WAL").execute(&pool).await?;
|
// (no corruption, a power loss can only drop the last commits) and avoids an
|
||||||
|
// fsync per message. Writers queue on the busy timeout instead of failing.
|
||||||
|
let options = SqliteConnectOptions::from_str(url)?
|
||||||
|
.journal_mode(SqliteJournalMode::Wal)
|
||||||
|
.synchronous(SqliteSynchronous::Normal)
|
||||||
|
.busy_timeout(Duration::from_secs(10));
|
||||||
|
let pool = SqlitePoolOptions::new().max_connections(8).connect_with(options).await?;
|
||||||
for statement in SCHEMA.split(';') {
|
for statement in SCHEMA.split(';') {
|
||||||
let statement = statement.trim();
|
let statement = statement.trim();
|
||||||
if !statement.is_empty() {
|
if !statement.is_empty() {
|
||||||
@@ -840,15 +849,17 @@ pub async fn reactions_for(db: &Db, mids: &[i64]) -> Result<Vec<Reaction>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn reactions_replace(db: &Db, mid: i64, jidfrom: &str, emojis: &[String]) -> Result<()> {
|
pub async fn reactions_replace(db: &Db, mid: i64, jidfrom: &str, emojis: &[String]) -> Result<()> {
|
||||||
sqlx::query("DELETE FROM reactions WHERE message_mid = ? AND jidfrom = ?").bind(mid).bind(jidfrom).execute(db).await?;
|
let mut tx = db.begin().await?;
|
||||||
|
sqlx::query("DELETE FROM reactions WHERE message_mid = ? AND jidfrom = ?").bind(mid).bind(jidfrom).execute(&mut *tx).await?;
|
||||||
for emoji in emojis {
|
for emoji in emojis {
|
||||||
sqlx::query("INSERT OR IGNORE INTO reactions (message_mid, jidfrom, emoji) VALUES (?, ?, ?)")
|
sqlx::query("INSERT OR IGNORE INTO reactions (message_mid, jidfrom, emoji) VALUES (?, ?, ?)")
|
||||||
.bind(mid)
|
.bind(mid)
|
||||||
.bind(jidfrom)
|
.bind(jidfrom)
|
||||||
.bind(emoji)
|
.bind(emoji)
|
||||||
.execute(db)
|
.execute(&mut *tx)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
tx.commit().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1014,6 +1025,7 @@ pub async fn messages_search(db: &Db, user: &str, jid: &str, muc: bool, keywords
|
|||||||
|
|
||||||
/// Forget everything stored for an account
|
/// Forget everything stored for an account
|
||||||
pub async fn user_delete_data(db: &Db, user: &str) -> Result<()> {
|
pub async fn user_delete_data(db: &Db, user: &str) -> Result<()> {
|
||||||
|
let mut tx = db.begin().await?;
|
||||||
for sql in [
|
for sql in [
|
||||||
"DELETE FROM reactions WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ?)",
|
"DELETE FROM reactions WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ?)",
|
||||||
"DELETE FROM message_files WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ?)",
|
"DELETE FROM message_files WHERE message_mid IN (SELECT mid FROM messages WHERE user_id = ?)",
|
||||||
@@ -1025,8 +1037,9 @@ pub async fn user_delete_data(db: &Db, user: &str) -> Result<()> {
|
|||||||
"DELETE FROM push_subscriptions WHERE user_id = ?",
|
"DELETE FROM push_subscriptions WHERE user_id = ?",
|
||||||
"DELETE FROM users WHERE id = ?",
|
"DELETE FROM users WHERE id = ?",
|
||||||
] {
|
] {
|
||||||
sqlx::query(sql).bind(user).execute(db).await?;
|
sqlx::query(sql).bind(user).execute(&mut *tx).await?;
|
||||||
}
|
}
|
||||||
|
tx.commit().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user