This commit is contained in:
Anna 2022-09-05 00:02:36 -04:00
parent 581c16a8b2
commit 7b512e78d5
13 changed files with 75 additions and 38 deletions

25
server/Cargo.lock generated
View File

@ -120,8 +120,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
"iana-time-zone",
"js-sys",
"num-integer",
"num-traits",
"time",
"wasm-bindgen",
"winapi",
]
@ -364,7 +367,7 @@ checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
@ -663,7 +666,7 @@ checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
dependencies = [
"libc",
"log",
"wasi",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys",
]
@ -1032,6 +1035,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"chrono",
"rand",
"serde",
"serde_yaml",
@ -1281,6 +1285,17 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
@ -1599,6 +1614,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

@ -8,6 +8,7 @@ edition = "2021"
[dependencies]
anyhow = "1"
base64 = "0.13"
chrono = "0.4"
rand = "0.8"
serde = { version = "1", features = ["derive"] }
serde_yaml = "0.9"

View File

@ -0,0 +1,2 @@
alter table users
add column extra integer not null default 0;

View File

@ -155,6 +155,7 @@ words:
- Allagan tomestone
- gil
- scrip
- third-party tool
- name: Battle Tactics
words:

View File

@ -1,4 +1,5 @@
use serde::{Deserialize, Serialize};
use sqlx::types::chrono::NaiveDateTime;
use uuid::Uuid;
#[derive(Debug, Deserialize)]
@ -31,6 +32,8 @@ pub struct RetrievedMessage {
pub positive_votes: i32,
pub negative_votes: i32,
pub user_vote: i64,
#[serde(skip)]
pub created: NaiveDateTime,
}
#[derive(Debug, Serialize)]

View File

@ -30,7 +30,7 @@ pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.boxed()
}
pub fn get_id(state: Arc<State>) -> BoxedFilter<(i64, )> {
pub fn get_id(state: Arc<State>) -> BoxedFilter<((i64, i64), )> {
warp::cookie("access_token")
.or(warp::header("x-api-key"))
.unify()
@ -40,13 +40,13 @@ pub fn get_id(state: Arc<State>) -> BoxedFilter<(i64, )> {
let hashed = crate::util::hash(&access_token);
let id = sqlx::query!(
// language=sqlite
"select id from users where auth = ?",
"select id, extra from users where auth = ?",
hashed,
)
.fetch_optional(&state.db)
.await;
match id {
Ok(Some(i)) => Ok(i.id),
Ok(Some(i)) => Ok((i.id, i.extra)),
Ok(None) => Err(warp::reject::custom(WebError::InvalidAuthToken)),
Err(e) => Err(warp::reject::custom(AnyhowRejection(e.into()))),
}

View File

@ -14,7 +14,7 @@ pub fn erase(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path::param())
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and_then(move |post_id: Uuid, id: i64| logic(Arc::clone(&state), id, post_id))
.and_then(move |post_id: Uuid, (id, _)| logic(Arc::clone(&state), id, post_id))
.boxed()
}

View File

@ -2,6 +2,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use anyhow::Context;
use chrono::{Duration, Utc};
use rand::distributions::WeightedIndex;
use rand::Rng;
use warp::{Filter, Rejection, Reply};
@ -18,7 +19,7 @@ pub fn get_location(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path::end())
.and(warp::query::<HashMap<String, String>>())
.and(super::get_id(Arc::clone(&state)))
.and_then(move |location: u32, query: HashMap<String, String>, id: i64| logic(Arc::clone(&state), id, location, query))
.and_then(move |location: u32, query: HashMap<String, String>, (id, _)| logic(Arc::clone(&state), id, location, query))
.boxed()
}
@ -38,7 +39,8 @@ async fn logic(state: Arc<State>, id: i64, location: u32, query: HashMap<String,
m.message,
coalesce(sum(v.vote between 0 and 1), 0) as positive_votes,
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
v2.vote as user_vote
v2.vote as user_vote,
m.created
from messages m
left join votes v on m.id = v.message
left join votes v2 on m.id = v2.message and v2.user = ?
@ -60,8 +62,6 @@ async fn logic(state: Arc<State>, id: i64, location: u32, query: HashMap<String,
fn filter_messages(messages: &mut Vec<RetrievedMessage>) {
// just count nearby messages. this is O(n^2) but alternatives are hard
// let mut nearby = HashMap::with_capacity(messages.len());
let mut weights = HashMap::with_capacity(messages.len());
let mut ids = Vec::with_capacity(messages.len());
for a in messages.iter() {
let mut nearby = 0;
@ -79,10 +79,11 @@ fn filter_messages(messages: &mut Vec<RetrievedMessage>) {
continue;
}
// *nearby.entry(&a.id).or_insert(0) += 1;
nearby += 1;
}
println!("{} ({} nearby)", a.id, nearby);
if nearby <= 2 {
// always include groups of three or fewer
ids.push(a.id.clone());
@ -90,28 +91,36 @@ fn filter_messages(messages: &mut Vec<RetrievedMessage>) {
}
let score = (a.positive_votes - a.negative_votes).max(0);
let raw_weight = score as f32 * (1.0 / nearby as f32);
let weight = raw_weight.trunc() as i64;
println!("{}: weight {} ({} nearby)", a.id, weight.max(1), nearby);
weights.insert(a.id.clone(), weight.max(1));
}
if weights.is_empty() {
return;
}
let max_weight = weights.values().map(|weight| *weight).max().unwrap();
messages.drain_filter(|msg| {
if ids.contains(&msg.id) {
return false;
let time_since_creation = Utc::now().naive_utc().signed_duration_since(a.created) - Duration::weeks(score as i64);
println!(" time_since_creation: {}", Utc::now().naive_utc().signed_duration_since(a.created));
println!(" modified: {}", time_since_creation);
if time_since_creation > Duration::weeks(1) {
continue;
}
let weight = match weights.get(&msg.id) {
Some(w) => *w,
None => return true,
};
let brand_new = time_since_creation < Duration::hours(6);
let new = time_since_creation < Duration::days(2);
// weight / max_weight chance of being included (returning true means NOT included)
!rand::thread_rng().gen_ratio(weight as u32, max_weight as u32)
let mut numerator = 1;
if brand_new {
numerator = nearby;
} else if new {
numerator += (nearby / 3).min(1);
}
if score > 0 {
let pad = score as f32 / nearby as f32;
let rounded = pad.round() as u32;
numerator += rounded.max(nearby / 2);
}
println!(" chance: {}/{}", numerator, nearby);
if rand::thread_rng().gen_ratio(numerator.min(nearby), nearby) {
ids.push(a.id.clone());
}
}
messages.drain_filter(|msg| {
return !ids.contains(&msg.id);
});
}

View File

@ -15,7 +15,7 @@ pub fn get_message(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path::param())
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and_then(move |message_id: Uuid, id: i64| logic(Arc::clone(&state), id, message_id))
.and_then(move |message_id: Uuid, (id, _)| logic(Arc::clone(&state), id, message_id))
.boxed()
}

View File

@ -13,7 +13,7 @@ pub fn get_mine(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("messages"))
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and_then(move |id: i64| logic(Arc::clone(&state), id))
.and_then(move |(id, _)| logic(Arc::clone(&state), id))
.boxed()
}

View File

@ -12,7 +12,7 @@ pub fn unregister(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("account"))
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and_then(move |id: i64| logic(Arc::clone(&state), id))
.and_then(move |(id, _)| logic(Arc::clone(&state), id))
.boxed()
}

View File

@ -16,7 +16,7 @@ pub fn vote(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and(warp::body::json())
.and_then(move |message_id: Uuid, id: i64, vote: i8| logic(Arc::clone(&state), id, message_id, vote))
.and_then(move |message_id: Uuid, (id, _), vote: i8| logic(Arc::clone(&state), id, message_id, vote))
.boxed()
}

View File

@ -15,11 +15,11 @@ pub fn write(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and(warp::body::json())
.and_then(move |id: i64, message: Message| logic(Arc::clone(&state), id, message))
.and_then(move |(id, extra), message: Message| logic(Arc::clone(&state), id, extra, message))
.boxed()
}
async fn logic(state: Arc<State>, id: i64, message: Message) -> Result<impl Reply, Rejection> {
async fn logic(state: Arc<State>, id: i64, extra: i64, message: Message) -> Result<impl Reply, Rejection> {
let text = {
let packs = state.packs.read().await;
let pack = packs.get(&message.pack_id)
@ -47,7 +47,7 @@ async fn logic(state: Arc<State>, id: i64, message: Message) -> Result<impl Repl
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
if existing.count >= 10 {
if existing.count >= 10 + extra as i32 {
return Err(warp::reject::custom(WebError::TooManyMessages));
}