diff --git a/README.md b/README.md index a4a69cab0..41ba48099 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ Front Page|Post ---|--- -![main screen](https://i.imgur.com/kZSRcRu.png)|![chat screen](https://i.imgur.com/4XghNh6.png) +![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/chat_screen.png) [Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). @@ -44,7 +44,7 @@ The overall goal is to create an easily self-hostable, decentralized alternative Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing. -*Note: Federation is still in active development* +*Note: Federation is still in active development and the WebSocket, as well as, HTTP API are currently unstable* ### Why's it called Lemmy? @@ -125,16 +125,19 @@ Lemmy is free, open-source software, meaning no advertising, monetizing, or vent - [Docker Development](https://dev.lemmy.ml/docs/contributing_docker_development.html) - [Local Development](https://dev.lemmy.ml/docs/contributing_local_development.html) -### Translations +### Translations If you want to help with translating, take a look at [Weblate](https://weblate.yerbamate.dev/projects/lemmy/). ## Contact -- [Mastodon](https://mastodon.social/@LemmyDev) - [![Mastodon Follow](https://img.shields.io/mastodon/follow/810572?domain=https%3A%2F%2Fmastodon.social&style=social)](https://mastodon.social/@LemmyDev) -- [Matrix](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org) - [![Matrix](https://img.shields.io/matrix/rust-reddit-fediverse:matrix.org.svg?label=matrix-chat)](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org) +- [Mastodon](https://mastodon.social/@LemmyDev) +- [Matrix](https://riot.im/app/#/room/#rust-reddit-fediverse:matrix.org) + +## Code Mirrors + - [GitHub](https://github.com/LemmyNet/lemmy) -- [Gitea](https://yerbamate.dev/dessalines/lemmy) +- [Gitea](https://yerbamate.dev/LemmyNet/lemmy) - [GitLab](https://gitlab.com/dessalines/lemmy) ## Credits diff --git a/ansible/VERSION b/ansible/VERSION index e39e33bc0..e31dcbc42 100644 --- a/ansible/VERSION +++ b/ansible/VERSION @@ -1 +1 @@ -v0.6.51 +v0.6.71 diff --git a/ansible/templates/docker-compose.yml b/ansible/templates/docker-compose.yml index a4d54f6d8..9ec1bfbc2 100644 --- a/ansible/templates/docker-compose.yml +++ b/ansible/templates/docker-compose.yml @@ -26,7 +26,7 @@ services: restart: always pictshare: - image: shtripok/pictshare:latest + image: hascheksolutions/pictshare:latest ports: - "127.0.0.1:8537:80" volumes: diff --git a/ansible/templates/nginx.conf b/ansible/templates/nginx.conf index 04e5a6436..a978c1899 100644 --- a/ansible/templates/nginx.conf +++ b/ansible/templates/nginx.conf @@ -36,7 +36,7 @@ server { # It might be nice to compress JSON, but leaving that out to protect against potential # compression+encryption information leak attacks like BREACH. gzip on; - gzip_types text/css application/javascript; + gzip_types text/css application/javascript image/svg+xml; gzip_vary on; # Only connect to this site via HTTPS for the two years diff --git a/docker/dev/docker-compose.yml b/docker/dev/docker-compose.yml index 556c67bd9..3fc940990 100644 --- a/docker/dev/docker-compose.yml +++ b/docker/dev/docker-compose.yml @@ -28,7 +28,7 @@ services: - iframely pictshare: - image: shtripok/pictshare:latest + image: hascheksolutions/pictshare:latest ports: - "127.0.0.1:8537:80" volumes: diff --git a/docker/lemmy.hjson b/docker/lemmy.hjson index b61ea8261..271fc78d8 100644 --- a/docker/lemmy.hjson +++ b/docker/lemmy.hjson @@ -23,9 +23,6 @@ jwt_secret: "changeme" # The dir for the front end front_end_dir: "/app/dist" - # whether to enable activitypub federation. this feature is in alpha, do not enable in production, as might - # cause problems like remote instances fetching and permanently storing bad data. - federation_enabled: false # rate limits for various user actions, by user ip rate_limit: { # maximum number of messages created in interval @@ -60,6 +57,7 @@ # smtp_password: "" # # address to send emails from, eg "info@your-instance.com" # smtp_from_address: "" +# use_tls: true # } } diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index c38bb8996..db6e40045 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.3' +version: '2.2' services: postgres: @@ -12,7 +12,7 @@ services: restart: always lemmy: - image: dessalines/lemmy:v0.6.51 + image: dessalines/lemmy:v0.6.71 ports: - "127.0.0.1:8536:8536" restart: always @@ -26,12 +26,13 @@ services: - iframely pictshare: - image: shtripok/pictshare:latest + image: hascheksolutions/pictshare:latest ports: - "127.0.0.1:8537:80" volumes: - ./volumes/pictshare:/usr/share/nginx/html/data restart: always + mem_limit: 100m iframely: image: dogbin/iframely:latest @@ -40,3 +41,4 @@ services: volumes: - ./iframely.config.local.js:/iframely/config.local.js:ro restart: always + mem_limit: 100m diff --git a/docs/img/chat_screen.png b/docs/img/chat_screen.png new file mode 100644 index 000000000..21a452dcf Binary files /dev/null and b/docs/img/chat_screen.png differ diff --git a/docs/img/main_screen.png b/docs/img/main_screen.png new file mode 100644 index 000000000..5d1f0c32a Binary files /dev/null and b/docs/img/main_screen.png differ diff --git a/docs/img/rank_algorithm.png b/docs/img/rank_algorithm.png new file mode 100644 index 000000000..c8200f91b Binary files /dev/null and b/docs/img/rank_algorithm.png differ diff --git a/docs/src/about.md b/docs/src/about.md index 31081f485..2c0e418b3 100644 --- a/docs/src/about.md +++ b/docs/src/about.md @@ -2,7 +2,7 @@ Front Page|Post ---|--- -![main screen](https://i.imgur.com/kZSRcRu.png)|![chat screen](https://i.imgur.com/4XghNh6.png) +![main screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/main_screen.png)|![chat screen](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/chat_screen.png) [Lemmy](https://github.com/LemmyNet/lemmy) is similar to sites like [Reddit](https://reddit.com), [Lobste.rs](https://lobste.rs), [Raddle](https://raddle.me), or [Hacker News](https://news.ycombinator.com/): you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse). diff --git a/docs/src/about_ranking.md b/docs/src/about_ranking.md index d318ae82d..fe9e82bbb 100644 --- a/docs/src/about_ranking.md +++ b/docs/src/about_ranking.md @@ -26,4 +26,4 @@ Gravity = Decay gravity, 1.8 is default A plot of rank over 24 hours, of scores of 1, 5, 10, 100, 1000, with a scale factor of 10k. -![](https://i.imgur.com/w8oBLlL.png) +![](https://raw.githubusercontent.com/LemmyNet/lemmy/master/docs/img/rank_algorithm.png) diff --git a/docs/src/administration_install_docker.md b/docs/src/administration_install_docker.md index 391299b3d..236faa6bd 100644 --- a/docs/src/administration_install_docker.md +++ b/docs/src/administration_install_docker.md @@ -10,12 +10,13 @@ cd /lemmy wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/lemmy.hjson wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/iframely.config.local.js -docker-compose up -d ``` -After this, have a look at the [config file](administration_configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname. +After this, have a look at the [config file](administration_configuration.md) named `lemmy.hjson`, and adjust it, in particular the hostname, and possibly the db password. Then run: -To make Lemmy available outside the server, you need to setup a reverse proxy, like Nginx. [A sample nginx config](/ansible/templates/nginx.conf), could be setup with: +`docker-compose up -d` + +To make Lemmy available outside the server, you need to setup a reverse proxy, like Nginx. [A sample nginx config](https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf), could be setup with: ```bash wget https://raw.githubusercontent.com/dessalines/lemmy/master/ansible/templates/nginx.conf diff --git a/docs/src/contributing_federation_development.md b/docs/src/contributing_federation_development.md index bcac4caa7..80567e60f 100644 --- a/docs/src/contributing_federation_development.md +++ b/docs/src/contributing_federation_development.md @@ -5,12 +5,12 @@ If you don't have a local clone of the Lemmy repo yet, just run the following command: ```bash -git clone https://yerbamate.dev/LemmyNet/lemmy.git -b federation +git clone https://github.com/LemmyNet/lemmy -b federation ``` If you already have the Lemmy repo cloned, you need to add a new remote: ```bash -git remote add federation https://yerbamate.dev/LemmyNet/lemmy.git +git remote add federation https://github.com/LemmyNet/lemmy git checkout federation git pull federation federation ``` diff --git a/docs/src/contributing_websocket_http_api.md b/docs/src/contributing_websocket_http_api.md index f228f94e0..567f674cd 100644 --- a/docs/src/contributing_websocket_http_api.md +++ b/docs/src/contributing_websocket_http_api.md @@ -1,6 +1,6 @@ # Lemmy API -*Note: this may lag behind the actual API endpoints [here](../server/src/api).* +*Note: this may lag behind the actual API endpoints [here](../server/src/api). The API should be considered unstable and may change any time.* diff --git a/install.sh b/install.sh index ad3e4ab3a..fb42b26d1 100755 --- a/install.sh +++ b/install.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh set -e # Set the database variable to the default first. @@ -10,25 +10,55 @@ export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy export JWT_SECRET=changeme export HOSTNAME=rrr +yes_no_prompt_invalid() { + echo "Invalid input. Please enter either \"y\" or \"n\"." 1>&2 +} + +ask_to_init_db() { + init_db_valid=0 + init_db_final=0 + while [ "$init_db_valid" == 0 ] + do + read -p "Initialize database (y/n)? " init_db + case "$init_db" in + [yY]* ) init_db_valid=1; init_db_final=1;; + [nN]* ) init_db_valid=1; init_db_final=0;; + * ) yes_no_prompt_invalid;; + esac + echo + done + if [ "$init_db_final" = 1 ] + then + source ./server/db-init.sh + read -n 1 -s -r -p "Press ANY KEY to continue execution of this script, press CTRL+C to quit..." + echo + fi +} + +ask_to_auto_reload() { + auto_reload_valid=0 + auto_reload_final=0 + while [ "$auto_reload_valid" == 0 ] + do + echo "Automagically reload the project when source files are changed?" + echo "ONLY ENABLE THIS FOR DEVELOPMENT!" + read -p "(y/n) " auto_reload + case "$auto_reload" in + [yY]* ) auto_reload_valid=1; auto_reload_final=1;; + [nN]* ) auto_reload_valid=1; auto_reload_final=0;; + * ) yes_no_prompt_invalid;; + esac + echo + done + if [ "$auto_reload_final" = 1 ] + then + cd ui && yarn start + cd server && cargo watch -x run + fi +} + # Optionally initialize the database -init_db_valid=0 -init_db_final=0 -while [ "$init_db_valid" == 0 ] -do - read -p "Initialize database (y/n)? " init_db - case "${init_db,,}" in - y|yes ) init_db_valid=1; init_db_final=1;; - n|no ) init_db_valid=1; init_db_final=0;; - * ) echo "Invalid input" 1>&2;; - esac - echo -done -if [ "$init_db_final" = 1 ] -then - source ./server/db-init.sh - read -n 1 -s -r -p "Press ANY KEY to continue execution of this script, press CTRL+C to quit..." - echo -fi +ask_to_init_db # Build the web client cd ui @@ -39,6 +69,5 @@ yarn build cd ../server RUST_LOG=debug cargo run -# For live coding, where both the front and back end, automagically reload on any save, do: -# cd ui && yarn start -# cd server && cargo watch -x run +# For live coding, where both the front and back end, automagically reload on any save +ask_to_auto_reload diff --git a/server/Cargo.toml b/server/Cargo.toml index ddc6e7666..47be3239e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "lemmy_server" version = "0.0.1" -authors = ["Dessalines "] +authors = ["Dessalines "] edition = "2018" [dependencies] @@ -13,8 +13,8 @@ activitystreams-new = { git = "https://git.asonix.dog/asonix/activitystreams-ske activitystreams-ext = { git = "https://git.asonix.dog/asonix/activitystreams-ext" } bcrypt = "0.8.0" chrono = { version = "0.4.7", features = ["serde"] } -failure = "0.1.8" serde_json = { version = "1.0.48", features = ["preserve_order"]} +failure = "0.1.8" serde = { version = "1.0.105", features = ["derive"] } actix = "0.9.0" actix-web = "2.0.0" @@ -29,8 +29,8 @@ strum_macros = "0.18.0" jsonwebtoken = "7.0.1" regex = "1.3.5" lazy_static = "1.3.0" -lettre = "0.9.2" -lettre_email = "0.9.2" +lettre = "0.9.3" +lettre_email = "0.9.4" sha2 = "0.8.1" rss = "1.9.0" htmlescape = "0.3.1" diff --git a/server/db-init.sh b/server/db-init.sh index c9150e9de..a2ad77b59 100755 --- a/server/db-init.sh +++ b/server/db-init.sh @@ -1,43 +1,106 @@ -#!/bin/bash +#!/bin/sh +# Default configurations username=lemmy dbname=lemmy port=5432 -password="" -password_confirm="" -password_valid=0 +yes_no_prompt_invalid() { + echo "Invalid input. Please enter either \"y\" or \"n\"." 1>&2 +} -while [ "$password_valid" == 0 ] -do - read -p "Enter database password: " -s password +print_config() { + echo " database name: $dbname" + echo " username: $username" + echo " port: $port" +} + +ask_for_db_config() { + echo "The default database configuration is:" + print_config echo - read -p "Verify database password: " -s password_confirm - echo - echo - - # Start the loop from the top if either check fails - if [ -z "$password" ] - then - echo "Error: Password cannot be empty." 1>&2 + default_config_final=0 + default_config_valid=0 + while [ "$default_config_valid" == 0 ] + do + read -p "Use this configuration (y/n)? " default_config + case "$default_config" in + [yY]* ) default_config_valid=1; default_config_final=1;; + [nN]* ) default_config_valid=1; default_config_final=0;; + * ) yes_no_prompt_invalid;; + esac echo - continue - fi - if [ "$password" != "$password_confirm" ] + done + + if [ "$default_config_final" == 0 ] then - echo "Error: Passwords don't match." 1>&2 - echo - continue + config_ok_final=0 + while [ "$config_ok_final" == 0 ] + do + read -p "Database name: " dbname + read -p "Username: " username + read -p "Port: " port + #echo + + #echo "The database configuration is:" + #print_config + #echo + + config_ok_valid=0 + while [ "$config_ok_valid" == 0 ] + do + read -p "Use this configuration (y/n)? " config_ok + case "$config_ok" in + [yY]* ) config_ok_valid=1; config_ok_final=1;; + [nN]* ) config_ok_valid=1; config_ok_final=0;; + * ) yes_no_prompt_invalid;; + esac + echo + done + done fi +} - # Set the password_valid variable to break out of the loop - password_valid=1 -done +ask_for_password() { + password="" + password_confirm="" + password_valid=0 + while [ "$password_valid" == 0 ] + do + read -p "Enter database password: " -s password + echo + read -p "Verify database password: " -s password_confirm + echo + echo + + # Start the loop from the top if either check fails + if [ -z "$password" ] + then + echo "Error: Password cannot be empty." 1>&2 + echo + continue + fi + if [ "$password" != "$password_confirm" ] + then + echo "Error: Passwords don't match." 1>&2 + echo + continue + fi + + # Set the password_valid variable to break out of the loop + password_valid=1 + done +} + +ask_for_db_config + +ask_for_password psql -c "CREATE USER $username WITH PASSWORD '$password' SUPERUSER;" -U postgres -psql -c 'CREATE DATABASE $dbname WITH OWNER $username;' -U postgres +psql -c "CREATE DATABASE $dbname WITH OWNER $username;" -U postgres export LEMMY_DATABASE_URL=postgres://$username:$password@localhost:$port/$dbname -echo $LEMMY_DATABASE_URL +echo "The database URL is $LEMMY_DATABASE_URL" + diff --git a/server/src/api/user.rs b/server/src/api/user.rs index 18d309850..f68a1a82b 100644 --- a/server/src/api/user.rs +++ b/server/src/api/user.rs @@ -30,6 +30,7 @@ use crate::{ SortType, }, generate_random_string, + is_valid_username, naive_from_unix, naive_now, remove_slurs, @@ -314,6 +315,9 @@ impl Perform for Oper { } let user_keypair = generate_actor_keypair()?; + if !is_valid_username(&data.username) { + return Err(APIError::err("invalid_username").into()); + } // Register the new user let user_form = UserForm { diff --git a/server/src/lib.rs b/server/src/lib.rs index 055cc5f7a..32c374390 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -85,6 +85,20 @@ pub fn is_email_regex(test: &str) -> bool { EMAIL_REGEX.is_match(test) } +pub fn is_image_content_type(test: &str) -> Result<(), failure::Error> { + if isahc::get(test)? + .headers() + .get("Content-Type") + .ok_or_else(|| format_err!("No Content-Type header"))? + .to_str()? + .starts_with("image/") + { + Ok(()) + } else { + Err(format_err!("Not an image type.")) + } +} + pub fn remove_slurs(test: &str) -> String { SLUR_REGEX.replace_all(test, "*removed*").to_string() } @@ -178,6 +192,8 @@ pub struct PictshareResponse { } pub fn fetch_pictshare(image_url: &str) -> Result { + is_image_content_type(image_url)?; + let fetch_url = format!( "http://pictshare/api/geturl.php?url={}", utf8_percent_encode(image_url, NON_ALPHANUMERIC) @@ -195,36 +211,46 @@ fn fetch_iframely_and_pictshare_data( Option, Option, ) { - // Fetch iframely data - let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = match url { - Some(url) => match fetch_iframely(&url) { - Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), - Err(e) => { - error!("iframely err: {}", e); - (None, None, None, None) - } - }, + match &url { + Some(url) => { + // Fetch iframely data + let (iframely_title, iframely_description, iframely_thumbnail_url, iframely_html) = + match fetch_iframely(url) { + Ok(res) => (res.title, res.description, res.thumbnail_url, res.html), + Err(e) => { + error!("iframely err: {}", e); + (None, None, None, None) + } + }; + + // Fetch pictshare thumbnail + let pictshare_thumbnail = match iframely_thumbnail_url { + Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) { + Ok(res) => Some(res.url), + Err(e) => { + error!("pictshare err: {}", e); + None + } + }, + // Try to generate a small thumbnail if iframely is not supported + None => match fetch_pictshare(&url) { + Ok(res) => Some(res.url), + Err(e) => { + error!("pictshare err: {}", e); + None + } + }, + }; + + ( + iframely_title, + iframely_description, + iframely_html, + pictshare_thumbnail, + ) + } None => (None, None, None, None), - }; - - // Fetch pictshare thumbnail - let pictshare_thumbnail = match iframely_thumbnail_url { - Some(iframely_thumbnail_url) => match fetch_pictshare(&iframely_thumbnail_url) { - Ok(res) => Some(res.url), - Err(e) => { - error!("pictshare err: {}", e); - None - } - }, - None => None, - }; - - ( - iframely_title, - iframely_description, - iframely_html, - pictshare_thumbnail, - ) + } } pub fn markdown_to_html(text: &str) -> String { @@ -268,10 +294,16 @@ pub fn scrape_text_for_mentions(text: &str) -> Vec { out.into_iter().unique().collect() } +pub fn is_valid_username(name: &str) -> bool { + VALID_USERNAME_REGEX.is_match(name) +} + #[cfg(test)] mod tests { use crate::{ is_email_regex, + is_image_content_type, + is_valid_username, remove_slurs, scrape_text_for_mentions, slur_check, @@ -288,12 +320,30 @@ mod tests { assert_eq!(mentions[1].domain, "lemmy_alpha:8540".to_string()); } + #[test] + fn test_image() { + assert!(is_image_content_type("https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").is_ok()); + assert!(is_image_content_type( + "https://twitter.com/BenjaminNorton/status/1259922424272957440?s=20" + ) + .is_err()); + } + #[test] fn test_email() { assert!(is_email_regex("gush@gmail.com")); assert!(!is_email_regex("nada_neutho")); } + #[test] + fn test_valid_register_username() { + assert!(is_valid_username("Hello_98")); + assert!(is_valid_username("ten")); + assert!(!is_valid_username("Hello-98")); + assert!(!is_valid_username("a")); + assert!(!is_valid_username("")); + } + #[test] fn test_slur_filter() { let test = @@ -351,4 +401,5 @@ lazy_static! { // TODO keep this old one, it didn't work with port well tho // static ref WEBFINGER_USER_REGEX: Regex = Regex::new(r"@(?P[\w.]+)@(?P[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)").unwrap(); static ref WEBFINGER_USER_REGEX: Regex = Regex::new(r"@(?P[\w.]+)@(?P[a-zA-Z0-9._:-]+)").unwrap(); + static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap(); } diff --git a/server/src/main.rs b/server/src/main.rs index 4339e5f06..2f53f3ac6 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,14 +1,24 @@ extern crate lemmy_server; #[macro_use] extern crate diesel_migrations; +#[macro_use] +pub extern crate lazy_static; +use crate::lemmy_server::actix_web::dev::Service; use actix::prelude::*; -use actix_web::*; +use actix_web::{ + body::Body, + dev::{ServiceRequest, ServiceResponse}, + http::{ + header::{CACHE_CONTROL, CONTENT_TYPE}, + HeaderValue, + }, + *, +}; use diesel::{ r2d2::{ConnectionManager, Pool}, PgConnection, }; -use failure::Error; use lemmy_server::{ db::code_migrations::run_advanced_migrations, rate_limit::{rate_limiter::RateLimiter, RateLimit}, @@ -16,13 +26,22 @@ use lemmy_server::{ settings::Settings, websocket::server::*, }; -use std::sync::Arc; +use regex::Regex; +use std::{io, sync::Arc}; use tokio::sync::Mutex; +lazy_static! { + static ref CACHE_CONTROL_REGEX: Regex = + Regex::new("^((text|image)/.+|application/javascript)$").unwrap(); + // static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 365 * 24 * 60 * 60); + // Test out 1 hour here, this is breaking some things + static ref CACHE_CONTROL_VALUE: String = format!("public, max-age={}", 60 * 60); +} + embed_migrations!(); #[actix_rt::main] -async fn main() -> Result<(), Error> { +async fn main() -> io::Result<()> { env_logger::init(); let settings = Settings::get(); @@ -52,33 +71,52 @@ async fn main() -> Result<(), Error> { ); // Create Http server with websocket support - Ok( - HttpServer::new(move || { - let settings = Settings::get(); - let rate_limiter = rate_limiter.clone(); - App::new() - .wrap(middleware::Logger::default()) - .data(pool.clone()) - .data(server.clone()) - // The routes - .configure(move |cfg| api::config(cfg, &rate_limiter)) - .configure(federation::config) - .configure(feeds::config) - .configure(index::config) - .configure(nodeinfo::config) - .configure(webfinger::config) - // static files - .service(actix_files::Files::new( - "/static", - settings.front_end_dir.to_owned(), - )) - .service(actix_files::Files::new( - "/docs", - settings.front_end_dir + "/documentation", - )) - }) - .bind((settings.bind, settings.port))? - .run() - .await?, - ) + HttpServer::new(move || { + let settings = Settings::get(); + let rate_limiter = rate_limiter.clone(); + App::new() + .wrap_fn(add_cache_headers) + .wrap(middleware::Logger::default()) + .data(pool.clone()) + .data(server.clone()) + // The routes + .configure(move |cfg| api::config(cfg, &rate_limiter)) + .configure(federation::config) + .configure(feeds::config) + .configure(index::config) + .configure(nodeinfo::config) + .configure(webfinger::config) + // static files + .service(actix_files::Files::new( + "/static", + settings.front_end_dir.to_owned(), + )) + .service(actix_files::Files::new( + "/docs", + settings.front_end_dir + "/documentation", + )) + }) + .bind((settings.bind, settings.port))? + .run() + .await +} + +fn add_cache_headers( + req: ServiceRequest, + srv: &mut S, +) -> impl Future> +where + S: Service, Error = Error>, +{ + let fut = srv.call(req); + async move { + let mut res = fut.await?; + if let Some(content_type) = res.headers().get(CONTENT_TYPE) { + if CACHE_CONTROL_REGEX.is_match(content_type.to_str().unwrap()) { + let header_val = HeaderValue::from_static(&CACHE_CONTROL_VALUE); + res.headers_mut().insert(CACHE_CONTROL, header_val); + } + } + Ok(res) + } } diff --git a/server/src/routes/websocket.rs b/server/src/routes/websocket.rs index e6de7b77b..2f964d431 100644 --- a/server/src/routes/websocket.rs +++ b/server/src/routes/websocket.rs @@ -130,7 +130,7 @@ impl StreamHandler> for WSSession { } actix::fut::ready(()) }) - .wait(ctx); + .spawn(ctx); } ws::Message::Binary(_bin) => info!("Unexpected binary"), ws::Message::Close(_) => { diff --git a/server/src/version.rs b/server/src/version.rs index a287e3a46..354b86b40 100644 --- a/server/src/version.rs +++ b/server/src/version.rs @@ -1 +1 @@ -pub const VERSION: &str = "v0.6.51"; +pub const VERSION: &str = "v0.6.71"; diff --git a/ui/assets/css/main.css b/ui/assets/css/main.css index bf249e5bf..8b51dcd93 100644 --- a/ui/assets/css/main.css +++ b/ui/assets/css/main.css @@ -128,10 +128,7 @@ blockquote { .new-comments { max-height: 50vh; - overflow: hidden; -} - -.new-comments:hover { + overflow-x: hidden; overflow-y: auto; } diff --git a/ui/src/components/comment-node.tsx b/ui/src/components/comment-node.tsx index 9bc9c7bbb..ca828a458 100644 --- a/ui/src/components/comment-node.tsx +++ b/ui/src/components/comment-node.tsx @@ -709,19 +709,15 @@ export class CommentNode extends Component { get linkBtn() { let node = this.props.node; return ( - + + + + + ); } diff --git a/ui/src/components/login.tsx b/ui/src/components/login.tsx index 581c4995d..84014f68c 100644 --- a/ui/src/components/login.tsx +++ b/ui/src/components/login.tsx @@ -187,6 +187,7 @@ export class Login extends Component { type="password" id="register-password" value={this.state.registerForm.password} + autoComplete="new-password" onInput={linkEvent(this, this.handleRegisterPasswordChange)} class="form-control" required @@ -206,6 +207,7 @@ export class Login extends Component { type="password" id="register-verify-password" value={this.state.registerForm.password_verify} + autoComplete="new-password" onInput={linkEvent(this, this.handleRegisterPasswordVerifyChange)} class="form-control" required diff --git a/ui/src/components/main.tsx b/ui/src/components/main.tsx index 9e0c3a598..c168feb0c 100644 --- a/ui/src/components/main.tsx +++ b/ui/src/components/main.tsx @@ -288,9 +288,11 @@ export class Main extends Component { )}
    + {/*
  • {i18n.t('number_online', { count: this.state.siteRes.online })}
  • + */}
  • {i18n.t('number_of_users', { count: this.state.siteRes.site.number_of_users, diff --git a/ui/src/components/post-form.tsx b/ui/src/components/post-form.tsx index f04910bee..22224c344 100644 --- a/ui/src/components/post-form.tsx +++ b/ui/src/components/post-form.tsx @@ -331,6 +331,7 @@ export class PostForm extends Component { value={this.state.postForm.community_id} onInput={linkEvent(this, this.handlePostCommunityChange)} > + {this.state.communities.map(community => (