Add ability to invite people to corps.
This commit is contained in:
parent
8084b020c3
commit
cb05843ee9
@ -814,6 +814,31 @@ impl DBTrans {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn match_user_corp_by_name(&self, corpname: &str, username: &str) ->
|
||||||
|
DResult<Option<(CorpId, Corp, CorpMembership)>> {
|
||||||
|
Ok(match self.pg_trans()?
|
||||||
|
.query_opt("SELECT c.corp_id, c.details AS cdetails, m.details AS mdetails FROM corps c \
|
||||||
|
JOIN corp_membership m ON m.corp_id = c.corp_id \
|
||||||
|
WHERE LOWER(c.details->>'name') LIKE $1 AND \
|
||||||
|
m.member_username = $2 \
|
||||||
|
ORDER BY \
|
||||||
|
ABS(length(c.details->>'name')-length($1)) DESC \
|
||||||
|
LIMIT 1",
|
||||||
|
&[&(corpname.replace("\\", "\\\\")
|
||||||
|
.replace("_", "\\_")
|
||||||
|
.replace("%", "")
|
||||||
|
.to_lowercase() + "%"), &(username.to_lowercase())])
|
||||||
|
.await? {
|
||||||
|
None => None,
|
||||||
|
Some(row) =>
|
||||||
|
Some(
|
||||||
|
(CorpId(row.get("corp_id")),
|
||||||
|
serde_json::from_value(row.get("cdetails"))?,
|
||||||
|
serde_json::from_value(row.get("mdetails"))?)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn create_corp(&self, details: &Corp) -> DResult<CorpId> {
|
pub async fn create_corp(&self, details: &Corp) -> DResult<CorpId> {
|
||||||
let id = self.pg_trans()?
|
let id = self.pg_trans()?
|
||||||
.query_one("INSERT INTO corps (details) VALUES ($1) RETURNING corp_id", &[&serde_json::to_value(details)?]).await?
|
.query_one("INSERT INTO corps (details) VALUES ($1) RETURNING corp_id", &[&serde_json::to_value(details)?]).await?
|
||||||
@ -821,6 +846,15 @@ impl DBTrans {
|
|||||||
Ok(CorpId(id))
|
Ok(CorpId(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn expire_old_invites(&self) -> DResult<()> {
|
||||||
|
self.pg_trans()?
|
||||||
|
.execute(
|
||||||
|
"DELETE FROM corp_membership WHERE details->>'invited_at' <= $1",
|
||||||
|
&[&(Utc::now() - chrono::Duration::hours(4)).to_rfc3339_opts(chrono::SecondsFormat::Nanos, true)]
|
||||||
|
).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn upsert_corp_membership(&self, corp: &CorpId, username: &str, details: &CorpMembership) -> DResult<()> {
|
pub async fn upsert_corp_membership(&self, corp: &CorpId, username: &str, details: &CorpMembership) -> DResult<()> {
|
||||||
self.pg_trans()?
|
self.pg_trans()?
|
||||||
.execute("INSERT INTO corp_membership (corp_id, member_username, details) \
|
.execute("INSERT INTO corp_membership (corp_id, member_username, details) \
|
||||||
@ -837,7 +871,10 @@ impl DBTrans {
|
|||||||
Ok(self.pg_trans()?
|
Ok(self.pg_trans()?
|
||||||
.query("SELECT m.corp_id, c.details->>'name', m.details FROM corp_membership m \
|
.query("SELECT m.corp_id, c.details->>'name', m.details FROM corp_membership m \
|
||||||
JOIN corps c ON c.corp_id = m.corp_id WHERE m.member_username = $1 \
|
JOIN corps c ON c.corp_id = m.corp_id WHERE m.member_username = $1 \
|
||||||
AND m.details->>'joined_at' IS NOT NULL", &[&username.to_lowercase()])
|
AND m.details->>'joined_at' IS NOT NULL \
|
||||||
|
ORDER BY (m.details->>'priority')::int DESC NULLS LAST, \
|
||||||
|
(m.details->>'joined_at') :: TIMESTAMPTZ ASC",
|
||||||
|
&[&username.to_lowercase()])
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|row|
|
.filter_map(|row|
|
||||||
|
@ -15,6 +15,7 @@ mod agree;
|
|||||||
mod allow;
|
mod allow;
|
||||||
pub mod attack;
|
pub mod attack;
|
||||||
mod buy;
|
mod buy;
|
||||||
|
mod corp;
|
||||||
pub mod drop;
|
pub mod drop;
|
||||||
pub mod get;
|
pub mod get;
|
||||||
mod describe;
|
mod describe;
|
||||||
@ -118,6 +119,7 @@ static REGISTERED_COMMANDS: UserVerbRegistry = phf_map! {
|
|||||||
|
|
||||||
"attack" => attack::VERB,
|
"attack" => attack::VERB,
|
||||||
"buy" => buy::VERB,
|
"buy" => buy::VERB,
|
||||||
|
"corp" => corp::VERB,
|
||||||
"drop" => drop::VERB,
|
"drop" => drop::VERB,
|
||||||
"get" => get::VERB,
|
"get" => get::VERB,
|
||||||
"inventory" => inventory::VERB,
|
"inventory" => inventory::VERB,
|
||||||
|
142
blastmud_game/src/message_handler/user_commands/corp.rs
Normal file
142
blastmud_game/src/message_handler/user_commands/corp.rs
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
use super::{
|
||||||
|
VerbContext,
|
||||||
|
UserVerb,
|
||||||
|
UserVerbRef,
|
||||||
|
UResult,
|
||||||
|
user_error,
|
||||||
|
get_user_or_fail,
|
||||||
|
get_player_item_or_fail,
|
||||||
|
search_item_for_user,
|
||||||
|
parsing::parse_command_name,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
models::corp::{CorpMembership, CorpPermission},
|
||||||
|
db::ItemSearchParams,
|
||||||
|
};
|
||||||
|
use chrono::Utc;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use ansi::ansi;
|
||||||
|
|
||||||
|
fn check_corp_perm(perm: &CorpPermission, mem: &CorpMembership) -> bool {
|
||||||
|
mem.permissions.iter().any(|p| *p == CorpPermission::Holder || *p == *perm)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn corp_invite(ctx: &mut VerbContext<'_>, remaining: &str) -> UResult<()> {
|
||||||
|
let (target_raw, into_raw) = match remaining.rsplit_once(" into ") {
|
||||||
|
None => user_error(
|
||||||
|
ansi!("Usage: <bold>corp hire<reset> username <bold>into<reset> corpname").to_owned()
|
||||||
|
)?,
|
||||||
|
Some(c) => c
|
||||||
|
};
|
||||||
|
let user = get_user_or_fail(ctx)?;
|
||||||
|
let player = get_player_item_or_fail(ctx).await?;
|
||||||
|
let (corp_id, corp, mem) =
|
||||||
|
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &user.username).await? {
|
||||||
|
None => user_error("No such corp!".to_owned())?,
|
||||||
|
Some(c) => c
|
||||||
|
};
|
||||||
|
if !check_corp_perm(&CorpPermission::Hire, &mem) || mem.joined_at.is_none() {
|
||||||
|
user_error("You don't have hire permissions for that corp".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_user = search_item_for_user(ctx, &ItemSearchParams {
|
||||||
|
include_loc_contents: true,
|
||||||
|
..ItemSearchParams::base(&player, target_raw.trim())
|
||||||
|
}).await?;
|
||||||
|
|
||||||
|
if target_user.item_type != "player" {
|
||||||
|
user_error("Only players can be hired.".to_owned())?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let (their_sess, their_sess_dat) = match ctx.trans.find_session_for_player(&target_user.item_code).await? {
|
||||||
|
None => user_error("The user needs to be logged in while you hire them.".to_owned())?,
|
||||||
|
Some(c) => c
|
||||||
|
};
|
||||||
|
|
||||||
|
ctx.trans.expire_old_invites().await?;
|
||||||
|
|
||||||
|
match ctx.trans.match_user_corp_by_name(into_raw.trim(), &target_user.item_code).await? {
|
||||||
|
None => (),
|
||||||
|
Some((_, _, CorpMembership { invited_at: Some(_), .. })) =>
|
||||||
|
user_error("They've already been invited.".to_owned())?,
|
||||||
|
Some((_, _, _)) =>
|
||||||
|
user_error("They're already hired.".to_owned())?,
|
||||||
|
};
|
||||||
|
|
||||||
|
let new_mem = CorpMembership {
|
||||||
|
invited_at: Some(Utc::now()),
|
||||||
|
allow_combat: corp.allow_combat_required,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
ctx.trans.upsert_corp_membership(&corp_id, &target_user.item_code, &new_mem).await?;
|
||||||
|
|
||||||
|
ctx.trans.queue_for_session(&their_sess, Some(&format!(
|
||||||
|
ansi!("{} wants to hire you into {}! Type <bold>corp join {}<reset> to accept.{}\n"),
|
||||||
|
&player.display_for_sentence(!their_sess_dat.less_explicit_mode, 1, true),
|
||||||
|
&corp.name,
|
||||||
|
&corp.name,
|
||||||
|
if new_mem.allow_combat {
|
||||||
|
" This corp is configured to allow the leadership to declare war on other corps; if you join, you may be attacked by members of other corps, even without your personal consent."
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
))).await?;
|
||||||
|
ctx.trans.queue_for_session(
|
||||||
|
&ctx.session,
|
||||||
|
Some(&format!(
|
||||||
|
"You offer to hire {} into {}.\n",
|
||||||
|
&target_user.display_for_sentence(!their_sess_dat.less_explicit_mode, 1, false),
|
||||||
|
&corp.name
|
||||||
|
))).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn corp_join(_ctx: &mut VerbContext<'_>, _remaining: &str) -> UResult<()> {
|
||||||
|
user_error("Join not implemented yet".to_owned())?
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn corp_list(ctx: &mut VerbContext<'_>, _remaining: &str) -> UResult<()> {
|
||||||
|
let mut msg = String::new();
|
||||||
|
let user = get_user_or_fail(ctx)?;
|
||||||
|
|
||||||
|
let corps = ctx.trans.get_corp_memberships_for_user(&user.username).await?;
|
||||||
|
if corps.is_empty() {
|
||||||
|
msg.push_str(ansi!(
|
||||||
|
"You don't yet belong to any corps - try <bold>who<reset> to see the \
|
||||||
|
corps of people online, and message someone in a corp to see if they \
|
||||||
|
will hire you! Or buy a new corp licence at the Kings Office in Melbs.\n"
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
msg.push_str("You belong to the following corps:\n");
|
||||||
|
msg.push_str(&format!(ansi!("<bgblue><white><bold>| {:20} | {:7} | {:5} |<reset>\n"),
|
||||||
|
"Name", "Combat?", "Order"));
|
||||||
|
for corp in &corps {
|
||||||
|
match corp {
|
||||||
|
(_, name, CorpMembership { allow_combat: combat, priority: p, .. }) => {
|
||||||
|
msg.push_str(&format!("| {:20} | {:7} | {:5} |\n",
|
||||||
|
name, if *combat { "Y" } else { "N" }, p));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.trans.queue_for_session(ctx.session, Some(&msg)).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Verb;
|
||||||
|
#[async_trait]
|
||||||
|
impl UserVerb for Verb {
|
||||||
|
async fn handle(self: &Self, ctx: &mut VerbContext, _verb: &str, remaining: &str) -> UResult<()> {
|
||||||
|
let (command, remaining) = parse_command_name(remaining);
|
||||||
|
match command.to_lowercase().as_str() {
|
||||||
|
"hire" | "invite" => corp_invite(ctx, remaining).await?,
|
||||||
|
"join" => corp_join(ctx, remaining).await?,
|
||||||
|
"" | "list" => corp_list(ctx, remaining).await?,
|
||||||
|
_ => user_error("Unknown command".to_owned())?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static VERB_INT: Verb = Verb;
|
||||||
|
pub static VERB: UserVerbRef = &VERB_INT as UserVerbRef;
|
@ -1,7 +1,7 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, PartialEq)]
|
||||||
pub enum CorpPermission {
|
pub enum CorpPermission {
|
||||||
Holder, // Implies all permissions.
|
Holder, // Implies all permissions.
|
||||||
Hire,
|
Hire,
|
||||||
@ -12,6 +12,7 @@ pub enum CorpPermission {
|
|||||||
pub struct CorpId(pub i64);
|
pub struct CorpId(pub i64);
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct Corp {
|
pub struct Corp {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
// If true, new members get allow_combat on, and members cannot turn
|
// If true, new members get allow_combat on, and members cannot turn
|
||||||
@ -32,6 +33,7 @@ impl Default for Corp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
pub struct CorpMembership {
|
pub struct CorpMembership {
|
||||||
pub invited_at: Option<DateTime<Utc>>,
|
pub invited_at: Option<DateTime<Utc>>,
|
||||||
pub joined_at: Option<DateTime<Utc>>,
|
pub joined_at: Option<DateTime<Utc>>,
|
||||||
|
@ -66,6 +66,7 @@ CREATE TABLE corp_membership (
|
|||||||
PRIMARY KEY (corp_id, member_username)
|
PRIMARY KEY (corp_id, member_username)
|
||||||
);
|
);
|
||||||
CREATE INDEX corp_membership_by_username ON corp_membership(member_username);
|
CREATE INDEX corp_membership_by_username ON corp_membership(member_username);
|
||||||
|
CREATE INDEX corp_membership_by_invited ON corp_membership((details->>'invited_at'));
|
||||||
|
|
||||||
CREATE TABLE user_consent (
|
CREATE TABLE user_consent (
|
||||||
consenting_user TEXT NOT NULL REFERENCES users(username),
|
consenting_user TEXT NOT NULL REFERENCES users(username),
|
||||||
|
Loading…
Reference in New Issue
Block a user