serverino
This commit is contained in:
parent
d2faa0e2e4
commit
216a62708e
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/database.sqlite*
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
base64 = "0.13"
|
||||
rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_yaml = "0.9"
|
||||
sha3 = "0.10"
|
||||
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "sqlite", "chrono"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
uuid = { version = "1", features = ["serde", "v4"] }
|
||||
warp = "0.3"
|
|
@ -0,0 +1,27 @@
|
|||
create table users
|
||||
(
|
||||
id integer not null primary key autoincrement,
|
||||
auth text not null
|
||||
);
|
||||
|
||||
create table messages
|
||||
(
|
||||
id text not null primary key,
|
||||
user text not null references users (id) on delete cascade,
|
||||
created timestamp not null default current_timestamp,
|
||||
territory integer not null,
|
||||
x float not null,
|
||||
y float not null,
|
||||
z float not null,
|
||||
message text not null
|
||||
);
|
||||
|
||||
create table votes
|
||||
(
|
||||
user text not null references users (id) on delete cascade,
|
||||
message text not null references messages (id) on delete cascade,
|
||||
vote tinyint not null,
|
||||
primary key (user, message)
|
||||
);
|
||||
|
||||
create index votes_user_idx on votes (user);
|
|
@ -0,0 +1,446 @@
|
|||
name: Elden Ring
|
||||
id: 8169c792-3d7c-44eb-aeee-9823c8521ae6
|
||||
|
||||
templates:
|
||||
- '{0} ahead'
|
||||
- 'No {0} ahead'
|
||||
- '{0} required ahead'
|
||||
- 'Be wary of {0}'
|
||||
- 'Try {0}'
|
||||
- 'Likely {0}'
|
||||
- 'First off, {0}'
|
||||
- 'Seek {0}'
|
||||
- 'Still no {0}...'
|
||||
- 'Why is it always {0}?'
|
||||
- 'If only I had a {0}...'
|
||||
- 'Didn''t expect {0}...'
|
||||
- 'Visions of {0}...'
|
||||
- 'Could this be a {0}?'
|
||||
- 'Time for {0}'
|
||||
- '{0}, O {0}'
|
||||
- 'Behold, {0}!'
|
||||
- 'Offer {0}'
|
||||
- 'Praise the {0}'
|
||||
- 'Let there be {0}'
|
||||
- 'Ahh, {0}...'
|
||||
- '{0}'
|
||||
- '{0}!'
|
||||
- '{0}?'
|
||||
- '{0}...'
|
||||
|
||||
conjunctions:
|
||||
- 'and then'
|
||||
- 'or'
|
||||
- 'but'
|
||||
- 'therefore'
|
||||
- 'in short'
|
||||
- 'except'
|
||||
- 'by the way'
|
||||
- 'so to speak'
|
||||
- 'all the more'
|
||||
- ','
|
||||
|
||||
words:
|
||||
- name: Enemies
|
||||
words:
|
||||
- 'enemy'
|
||||
- 'weak foe'
|
||||
- 'strong foe'
|
||||
- 'monster'
|
||||
- 'dragon'
|
||||
- 'boss'
|
||||
- 'sentry'
|
||||
- 'group'
|
||||
- 'pack'
|
||||
- 'decoy'
|
||||
- 'undead'
|
||||
- 'soldier'
|
||||
- 'knight'
|
||||
- 'cavalier'
|
||||
- 'archer'
|
||||
- 'sniper'
|
||||
- 'mage'
|
||||
- 'ordnance'
|
||||
- 'monarch'
|
||||
- 'lord'
|
||||
- 'demi-human'
|
||||
- 'outsider'
|
||||
- 'giant'
|
||||
- 'horse'
|
||||
- 'dog'
|
||||
- 'wolf'
|
||||
- 'rat'
|
||||
- 'beast'
|
||||
- 'bird'
|
||||
- 'raptor'
|
||||
- 'snake'
|
||||
- 'crab'
|
||||
- 'prawn'
|
||||
- 'octopus'
|
||||
- 'bug'
|
||||
- 'scarab'
|
||||
- 'slug'
|
||||
- 'wraith'
|
||||
- 'skeleton'
|
||||
- 'monstrosity'
|
||||
- 'ill-omened creature'
|
||||
|
||||
- name: People
|
||||
words:
|
||||
- 'Tarnished'
|
||||
- 'warrior'
|
||||
- 'swordfighter'
|
||||
- 'knight'
|
||||
- 'samurai'
|
||||
- 'sorcerer'
|
||||
- 'cleric'
|
||||
- 'sage'
|
||||
- 'merchant'
|
||||
- 'teacher'
|
||||
- 'master'
|
||||
- 'friend'
|
||||
- 'lover'
|
||||
- 'old dear'
|
||||
- 'old codger'
|
||||
- 'angel'
|
||||
- 'fat coinpurse'
|
||||
- 'pauper'
|
||||
- 'good sort'
|
||||
- 'wicked sort'
|
||||
- 'plump sort'
|
||||
- 'skinny sort'
|
||||
- 'lovable sort'
|
||||
- 'pathetic sort'
|
||||
- 'strange sort'
|
||||
- 'nimble sort'
|
||||
- 'laggardly sort'
|
||||
- 'invisible sort'
|
||||
- 'unfathomable sort'
|
||||
- 'giant sort'
|
||||
- 'sinner'
|
||||
- 'thief'
|
||||
- 'liar'
|
||||
- 'dastard'
|
||||
- 'traitor'
|
||||
- 'pair'
|
||||
- 'trio'
|
||||
- 'noble'
|
||||
- 'aristocrat'
|
||||
- 'hero'
|
||||
- 'champion'
|
||||
- 'monarch'
|
||||
- 'lord'
|
||||
- 'god'
|
||||
|
||||
- name: Things
|
||||
words:
|
||||
- 'item'
|
||||
- 'necessary item'
|
||||
- 'precious item'
|
||||
- 'something'
|
||||
- 'something incredible'
|
||||
- 'treasure chest'
|
||||
- 'corpse'
|
||||
- 'coffin'
|
||||
- 'trap'
|
||||
- 'armament'
|
||||
- 'shield'
|
||||
- 'bow'
|
||||
- 'projectile weapon'
|
||||
- 'armor'
|
||||
- 'talisman'
|
||||
- 'skill'
|
||||
- 'sorcery'
|
||||
- 'incantation'
|
||||
- 'map'
|
||||
- 'material'
|
||||
- 'flower'
|
||||
- 'grass'
|
||||
- 'tree'
|
||||
- 'fruit'
|
||||
- 'seed'
|
||||
- 'mushroom'
|
||||
- 'tear'
|
||||
- 'crystal'
|
||||
- 'butterfly'
|
||||
- 'bug'
|
||||
- 'dung'
|
||||
- 'grace'
|
||||
- 'door'
|
||||
- 'key'
|
||||
- 'ladder'
|
||||
- 'lever'
|
||||
- 'lift'
|
||||
- 'spiritspring'
|
||||
- 'sending gate'
|
||||
- 'stone astrolabe'
|
||||
- 'Birdseye Telescope'
|
||||
- 'message'
|
||||
- 'bloodstain'
|
||||
- 'Erdtree'
|
||||
- 'Elden Ring'
|
||||
|
||||
- name: Battle Tactics
|
||||
words:
|
||||
- 'close-quarters battle'
|
||||
- 'ranged battle'
|
||||
- 'horseback battle'
|
||||
- 'luring out'
|
||||
- 'defeating one-by-one'
|
||||
- 'taking on all at once'
|
||||
- 'rushing in'
|
||||
- 'stealth'
|
||||
- 'mimicry'
|
||||
- 'confusion'
|
||||
- 'pursuit'
|
||||
- 'fleeing'
|
||||
- 'summoning'
|
||||
- 'circling around'
|
||||
- 'jumping off'
|
||||
- 'dashing through'
|
||||
- 'brief respite'
|
||||
|
||||
- name: Actions
|
||||
words:
|
||||
- 'attacking'
|
||||
- 'jump attack'
|
||||
- 'running attack'
|
||||
- 'critical hit'
|
||||
- 'two-handing'
|
||||
- 'blocking'
|
||||
- 'parrying'
|
||||
- 'guard counter'
|
||||
- 'sorcery'
|
||||
- 'incantation'
|
||||
- 'skill'
|
||||
- 'summoning'
|
||||
- 'throwing'
|
||||
- 'healing'
|
||||
- 'running'
|
||||
- 'rolling'
|
||||
- 'backstepping'
|
||||
- 'jumping'
|
||||
- 'crouching'
|
||||
- 'target lock'
|
||||
- 'item crafting'
|
||||
- 'gesturing'
|
||||
|
||||
- name: Situations
|
||||
words:
|
||||
- 'morning'
|
||||
- 'noon'
|
||||
- 'evening'
|
||||
- 'night'
|
||||
- 'clear sky'
|
||||
- 'overcast'
|
||||
- 'rain'
|
||||
- 'storm'
|
||||
- 'mist'
|
||||
- 'snow'
|
||||
- 'patrolling'
|
||||
- 'procession'
|
||||
- 'crowd'
|
||||
- 'surprise attack'
|
||||
- 'ambush'
|
||||
- 'pincer attack'
|
||||
- 'beating to a pulp'
|
||||
- 'battle'
|
||||
- 'reinforcements'
|
||||
- 'ritual'
|
||||
- 'explosion'
|
||||
- 'high spot'
|
||||
- 'defensible spot'
|
||||
- 'climbable spot'
|
||||
- 'bright spot'
|
||||
- 'dark spot'
|
||||
- 'open area'
|
||||
- 'cramped area'
|
||||
- 'hiding place'
|
||||
- 'sniping spot'
|
||||
- 'recon spot'
|
||||
- 'safety'
|
||||
- 'danger'
|
||||
- 'gorgeous view'
|
||||
- 'detour'
|
||||
- 'hidden path'
|
||||
- 'secret passage'
|
||||
- 'shortcut'
|
||||
- 'dead end'
|
||||
- 'looking away'
|
||||
- 'unnoticed'
|
||||
- 'out of stamina'
|
||||
|
||||
- name: Places
|
||||
words:
|
||||
- 'high road'
|
||||
- 'checkpoint'
|
||||
- 'bridge'
|
||||
- 'castle'
|
||||
- 'fort'
|
||||
- 'city'
|
||||
- 'ruins'
|
||||
- 'church'
|
||||
- 'tower'
|
||||
- 'camp site'
|
||||
- 'house'
|
||||
- 'cemetery'
|
||||
- 'underground tomb'
|
||||
- 'tunnel'
|
||||
- 'cave'
|
||||
- 'evergaol'
|
||||
- 'great tree'
|
||||
- 'cellar'
|
||||
- 'surface'
|
||||
- 'underground'
|
||||
- 'forest'
|
||||
- 'river'
|
||||
- 'lake'
|
||||
- 'bog'
|
||||
- 'mountain'
|
||||
- 'valley'
|
||||
- 'cliff'
|
||||
- 'waterside'
|
||||
- 'nest'
|
||||
- 'hole'
|
||||
|
||||
- name: Directions
|
||||
words:
|
||||
- 'east'
|
||||
- 'west'
|
||||
- 'south'
|
||||
- 'north'
|
||||
- 'ahead'
|
||||
- 'behind'
|
||||
- 'left'
|
||||
- 'right'
|
||||
- 'center'
|
||||
- 'up'
|
||||
- 'down'
|
||||
- 'edge'
|
||||
|
||||
- name: Body Parts
|
||||
words:
|
||||
- 'head'
|
||||
- 'stomach'
|
||||
- 'back'
|
||||
- 'arms'
|
||||
- 'legs'
|
||||
- 'rump'
|
||||
- 'tail'
|
||||
- 'core'
|
||||
- 'fingers'
|
||||
|
||||
- name: Affinities
|
||||
words:
|
||||
- 'physical'
|
||||
- 'standard'
|
||||
- 'striking'
|
||||
- 'slashing'
|
||||
- 'piercing'
|
||||
- 'fire'
|
||||
- 'lightning'
|
||||
- 'magic'
|
||||
- 'holy'
|
||||
- 'poison'
|
||||
- 'toxic'
|
||||
- 'scarlet rot'
|
||||
- 'blood loss'
|
||||
- 'frost'
|
||||
- 'sleep'
|
||||
- 'madness'
|
||||
- 'death'
|
||||
|
||||
- name: Concepts
|
||||
words:
|
||||
- 'life'
|
||||
- 'Death'
|
||||
- 'light'
|
||||
- 'dark'
|
||||
- 'stars'
|
||||
- 'fire'
|
||||
- 'Order'
|
||||
- 'chaos'
|
||||
- 'joy'
|
||||
- 'wrath'
|
||||
- 'suffering'
|
||||
- 'sadness'
|
||||
- 'comfort'
|
||||
- 'bliss'
|
||||
- 'misfortune'
|
||||
- 'good fortune'
|
||||
- 'bad luck'
|
||||
- 'hope'
|
||||
- 'despair'
|
||||
- 'victory'
|
||||
- 'defeat'
|
||||
- 'research'
|
||||
- 'faith'
|
||||
- 'abundance'
|
||||
- 'rot'
|
||||
- 'loyalty'
|
||||
- 'injustice'
|
||||
- 'secret'
|
||||
- 'opportunity'
|
||||
- 'pickle'
|
||||
- 'clue'
|
||||
- 'friendship'
|
||||
- 'love'
|
||||
- 'bravery'
|
||||
- 'vigor'
|
||||
- 'fortitude'
|
||||
- 'confidence'
|
||||
- 'distracted'
|
||||
- 'unguarded'
|
||||
- 'introspection'
|
||||
- 'regret'
|
||||
- 'resignation'
|
||||
- 'futility'
|
||||
- 'on the brink'
|
||||
- 'betrayal'
|
||||
- 'revenge'
|
||||
- 'destruction'
|
||||
- 'recklessness'
|
||||
- 'calmness'
|
||||
- 'vigilance'
|
||||
- 'tranquility'
|
||||
- 'sound'
|
||||
- 'tears'
|
||||
- 'sleep'
|
||||
- 'depths'
|
||||
- 'dregs'
|
||||
- 'fear'
|
||||
- 'sacrifice'
|
||||
- 'ruin'
|
||||
|
||||
- name: Phrases
|
||||
words:
|
||||
- 'good luck'
|
||||
- 'look carefully'
|
||||
- 'listen carefully'
|
||||
- 'think carefully'
|
||||
- 'well done'
|
||||
- 'I did it!'
|
||||
- 'I''ve failed...'
|
||||
- 'here!'
|
||||
- 'not here!'
|
||||
- 'don''t you dare!'
|
||||
- 'do it!'
|
||||
- 'I can''t take this...'
|
||||
- 'don''t think'
|
||||
- 'so lonely...'
|
||||
- 'here again...'
|
||||
- 'just getting started'
|
||||
- 'stay calm'
|
||||
- 'keep moving'
|
||||
- 'turn back'
|
||||
- 'give up'
|
||||
- 'don''t give up'
|
||||
- 'help me...'
|
||||
- 'I don''t believe it...'
|
||||
- 'too high up'
|
||||
- 'I want to go home...'
|
||||
- 'it''s like a dream...'
|
||||
- 'seems familiar...'
|
||||
- 'beautiful...'
|
||||
- 'you don''t have the right'
|
||||
- 'are you ready?'
|
|
@ -0,0 +1,17 @@
|
|||
name: FINAL FANTASY XIV
|
||||
id: 7cd9e479-080a-4fec-9511-41a53034c2ad
|
||||
|
||||
templates:
|
||||
- '{0} ahead'
|
||||
- 'Be wary of {0}'
|
||||
- 'Try {0}'
|
||||
- 'Need {0}'
|
||||
- 'Imminent {0}...'
|
||||
- 'Weakness: {0}'
|
||||
- '{0}'
|
||||
- '{0}?'
|
||||
- 'Good Luck'
|
||||
- 'I did it!'
|
||||
- 'Here!'
|
||||
- 'I can''t take this...'
|
||||
- 'Praise the Sun!'
|
|
@ -0,0 +1,95 @@
|
|||
#![feature(let_chains)]
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use sqlx::{Executor, Pool, Sqlite};
|
||||
use sqlx::migrate::Migrator;
|
||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions};
|
||||
use tokio::sync::RwLock;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::pack::Pack;
|
||||
|
||||
mod pack;
|
||||
mod message;
|
||||
mod web;
|
||||
mod util;
|
||||
|
||||
static MIGRATOR: Migrator = sqlx::migrate!();
|
||||
|
||||
pub struct State {
|
||||
pub db: Pool<Sqlite>,
|
||||
pub packs: RwLock<HashMap<Uuid, Pack>>,
|
||||
}
|
||||
|
||||
impl State {
|
||||
pub async fn update_packs(&self) -> Result<()> {
|
||||
let mut packs = HashMap::new();
|
||||
|
||||
let mut dir = tokio::fs::read_dir("packs").await?;
|
||||
while let Ok(Some(entry)) = dir.next_entry().await {
|
||||
if !entry.path().is_file() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match entry.path().extension().and_then(|x| x.to_str()) {
|
||||
Some("yaml") | Some("yml") => {}
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let text = match tokio::fs::read_to_string(entry.path()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
eprintln!("error reading pack: {:#?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match serde_yaml::from_str::<Pack>(&text) {
|
||||
Ok(pack) => {
|
||||
println!("added {}", pack.name);
|
||||
packs.insert(pack.id, pack);
|
||||
}
|
||||
Err(e) => eprintln!("error parsing pack at {:?}: {:#?}", entry.path(), e),
|
||||
}
|
||||
}
|
||||
|
||||
*self.packs.write().await = packs;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let options = SqliteConnectOptions::new();
|
||||
// options.log_statements(LevelFilter::Debug);
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.after_connect(|conn, _| Box::pin(async move {
|
||||
conn.execute(
|
||||
// language=sqlite
|
||||
"PRAGMA foreign_keys = ON;"
|
||||
).await?;
|
||||
Ok(())
|
||||
}))
|
||||
// .connect_with(options.filename(&config.database.path))
|
||||
.connect_with(options.filename("./database.sqlite"))
|
||||
.await
|
||||
.context("could not connect to database")?;
|
||||
MIGRATOR.run(&pool)
|
||||
.await
|
||||
.context("could not run database migrations")?;
|
||||
|
||||
let state = Arc::new(State {
|
||||
db: pool,
|
||||
packs: Default::default(),
|
||||
});
|
||||
|
||||
state.update_packs().await?;
|
||||
|
||||
warp::serve(web::routes(state)).run("127.0.0.1:8080".parse::<SocketAddr>()?).await;
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Message {
|
||||
pub territory: u32,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
|
||||
pub pack_id: Uuid,
|
||||
pub template_1: usize,
|
||||
pub word_1_list: Option<usize>,
|
||||
pub word_1_word: Option<usize>,
|
||||
pub conjugation: Option<usize>,
|
||||
pub template_2: Option<usize>,
|
||||
pub word_2_list: Option<usize>,
|
||||
pub word_2_word: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct VoteMessage {
|
||||
pub post_id: Uuid,
|
||||
pub vote: u8,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DeleteMessage {
|
||||
pub post_id: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct RetrievedMessage {
|
||||
pub id: String,
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub z: f64,
|
||||
pub message: String,
|
||||
pub positive_votes: i32,
|
||||
pub negative_votes: i32,
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Pack {
|
||||
pub name: String,
|
||||
pub id: Uuid,
|
||||
pub templates: Vec<String>,
|
||||
pub conjunctions: Vec<String>,
|
||||
pub words: Vec<WordList>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct WordList {
|
||||
pub name: String,
|
||||
pub words: Vec<String>,
|
||||
}
|
||||
|
||||
impl Pack {
|
||||
pub fn format(&self, template_1_idx: usize, word_1_idx: Option<(usize, usize)>, conjunction: Option<usize>, template_2_idx: Option<usize>, word_2_idx: Option<(usize, usize)>) -> Option<String> {
|
||||
let template_1 = self.templates.get(template_1_idx)?;
|
||||
|
||||
if template_1.contains("{0}") && word_1_idx.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut formatted = if template_1.contains("{0}") && let Some((w1_list, w1_word)) = word_1_idx {
|
||||
let word_1 = self.words.get(w1_list)?.words.get(w1_word)?;
|
||||
template_1.replace("{0}", word_1)
|
||||
} else {
|
||||
template_1.clone()
|
||||
};
|
||||
|
||||
if let Some(conj_idx) = conjunction {
|
||||
if let Some(template_2_idx) = template_2_idx {
|
||||
let conj = self.conjunctions.get(conj_idx)?;
|
||||
if conj.len() > 1 || conj.chars().next().map(|x| !x.is_ascii_punctuation()).unwrap_or(false) {
|
||||
formatted.push('\n');
|
||||
}
|
||||
|
||||
formatted.push_str(conj);
|
||||
formatted.push(' ');
|
||||
|
||||
let template_2 = self.templates.get(template_2_idx)?;
|
||||
if template_2.contains("{0}") && word_2_idx.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let append = if let Some((w2_list, w2_word)) = word_2_idx {
|
||||
let word_2 = self.words.get(w2_list)?.words.get(w2_word)?;
|
||||
template_2.replace("{0}", word_2)
|
||||
} else {
|
||||
template_2.clone()
|
||||
};
|
||||
|
||||
formatted.push_str(&append);
|
||||
}
|
||||
}
|
||||
|
||||
Some(formatted)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
use sha3::{Digest, Sha3_384};
|
||||
|
||||
pub fn hash(input: &str) -> String {
|
||||
let mut hasher = Sha3_384::default();
|
||||
hasher.update(input.as_bytes());
|
||||
let result = hasher.finalize();
|
||||
base64::encode(result)
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
use std::convert::Infallible;
|
||||
use std::sync::Arc;
|
||||
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
use warp::filters::BoxedFilter;
|
||||
use warp::http::StatusCode;
|
||||
use warp::reject::Reject;
|
||||
|
||||
use crate::State;
|
||||
|
||||
mod register;
|
||||
mod unregister;
|
||||
mod write;
|
||||
mod erase;
|
||||
mod get_location;
|
||||
mod vote;
|
||||
|
||||
pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
|
||||
register::register(Arc::clone(&state))
|
||||
.or(unregister::unregister(Arc::clone(&state)))
|
||||
.or(write::write(Arc::clone(&state)))
|
||||
.or(erase::erase(Arc::clone(&state)))
|
||||
.or(get_location::get_location(Arc::clone(&state)))
|
||||
.or(vote::vote(Arc::clone(&state)))
|
||||
.recover(handle_rejection)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
pub fn get_id(state: Arc<State>) -> BoxedFilter<(i64, )> {
|
||||
warp::cookie("access_token")
|
||||
.or(warp::header("x-api-key"))
|
||||
.unify()
|
||||
.and_then(move |access_token: String| {
|
||||
let state = Arc::clone(&state);
|
||||
async move {
|
||||
let hashed = crate::util::hash(&access_token);
|
||||
let id = sqlx::query!(
|
||||
// language=sqlite
|
||||
"select id from users where auth = ?",
|
||||
hashed,
|
||||
)
|
||||
.fetch_optional(&state.db)
|
||||
.await;
|
||||
match id {
|
||||
Ok(Some(i)) => Ok(i.id),
|
||||
Ok(None) => Err(warp::reject::custom(WebError::InvalidAuthToken)),
|
||||
Err(e) => Err(warp::reject::custom(AnyhowRejection(e.into()))),
|
||||
}
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum WebError {
|
||||
InvalidAuthToken,
|
||||
InvalidPackId,
|
||||
InvalidIndex,
|
||||
}
|
||||
|
||||
impl Reject for WebError {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AnyhowRejection(anyhow::Error);
|
||||
|
||||
impl Reject for AnyhowRejection {}
|
||||
|
||||
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
|
||||
let status = if let Some(AnyhowRejection(e)) = err.find::<AnyhowRejection>() {
|
||||
eprintln!("{:#?}", e);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
} else if let Some(e) = err.find::<WebError>() {
|
||||
match e {
|
||||
WebError::InvalidAuthToken => StatusCode::BAD_REQUEST,
|
||||
WebError::InvalidPackId => StatusCode::NOT_FOUND,
|
||||
WebError::InvalidIndex => StatusCode::NOT_FOUND,
|
||||
}
|
||||
} else {
|
||||
eprintln!("{:#?}", err);
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
};
|
||||
|
||||
Ok(warp::reply::with_status(warp::reply(), status))
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use uuid::Uuid;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
use warp::filters::BoxedFilter;
|
||||
|
||||
use crate::State;
|
||||
use crate::web::AnyhowRejection;
|
||||
|
||||
pub fn erase(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
|
||||
warp::delete()
|
||||
.and(warp::path("messages"))
|
||||
.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))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn logic(state: Arc<State>, id: i64, post_id: Uuid) -> Result<impl Reply, Rejection> {
|
||||
let post_id = post_id.simple().to_string();
|
||||
sqlx::query!(
|
||||
// language=sqlite
|
||||
"delete from messages where id = ? and user = ?",
|
||||
post_id,
|
||||
id,
|
||||
)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.context("could not delete message from database")
|
||||
.map_err(AnyhowRejection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Ok(warp::reply())
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
use warp::filters::BoxedFilter;
|
||||
|
||||
use crate::message::RetrievedMessage;
|
||||
use crate::State;
|
||||
use crate::web::AnyhowRejection;
|
||||
|
||||
pub fn get_location(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
|
||||
warp::get()
|
||||
.and(warp::path("messages"))
|
||||
.and(warp::path::param())
|
||||
.and(warp::path::end())
|
||||
.and_then(move |location: u32| logic(Arc::clone(&state), location))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn logic(state: Arc<State>, location: u32) -> Result<impl Reply, Rejection> {
|
||||
let id = location as i64;
|
||||
let messages = sqlx::query_as!(
|
||||
RetrievedMessage,
|
||||
// language=sqlite
|
||||
r#"
|
||||
select m.id,
|
||||
m.x,
|
||||
m.y,
|
||||
m.z,
|
||||
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
|
||||
from messages m
|
||||
left join votes v on m.id = v.message
|
||||
where m.territory = ?
|
||||
group by m.id"#,
|
||||
id,
|
||||
)
|
||||
.fetch_all(&state.db)
|
||||
.await
|
||||
.context("could not get messages from database")
|
||||
.map_err(AnyhowRejection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Ok(warp::reply::json(&messages))
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
use warp::filters::BoxedFilter;
|
||||
|
||||
use crate::State;
|
||||
use crate::web::AnyhowRejection;
|
||||
|
||||
pub fn register(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
|
||||
warp::post()
|
||||
.and(warp::path("account"))
|
||||
.and(warp::path::end())
|
||||
.and_then(move || logic(Arc::clone(&state)))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn logic(state: Arc<State>) -> Result<impl Reply, Rejection> {
|
||||
let auth = Alphanumeric.sample_string(&mut rand::thread_rng(), 32);
|
||||
let hashed = crate::util::hash(&auth);
|
||||
sqlx::query!(
|
||||
// language=sqlite
|
||||
"insert into users (auth) values (?)",
|
||||
hashed,
|
||||
)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.context("could not insert user into database")
|
||||
.map_err(AnyhowRejection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Ok(auth)
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
use warp::filters::BoxedFilter;
|
||||
|
||||
use crate::State;
|
||||
use crate::web::AnyhowRejection;
|
||||
|
||||
pub fn unregister(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
|
||||
warp::delete()
|
||||
.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))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn logic(state: Arc<State>, id: i64) -> Result<impl Reply, Rejection> {
|
||||
sqlx::query!(
|
||||
// language=sqlite
|
||||
"delete from users where id = ?",
|
||||
id,
|
||||
)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.context("could not delete user from database")
|
||||
.map_err(AnyhowRejection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Ok(warp::reply())
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use uuid::Uuid;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
use warp::filters::BoxedFilter;
|
||||
|
||||
use crate::State;
|
||||
use crate::web::AnyhowRejection;
|
||||
|
||||
pub fn vote(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
|
||||
warp::patch()
|
||||
.and(warp::path("messages"))
|
||||
.and(warp::path::param())
|
||||
.and(warp::path("votes"))
|
||||
.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))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn logic(state: Arc<State>, id: i64, message_id: Uuid, vote: i8) -> Result<impl Reply, Rejection> {
|
||||
let message_id = message_id.simple().to_string();
|
||||
let vote = match vote.signum() {
|
||||
-1 => -1,
|
||||
0 => 1,
|
||||
1 => 1,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
sqlx::query!(
|
||||
// language=sqlite
|
||||
"insert or ignore into votes (user, message, vote) values (?, ?, ?)",
|
||||
id,
|
||||
message_id,
|
||||
vote,
|
||||
)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.context("could not insert vote into database")
|
||||
.map_err(AnyhowRejection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Ok(warp::reply())
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use uuid::Uuid;
|
||||
use warp::{Filter, Rejection, Reply};
|
||||
use warp::filters::BoxedFilter;
|
||||
|
||||
use crate::message::Message;
|
||||
use crate::State;
|
||||
use crate::web::{AnyhowRejection, WebError};
|
||||
|
||||
pub fn write(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
|
||||
warp::post()
|
||||
.and(warp::path("messages"))
|
||||
.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))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
async fn logic(state: Arc<State>, id: i64, message: Message) -> Result<impl Reply, Rejection> {
|
||||
let text = {
|
||||
let packs = state.packs.read().await;
|
||||
let pack = packs.get(&message.pack_id)
|
||||
.ok_or(WebError::InvalidPackId)
|
||||
.map_err(warp::reject::custom)?;
|
||||
pack.format(
|
||||
message.template_1,
|
||||
message.word_1_list.and_then(|list| message.word_1_word.map(|word| (list, word))),
|
||||
message.conjugation,
|
||||
message.template_2,
|
||||
message.word_2_list.and_then(|list| message.word_2_word.map(|word| (list, word))),
|
||||
)
|
||||
.ok_or(WebError::InvalidIndex)
|
||||
.map_err(warp::reject::custom)?
|
||||
};
|
||||
|
||||
let message_id = Uuid::new_v4().simple().to_string();
|
||||
let territory = message.territory as i64;
|
||||
|
||||
sqlx::query!(
|
||||
// language=sqlite
|
||||
"insert into messages (id, user, territory, x, y, z, message) values (?, ?, ?, ?, ?, ?, ?)",
|
||||
message_id,
|
||||
id,
|
||||
territory,
|
||||
message.x,
|
||||
message.y,
|
||||
message.z,
|
||||
text,
|
||||
)
|
||||
.execute(&state.db)
|
||||
.await
|
||||
.context("could not insert message into database")
|
||||
.map_err(AnyhowRejection)
|
||||
.map_err(warp::reject::custom)?;
|
||||
Ok(message_id)
|
||||
}
|
Loading…
Reference in New Issue