Browse Source

postgres!

pbs
jake 4 years ago
parent
commit
8ddbdda0a4
  1. 6
      Cargo.toml
  2. 2
      src/entity/gateway/mod.rs
  3. 111
      src/entity/gateway/postgres/migrations/V0001__initial.sql
  4. 3
      src/entity/gateway/postgres/migrations/mod.rs
  5. 5
      src/entity/gateway/postgres/mod.rs
  6. 688
      src/entity/gateway/postgres/models.rs
  7. 343
      src/entity/gateway/postgres/postgres.rs

6
Cargo.toml

@ -15,7 +15,7 @@ mio-extras = "2.0.5"
crc = "^1.0.0"
bcrypt = "0.4"
threadpool = "1.0"
chrono = "*"
chrono = "0.4.11"
serde = "*"
serde_json = "*"
ron = "*"
@ -29,4 +29,8 @@ thiserror = "1.0.15"
ages-prs = "0.1"
async-trait = "0.1.31"
lazy_static = "1.4.0"
barrel = { version = "0.6.5", features = ["pg"] }
postgres = "0.17.5"
refinery = { version = "0.3.0", features = ["postgres"] }
sqlx = { version = "0.4.0-beta.1", features = ["postgres", "json", "chrono"] }

2
src/entity/gateway/mod.rs

@ -1,5 +1,7 @@
pub mod entitygateway;
pub mod inmemory;
pub mod postgres;
pub use entitygateway::EntityGateway;
pub use inmemory::InMemoryGateway;
pub use self::postgres::PostgresGateway;

111
src/entity/gateway/postgres/migrations/V0001__initial.sql

@ -0,0 +1,111 @@
create table user_accounts (
id serial primary key not null,
name varchar(128) not null unique,
password varchar(128) not null,
banned timestamptz,
muted timestamptz,
created_at timestamptz default current_timestamp not null,
flags integer default 0
);
create table user_settings (
id serial primary key not null,
user_account integer references user_accounts (id) not null,
blocked_users bytea not null,
key_config bytea not null,
joystick_config bytea not null,
option_flags integer not null,
shortcuts bytea not null,
symbol_chats bytea not null,
team_name bytea not null
);
/* TODO: guild card data */
create table player_character (
id serial primary key not null,
user_account integer references user_accounts (id) not null,
slot smallint not null,
name varchar(12) not null,
exp integer not null,
class varchar(12) null,
section_id varchar(12) not null,
costume smallint not null,
skin smallint not null,
face smallint not null,
head smallint not null,
hair smallint not null,
hair_r smallint not null,
hair_g smallint not null,
hair_b smallint not null,
prop_x real not null,
prop_y real not null,
techs bytea not null,
config bytea not null,
infoboard varchar(172) not null,
guildcard varchar(172) not null,
power smallint not null,
mind smallint not null,
def smallint not null,
evade smallint not null,
luck smallint not null,
hp smallint not null,
tp smallint not null,
tech_menu bytea not null,
meseta integer not null,
bank_meseta integer not null
);
create table item (
id serial primary key not null,
item jsonb not null
);
create table item_location (
item integer references item (id) not null,
location jsonb not null,
created_at timestamptz default current_timestamp not null
);
create table weapon_modifier (
weapon integer references item (id) not null,
modifier jsonb not null,
created_at timestamptz default current_timestamp not null
);
create table armor_modifier (
armor integer references item (id) not null,
modifier jsonb not null,
created_at timestamptz default current_timestamp not null
);
create table shield_modifier (
shield integer references item (id) not null,
modifier jsonb not null,
created_at timestamptz default current_timestamp not null
);
create table unit_modifier (
unit integer references item (id) not null,
modifier jsonb not null,
created_at timestamptz default current_timestamp not null
);
create table esweapon_modifier (
esweapon integer references item (id) not null,
modifier jsonb not null,
created_at timestamptz default current_timestamp not null
);
create table mag_modifier (
mag integer references item (id) not null,
modifier jsonb not null,
created_at timestamptz default current_timestamp not null
);

3
src/entity/gateway/postgres/migrations/mod.rs

@ -0,0 +1,3 @@
use refinery::include_migration_mods;
include_migration_mods!("src/entity/gateway/postgres/migrations");

5
src/entity/gateway/postgres/mod.rs

@ -0,0 +1,5 @@
pub mod postgres;
pub mod migrations;
pub mod models;
pub use self::postgres::PostgresGateway;

688
src/entity/gateway/postgres/models.rs

@ -0,0 +1,688 @@
use std::collections::HashMap;
use std::convert::Into;
use serde::{Serialize, Deserialize};
use futures::TryStreamExt;
use libpso::character::{settings, guildcard};
use libpso::util::vec_to_array;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::EntityGateway;
use crate::entity::item::*;
use crate::ship::map::MapArea;
use sqlx::postgres::PgPoolOptions;
use sqlx::Row;
use sqlx::Execute;
use postgres::{Client, NoTls};
#[derive(Debug, sqlx::FromRow)]
pub struct PgUserAccount {
id: i32,
name: String,
password: String,
banned: Option<chrono::DateTime<chrono::Utc>>,
muted: Option<chrono::DateTime<chrono::Utc>>,
created_at: chrono::DateTime<chrono::Utc>,
flags: i32,
}
impl Into<UserAccountEntity> for PgUserAccount {
fn into(self) -> UserAccountEntity {
UserAccountEntity {
id: UserAccountId(self.id as u32),
username: self.name,
password: self.password,
banned_until: self.banned,
muted_until: self.muted,
created_at: self.created_at,
flags: self.flags as u32,
// TOOD
guildcard: self.id as u32 + 1,
team_id: None,
}
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgUserSettings {
id: i32,
user_account: i32,
blocked_users: Vec<u8>, //[u32; 0x1E],
key_config: Vec<u8>, //[u8; 0x16C],
joystick_config: Vec<u8>, //[u8; 0x38],
option_flags: i32,
shortcuts: Vec<u8>, //[u8; 0xA40],
symbol_chats: Vec<u8>, //[u8; 0x4E0],
team_name: Vec<u8>, //[u16; 0x10],
}
impl Into<UserSettingsEntity> for PgUserSettings {
fn into(self) -> UserSettingsEntity {
UserSettingsEntity {
id: UserSettingsId(self.id as u32),
user_id: UserAccountId(self.user_account as u32),
settings: settings::UserSettings {
blocked_users: vec_to_array(self.blocked_users.chunks(4).map(|b| u32::from_le_bytes([b[0], b[1], b[2], b[3]])).collect()),
key_config: vec_to_array(self.key_config),
joystick_config: vec_to_array(self.joystick_config),
option_flags: self.option_flags as u32,
shortcuts: vec_to_array(self.shortcuts),
symbol_chats: vec_to_array(self.symbol_chats),
team_name: vec_to_array(self.team_name.chunks(2).map(|b| u16::from_le_bytes([b[0], b[1]])).collect()),
}
}
}
}
#[derive(sqlx::Type, Debug)]
#[sqlx(rename_all = "lowercase")]
pub enum PgCharacterClass {
HUmar,
HUnewearl,
HUcast,
HUcaseal,
RAmar,
RAmarl,
RAcast,
RAcaseal,
FOmar,
FOmarl,
FOnewm,
FOnewearl,
}
impl Into<CharacterClass> for PgCharacterClass {
fn into(self) -> CharacterClass {
match self {
PgCharacterClass::HUmar => CharacterClass::HUmar,
PgCharacterClass::HUnewearl => CharacterClass::HUnewearl,
PgCharacterClass::HUcast => CharacterClass::HUcast,
PgCharacterClass::HUcaseal => CharacterClass::HUcaseal,
PgCharacterClass::RAmar => CharacterClass::RAmar,
PgCharacterClass::RAmarl => CharacterClass::RAmarl,
PgCharacterClass::RAcast => CharacterClass::RAcast,
PgCharacterClass::RAcaseal => CharacterClass::RAcaseal,
PgCharacterClass::FOmar => CharacterClass::FOmar,
PgCharacterClass::FOmarl => CharacterClass::FOmarl,
PgCharacterClass::FOnewm => CharacterClass::FOnewm,
PgCharacterClass::FOnewearl => CharacterClass::FOnewearl,
}
}
}
impl From<CharacterClass> for PgCharacterClass {
fn from(other: CharacterClass) -> PgCharacterClass {
match other {
CharacterClass::HUmar => PgCharacterClass::HUmar,
CharacterClass::HUnewearl => PgCharacterClass::HUnewearl,
CharacterClass::HUcast => PgCharacterClass::HUcast,
CharacterClass::HUcaseal => PgCharacterClass::HUcaseal,
CharacterClass::RAmar => PgCharacterClass::RAmar,
CharacterClass::RAmarl => PgCharacterClass::RAmarl,
CharacterClass::RAcast => PgCharacterClass::RAcast,
CharacterClass::RAcaseal => PgCharacterClass::RAcaseal,
CharacterClass::FOmar => PgCharacterClass::FOmar,
CharacterClass::FOmarl => PgCharacterClass::FOmarl,
CharacterClass::FOnewm => PgCharacterClass::FOnewm,
CharacterClass::FOnewearl => PgCharacterClass::FOnewearl,
}
}
}
#[derive(sqlx::Type, Debug)]
#[sqlx(rename_all = "lowercase")]
pub enum PgSectionId {
Viridia,
Greenill,
Skyly,
Bluefull,
Purplenum,
Pinkal,
Redria,
Oran,
Yellowboze,
Whitill,
}
impl Into<SectionID> for PgSectionId {
fn into(self) -> SectionID {
match self {
PgSectionId::Viridia => SectionID::Viridia,
PgSectionId::Greenill => SectionID::Greenill,
PgSectionId::Skyly => SectionID::Skyly,
PgSectionId::Bluefull => SectionID::Bluefull,
PgSectionId::Purplenum => SectionID::Purplenum,
PgSectionId::Pinkal => SectionID::Pinkal,
PgSectionId::Redria => SectionID::Redria,
PgSectionId::Oran => SectionID::Oran,
PgSectionId::Yellowboze => SectionID::Yellowboze,
PgSectionId::Whitill => SectionID::Whitill,
}
}
}
impl From<SectionID> for PgSectionId {
fn from(other: SectionID) -> PgSectionId {
match other {
SectionID::Viridia => PgSectionId::Viridia,
SectionID::Greenill => PgSectionId::Greenill,
SectionID::Skyly => PgSectionId::Skyly,
SectionID::Bluefull => PgSectionId::Bluefull,
SectionID::Purplenum => PgSectionId::Purplenum,
SectionID::Pinkal => PgSectionId::Pinkal,
SectionID::Redria => PgSectionId::Redria,
SectionID::Oran => PgSectionId::Oran,
SectionID::Yellowboze => PgSectionId::Yellowboze,
SectionID::Whitill => PgSectionId::Whitill,
}
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgCharacter {
id: i32,
user_account: i32,
pub slot: i16,
name: String,
exp: i32,
class: String,
section_id: String,
costume: i16,
skin: i16,
face: i16,
head: i16,
hair: i16,
hair_r: i16,
hair_g: i16,
hair_b: i16,
prop_x: f32,
prop_y: f32,
techs: Vec<u8>,
config: Vec<u8>,
infoboard: String,
guildcard: String,
power: i16,
mind: i16,
def: i16,
evade: i16,
luck: i16,
hp: i16,
tp: i16,
tech_menu: Vec<u8>,
meseta: i32,
bank_meseta: i32,
}
impl Into<CharacterEntity> for PgCharacter {
fn into(self) -> CharacterEntity {
CharacterEntity {
id: CharacterEntityId(self.id as u32),
user_id: UserAccountId(self.user_account as u32),
slot: self.slot as u32,
name: self.name,
exp: self.exp as u32,
char_class: self.class.parse().unwrap(),
section_id: self.section_id.parse().unwrap(),
appearance: CharacterAppearance {
costume: self.costume as u16,
skin: self.skin as u16,
face: self.face as u16,
head: self.head as u16,
hair: self.hair as u16,
hair_r: self.hair_r as u16,
hair_g: self.hair_g as u16,
hair_b: self.hair_b as u16,
prop_x: self.prop_x,
prop_y: self.prop_y,
},
techs: CharacterTechniques {
techs: self.techs.iter().enumerate().take(19).map(|(i, t)| (tech::Technique::from_value(i as u8), TechLevel(*t)) ).collect()
},
config: CharacterConfig {
raw_data: vec_to_array(self.config)
},
info_board: CharacterInfoboard {
board: libpso::utf8_to_utf16_array!(self.infoboard, 172),
},
guildcard: CharacterGuildCard {
description: self.guildcard,
},
materials: CharacterMaterials {
power: self.power as u32,
mind: self.mind as u32,
def: self.def as u32,
evade: self.evade as u32,
luck: self.luck as u32,
hp: self.hp as u32,
tp: self.tp as u32,
},
tech_menu: CharacterTechMenu {
tech_menu: vec_to_array(self.tech_menu)
},
meseta: self.meseta as u32,
bank_meseta: self.bank_meseta as u32,
}
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgGuildCard {
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgWeapon {
weapon: weapon::WeaponType,
special: Option<weapon::WeaponSpecial>,
grind: u8,
attrs: HashMap<weapon::Attribute, i8>,
tekked: bool,
}
impl From<weapon::Weapon> for PgWeapon {
fn from(other: weapon::Weapon) -> PgWeapon {
PgWeapon {
weapon: other.weapon,
special: other.special,
grind: other.grind,
attrs: other.attrs.iter().flatten().map(|attr| (attr.attr, attr.value)).collect(),
tekked: other.tekked,
}
}
}
impl Into<weapon::Weapon> for PgWeapon {
fn into(self) -> weapon::Weapon {
let mut attrs: [Option<weapon::WeaponAttribute>; 3] = [None; 3];
for (attr, (atype, value)) in attrs.iter_mut().zip(self.attrs.iter()) {
*attr = Some(weapon::WeaponAttribute {
attr: *atype,
value: *value
});
}
weapon::Weapon {
weapon: self.weapon,
special: self.special,
grind: self.grind,
attrs: attrs,
tekked: self.tekked,
modifiers: Vec::new(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgArmor {
armor: armor::ArmorType,
dfp: u8,
evp: u8,
slots: u8,
}
impl From<armor::Armor> for PgArmor {
fn from(other: armor::Armor) -> PgArmor {
PgArmor {
armor: other.armor,
dfp: other.dfp,
evp: other.evp,
slots: other.slots,
}
}
}
impl Into<armor::Armor> for PgArmor {
fn into(self) -> armor::Armor {
armor::Armor {
armor: self.armor,
dfp: self.dfp,
evp: self.evp,
slots: self.slots,
modifiers: Vec::new(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgShield {
shield: shield::ShieldType,
dfp: u8,
evp: u8,
}
impl From<shield::Shield> for PgShield {
fn from(other: shield::Shield) -> PgShield {
PgShield {
shield: other.shield,
dfp: other.dfp,
evp: other.evp,
}
}
}
impl Into<shield::Shield> for PgShield {
fn into(self) -> shield::Shield {
shield::Shield {
shield: self.shield,
dfp: self.dfp,
evp: self.evp,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgUnit {
unit: unit::UnitType,
modifier: Option<unit::UnitModifier>,
}
impl From<unit::Unit> for PgUnit {
fn from(other: unit::Unit) -> PgUnit {
PgUnit {
unit: other.unit,
modifier: other.modifier,
}
}
}
impl Into<unit::Unit> for PgUnit {
fn into(self) -> unit::Unit {
unit::Unit {
unit: self.unit,
modifier: self.modifier,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgTool {
pub tool: tool::ToolType,
}
impl From<tool::Tool> for PgTool {
fn from(other: tool::Tool) -> PgTool {
PgTool {
tool: other.tool,
}
}
}
impl Into<tool::Tool> for PgTool {
fn into(self) -> tool::Tool {
tool::Tool {
tool: self.tool,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgTechDisk {
tech: tech::Technique,
level: u32,
}
impl From<tech::TechniqueDisk> for PgTechDisk {
fn from(other: tech::TechniqueDisk) -> PgTechDisk {
PgTechDisk {
tech: other.tech,
level: other.level,
}
}
}
impl Into<tech::TechniqueDisk> for PgTechDisk {
fn into(self) -> tech::TechniqueDisk {
tech::TechniqueDisk {
tech: self.tech,
level: self.level
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgMag {
mag: mag::MagType,
synchro: u8,
color: u8,
}
impl From<mag::Mag> for PgMag {
fn from(other: mag::Mag) -> PgMag {
PgMag {
mag: other.mag,
synchro: other.synchro,
color: other.color,
}
}
}
impl Into<mag::Mag> for PgMag {
fn into(self) -> mag::Mag {
/*mag::Mag {
mag: self.mag,
synchro: self.synchro,
color: self.color,
def: 500,
pow: 0,
dex: 0,
mnd: 0,
iq: 0,
photon_blast: [None; 3],
class: CharacterClass::HUmar,
id: SectionID::Viridia,
}*/
let mut mag = mag::Mag::baby_mag(self.color as u16);
mag.mag = self.mag;
mag.synchro = self.synchro;
mag
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PgESWeapon {
esweapon: esweapon::ESWeaponType,
special: Option<esweapon::ESWeaponSpecial>,
name: String,
grind: u8,
}
impl From<esweapon::ESWeapon> for PgESWeapon {
fn from(other: esweapon::ESWeapon) -> PgESWeapon {
PgESWeapon {
esweapon: other.esweapon,
special: other.special,
name: other.name,
grind: other.grind,
}
}
}
impl Into<esweapon::ESWeapon> for PgESWeapon {
fn into(self) -> esweapon::ESWeapon {
esweapon::ESWeapon {
esweapon: self.esweapon,
special: self.special,
name: self.name,
grind: self.grind,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PgItemDetail {
Weapon(PgWeapon),
Armor(PgArmor),
Shield(PgShield),
Unit(PgUnit),
Tool(PgTool),
TechDisk(PgTechDisk),
Mag(PgMag),
ESWeapon(PgESWeapon),
}
impl From<ItemDetail> for PgItemDetail {
fn from(other: ItemDetail) -> PgItemDetail {
match other {
ItemDetail::Weapon(weapon) => PgItemDetail::Weapon(weapon.into()),
ItemDetail::Armor(armor) => PgItemDetail::Armor(armor.into()),
ItemDetail::Shield(shield) => PgItemDetail::Shield(shield.into()),
ItemDetail::Unit(unit) => PgItemDetail::Unit(unit.into()),
ItemDetail::Tool(tool) => PgItemDetail::Tool(tool.into()),
ItemDetail::TechniqueDisk(tech_disk) => PgItemDetail::TechDisk(tech_disk.into()),
ItemDetail::Mag(mag) => PgItemDetail::Mag(mag.into()),
ItemDetail::ESWeapon(esweapon) => PgItemDetail::ESWeapon(esweapon.into()),
}
}
}
impl Into<ItemDetail> for PgItemDetail {
fn into(self) -> ItemDetail {
match self {
PgItemDetail::Weapon(weapon) => ItemDetail::Weapon(weapon.into()),
PgItemDetail::Armor(armor) => ItemDetail::Armor(armor.into()),
PgItemDetail::Shield(shield) => ItemDetail::Shield(shield.into()),
PgItemDetail::Unit(unit) => ItemDetail::Unit(unit.into()),
PgItemDetail::Tool(tool) => ItemDetail::Tool(tool.into()),
PgItemDetail::TechDisk(tech_disk) => ItemDetail::TechniqueDisk(tech_disk.into()),
PgItemDetail::Mag(mag) => ItemDetail::Mag(mag.into()),
PgItemDetail::ESWeapon(esweapon) => ItemDetail::ESWeapon(esweapon.into()),
}
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgItem {
pub id: i32,
pub item: sqlx::types::Json<PgItemDetail>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PgItemLocationDetail {
Inventory {
character_id: u32,
slot: usize,
equipped: bool,
},
Bank {
character_id: u32,
name: String,
},
LocalFloor {
character_id: u32,
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
SharedFloor {
map_area: MapArea,
x: f32,
y: f32,
z: f32,
},
Consumed,
FedToMag {
mag: u32,
},
Shop,
}
impl From<ItemLocation> for PgItemLocationDetail {
fn from(other: ItemLocation) -> PgItemLocationDetail {
match other {
ItemLocation::Inventory{character_id, slot, equipped} => PgItemLocationDetail::Inventory{character_id: character_id.0, slot, equipped},
ItemLocation::Bank{character_id, name} => PgItemLocationDetail::Bank{character_id: character_id.0, name: name.0},
ItemLocation::LocalFloor{character_id, map_area, x,y,z} => PgItemLocationDetail::LocalFloor{character_id: character_id.0, map_area, x,y,z},
ItemLocation::SharedFloor{map_area, x,y,z} => PgItemLocationDetail::SharedFloor{map_area, x,y,z},
ItemLocation::Consumed => PgItemLocationDetail::Consumed,
ItemLocation::FedToMag{mag} => PgItemLocationDetail::FedToMag{mag: mag.0},
ItemLocation::Shop => PgItemLocationDetail::Shop,
}
}
}
impl Into<ItemLocation> for PgItemLocationDetail {
fn into(self) -> ItemLocation {
match self {
PgItemLocationDetail::Inventory{character_id, slot, equipped} => ItemLocation::Inventory{character_id: CharacterEntityId(character_id), slot, equipped},
PgItemLocationDetail::Bank{character_id, name} => ItemLocation::Bank{character_id: CharacterEntityId(character_id), name: BankName(name)},
PgItemLocationDetail::LocalFloor{character_id, map_area, x,y,z} => ItemLocation::LocalFloor{character_id: CharacterEntityId(character_id), map_area, x,y,z},
PgItemLocationDetail::SharedFloor{map_area, x,y,z} => ItemLocation::SharedFloor{map_area, x,y,z},
PgItemLocationDetail::Consumed => ItemLocation::Consumed,
PgItemLocationDetail::FedToMag{mag} => ItemLocation::FedToMag{mag: ItemEntityId(mag)},
PgItemLocationDetail::Shop => ItemLocation::Shop,
}
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgItemLocation {
//pub id: i32,
pub location: sqlx::types::Json<PgItemLocationDetail>,
created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum PgMagModifierDetail {
FeedMag(i32),
BankMag,
MagCell(i32),
OwnerChange(CharacterClass, SectionID)
}
impl From<mag::MagModifier> for PgMagModifierDetail {
fn from(other: mag::MagModifier) -> PgMagModifierDetail {
match other {
mag::MagModifier::FeedMag{food} => PgMagModifierDetail::FeedMag(food.0 as i32),
mag::MagModifier::BankMag => PgMagModifierDetail::BankMag,
mag::MagModifier::MagCell(cell) => PgMagModifierDetail::MagCell(cell.0 as i32),
mag::MagModifier::OwnerChange(class, section_id) => PgMagModifierDetail::OwnerChange(class, section_id),
}
}
}
impl Into<mag::MagModifier> for PgMagModifierDetail {
fn into(self) -> mag::MagModifier {
match self {
PgMagModifierDetail::FeedMag(food) => mag::MagModifier::FeedMag{food: ItemEntityId(food as u32)},
PgMagModifierDetail::BankMag => mag::MagModifier::BankMag,
PgMagModifierDetail::MagCell(cell) => mag::MagModifier::MagCell(ItemEntityId(cell as u32)),
PgMagModifierDetail::OwnerChange(class, section_id) => mag::MagModifier::OwnerChange(class, section_id),
}
}
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgMagModifier {
mag: i32,
pub modifier: sqlx::types::Json<PgMagModifierDetail>,
created_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgItemWithLocation {
pub id: i32,
pub item: sqlx::types::Json<PgItemDetail>,
pub location: sqlx::types::Json<PgItemLocationDetail>,
}
#[derive(Debug, sqlx::FromRow)]
pub struct PgMagModifierWithParameters {
pub mag: i32,
pub modifier: sqlx::types::Json<PgMagModifierDetail>,
pub feed: Option<sqlx::types::Json<PgTool>>,
pub cell: Option<sqlx::types::Json<PgTool>>,
}

343
src/entity/gateway/postgres/postgres.rs

@ -0,0 +1,343 @@
use std::convert::{From, TryFrom, Into, TryInto};
use serde::{Serialize, Deserialize};
use futures::future::join_all;
use futures::TryStreamExt;
//use futures::StreamExt;
use async_std::stream::StreamExt;
//use futures::StreamExt;
use libpso::character::{settings, guildcard};
use libpso::util::vec_to_array;
use crate::entity::account::*;
use crate::entity::character::*;
use crate::entity::gateway::EntityGateway;
use crate::entity::item::*;
use super::models::*;
use sqlx::postgres::PgPoolOptions;
use sqlx::Row;
use sqlx::Execute;
use postgres::{Client, NoTls};
mod embedded {
use refinery::embed_migrations;
embed_migrations!("src/entity/gateway/postgres/migrations");
}
#[derive(Clone)]
pub struct PostgresGateway {
pool: sqlx::Pool<sqlx::Postgres>,
}
impl PostgresGateway {
pub fn new(host: &str, dbname: &str, username: &str, password: &str) -> PostgresGateway {
// the postgres dep can be removed once refinery supports sqlx
let mut conn = Client::connect(&format!("host='{}' dbname='{}' user='{}' password='{}'", host, dbname, username, password), NoTls).unwrap();
embedded::migrations::runner().run(&mut conn).unwrap();
let pool = async_std::task::block_on(async move {
let pool = PgPoolOptions::new()
.max_connections(5)
.connect(&format!("postgresql://{}:{}@{}:5432/{}", username, password, host, dbname)).await.unwrap();
pool
});
PostgresGateway {
pool: pool,
}
}
async fn apply_item_modifications(&self, item: ItemEntity) -> ItemEntity {
let ItemEntity {id, item, location} = item;
let item = match item {
ItemDetail::Mag(mut mag) => {
let q = r#"select mag, modifier, item.item -> 'Tool' as feed, item2.item -> 'Tool' as cell
from mag_modifier
left join item on item.id = cast (modifier ->> 'FeedMag' as integer)
left join item as item2 on item2.id = cast (modifier ->> 'MagCell' as integer)
where mag = $1 order by created_at"#;
let mag_modifiers = sqlx::query_as::<_, PgMagModifierWithParameters>(q)
.bind(id.0 as i32)
.fetch(&self.pool);
mag_modifiers.for_each(|modifier| {
let PgMagModifierWithParameters {modifier, feed, cell, ..} = modifier.unwrap();
let modifier: mag::MagModifier = modifier.0.into();
match modifier {
mag::MagModifier::FeedMag{..} => {
mag.feed(feed.unwrap().tool)
},
mag::MagModifier::BankMag => {
mag.bank()
},
mag::MagModifier::MagCell(_) => {
mag.apply_mag_cell(mag::MagCell::try_from(Into::<tool::Tool>::into(cell.unwrap().0).tool).unwrap())
},
mag::MagModifier::OwnerChange(class, section_id) => {
mag.change_owner(class, section_id)
},
}
}).await;
ItemDetail::Mag(mag)
},
item @ _ => item
};
ItemEntity {
id: id,
item: item,
location: location
}
}
}
#[async_trait::async_trait]
impl EntityGateway for PostgresGateway {
async fn create_user(&mut self, user: NewUserAccountEntity) -> Option<UserAccountEntity> {
let new_user = sqlx::query_as::<_, PgUserAccount>("insert into user_accounts (name, password) values ($1, $2) returning *;")
.bind(user.username)
.bind(user.password)
.fetch_one(&self.pool).await.unwrap();
Some(new_user.into())
}
async fn get_user_by_id(&self, id: UserAccountId) -> Option<UserAccountEntity> {
let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where id = $1")
.bind(id.0)
.fetch_one(&self.pool).await.unwrap();
Some(user.into())
}
async fn get_user_by_name(&self, username: String) -> Option<UserAccountEntity> {
let user = sqlx::query_as::<_, PgUserAccount>("select * from user_accounts where name = $1")
.bind(username)
.fetch_one(&self.pool).await.unwrap();
Some(user.into())
}
async fn save_user(&mut self, user: &UserAccountEntity) {
sqlx::query("UPDATE user_accounts set name=$1, password=$2, banned=$3, muted=$4, flags=$5 where id=$6")
.bind(&user.username)
.bind(&user.password)
.bind(&user.banned_until)
.bind(&user.muted_until)
.bind(&user.flags)
.bind(&user.id.0)
.fetch_one(&self.pool).await.unwrap();
}
async fn create_user_settings(&mut self, settings: NewUserSettingsEntity) -> Option<UserSettingsEntity> {
let new_settings = sqlx::query_as::<_, PgUserSettings>("insert into user_settings (user_account, blocked_users, key_config, joystick_config, option_flags, shortcuts, symbol_chats, team_name)
values ($1, $2, $3, $4, $5, $6, $7, $8) returning *;")
.bind(settings.user_id.0)
.bind(settings.settings.blocked_users.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
.bind(settings.settings.key_config.to_vec())
.bind(settings.settings.joystick_config.to_vec())
.bind(settings.settings.option_flags as i32)
.bind(settings.settings.shortcuts.to_vec())
.bind(settings.settings.symbol_chats.to_vec())
.bind(settings.settings.team_name.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
.fetch_one(&self.pool).await.unwrap();
Some(new_settings.into())
}
async fn get_user_settings_by_user(&self, user: &UserAccountEntity) -> Option<UserSettingsEntity> {
let settings = sqlx::query_as::<_, PgUserSettings>("select * from user_settings where id = $1")
.bind(user.id.0)
.fetch_one(&self.pool).await.unwrap();
Some(settings.into())
}
async fn save_user_settings(&mut self, settings: &UserSettingsEntity) {
sqlx::query("update user_settings set blocked_users=$1, key_config=$2, joystick_config=$3, option_flags=$4, shortcuts=$5, symbol_chats=$6, team_name=$7 where id=$8")
.bind(settings.settings.blocked_users.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
.bind(&settings.settings.key_config.to_vec())
.bind(&settings.settings.joystick_config.to_vec())
.bind(&settings.settings.option_flags)
.bind(&settings.settings.shortcuts.to_vec())
.bind(&settings.settings.symbol_chats.to_vec())
.bind(settings.settings.team_name.to_vec().into_iter().map(|i| i.to_le_bytes().to_vec()).flatten().collect::<Vec<u8>>())
.bind(&settings.id.0)
.fetch_one(&self.pool).await.unwrap();
}
async fn create_character(&mut self, char: NewCharacterEntity) -> Option<CharacterEntity> {
let q = r#"insert into player_character
(user_account, slot, name, exp, class, section_id, costume, skin, face, head, hair, hair_r, hair_g, hair_b, prop_x, prop_y, techs,
config, infoboard, guildcard, power, mind, def, evade, luck, hp, tp, tech_menu, meseta, bank_meseta)
values
($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30)
returning *;"#;
let character = sqlx::query_as::<_, PgCharacter>(q)
//sqlx::query(q)
.bind(char.user_id.0)
.bind(char.slot as i16)
.bind(char.name)
.bind(char.exp as i32)
.bind(char.char_class.to_string())
.bind(char.section_id.to_string())
.bind(char.appearance.costume as i16)
.bind(char.appearance.skin as i16)
.bind(char.appearance.face as i16)
.bind(char.appearance.head as i16)
.bind(char.appearance.hair as i16)
.bind(char.appearance.hair_r as i16)
.bind(char.appearance.hair_g as i16)
.bind(char.appearance.hair_b as i16)
.bind(char.appearance.prop_x)
.bind(char.appearance.prop_y)
.bind(&char.techs.as_bytes().to_vec())
.bind(&char.config.as_bytes().to_vec())
.bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0)))
.bind(char.guildcard.description)
.bind(char.materials.power as i16)
.bind(char.materials.mind as i16)
.bind(char.materials.def as i16)
.bind(char.materials.evade as i16)
.bind(char.materials.luck as i16)
.bind(char.materials.hp as i16)
.bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec())
.bind(char.meseta as i32)
.bind(char.bank_meseta as i32)
.fetch_one(&self.pool).await.unwrap();
Some(character.into())
}
async fn get_characters_by_user(&self, user: &UserAccountEntity) -> [Option<CharacterEntity>; 4] {
let mut stream = sqlx::query_as::<_, PgCharacter>("select * from player_character where user_account = $1 and slot < 4 order by slot")
.bind(user.id.0)
.fetch(&self.pool);
let mut result = [None; 4];
while let Some(character) = stream.try_next().await.unwrap() {
let index = character.slot as usize;
result[index] = Some(character.into())
}
result
}
async fn save_character(&mut self, char: &CharacterEntity) {
let q = r#"update player_character set
user_account=$1, slot=$2, name=$3, exp=$4, class=$5, section_id=$6, costume=$7, skin=$8, face=$9, head=$10, hair=$11, hair_r=$12,
hair_g=$13, hair_b=$14, prop_x=$15, prop_y=$16, techs=$17, config=$18, infoboard=$19, guildcard=$20, power=$21, mind=$22, def=$23,
evade=$24, luck=$25, hp=$26, tp=$27, tech_menu=$28, meseta=$29, bank_meseta=$30
where id=$31;"#;
sqlx::query(q)
.bind(char.user_id.0)
.bind(char.slot as i16)
.bind(&char.name)
.bind(char.exp as i32)
.bind(char.char_class.to_string())
.bind(char.section_id.to_string())
.bind(char.appearance.costume as i16)
.bind(char.appearance.skin as i16)
.bind(char.appearance.face as i16)
.bind(char.appearance.head as i16)
.bind(char.appearance.hair as i16)
.bind(char.appearance.hair_r as i16)
.bind(char.appearance.hair_g as i16)
.bind(char.appearance.hair_b as i16)
.bind(char.appearance.prop_x)
.bind(char.appearance.prop_y)
.bind(&char.techs.as_bytes().to_vec())
.bind(&char.config.as_bytes().to_vec())
.bind(String::from_utf16_lossy(&char.info_board.board).trim_matches(char::from(0)))
.bind(&char.guildcard.description)
.bind(char.materials.power as i16)
.bind(char.materials.mind as i16)
.bind(char.materials.def as i16)
.bind(char.materials.evade as i16)
.bind(char.materials.luck as i16)
.bind(char.materials.hp as i16)
.bind(char.materials.tp as i16)
.bind(char.tech_menu.tech_menu.to_vec())
.bind(char.meseta as i32)
.bind(char.bank_meseta as i32)
.bind(char.id.0 as i32)
.execute(&self.pool).await.unwrap();
}
async fn get_guild_card_data_by_user(&self, user: &UserAccountEntity) -> GuildCardDataEntity {
GuildCardDataEntity {
id: GuildCardDataId(0),
user_id: user.id,
guildcard: guildcard::GuildCardData::default(),
}
}
async fn create_item(&mut self, item: NewItemEntity) -> Option<ItemEntity> {
let new_item = sqlx::query_as::<_, PgItem>("insert into item (item) values ($1) returning *;")
.bind(sqlx::types::Json(PgItemDetail::from(item.item)))
.fetch_one(&self.pool).await.unwrap();
let location = sqlx::query_as::<_, PgItemLocation>("insert into item_location (item, location) values ($1, $2) returning *;")
.bind(new_item.id)
.bind(sqlx::types::Json(PgItemLocationDetail::from(item.location)))
.fetch_one(&self.pool).await.unwrap();
Some(ItemEntity {
id: ItemEntityId(new_item.id as u32),
item: new_item.item.0.into(),
location: location.location.0.into(),
})
}
async fn change_item_location(&mut self, item_id: &ItemEntityId, item_location: ItemLocation) {
sqlx::query("insert into item_location (item, location) values ($1, $2);")
.bind(item_id.0)
.bind(sqlx::types::Json(PgItemLocationDetail::from(item_location)))
.fetch_one(&self.pool).await.unwrap();
}
async fn feed_mag(&mut self, mag_item_id: &ItemEntityId, tool_item_id: &ItemEntityId) {
sqlx::query("insert into item_location (item, location) values ($1, $2);")
.bind(tool_item_id.0)
.bind(sqlx::types::Json(PgItemLocationDetail::from(ItemLocation::FedToMag {mag: *mag_item_id})))
.execute(&self.pool).await.unwrap();
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::FeedMag{food: *tool_item_id})))
.execute(&self.pool).await.unwrap();
}
async fn change_mag_owner(&mut self, mag_item_id: &ItemEntityId, character: &CharacterEntity) {
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::OwnerChange(character.char_class, character.section_id))))
.execute(&self.pool).await.unwrap();
}
async fn use_mag_cell(&mut self, mag_item_id: &ItemEntityId, mag_cell_id: &ItemEntityId) {
// consume cell?
sqlx::query("insert into mag_modifier (mag, modifier) values ($1, $2);")
.bind(mag_item_id.0)
.bind(sqlx::types::Json(PgMagModifierDetail::from(mag::MagModifier::MagCell(*mag_cell_id))))
.execute(&self.pool).await.unwrap();
}
async fn get_items_by_character(&self, char: &CharacterEntity) -> Vec<ItemEntity> {
let q = r#"select item.id, item_location.location, item.item from item_location
join item on item.id = item_location.item
where cast (location -> 'Inventory' ->> 'character_id' as integer) = $1
or cast (location -> 'Bank' ->> 'character_id' as integer) = $1"#;
let items = sqlx::query_as::<_, PgItemWithLocation>(q)
.bind(char.id.0)
.fetch(&self.pool);
join_all(items
.filter_map(|item: Result<PgItemWithLocation, _>| {
let item = item.ok()?;
Some(ItemEntity {
id: ItemEntityId(item.id as u32),
item: item.item.0.into(),
location: item.location.0.into()
})
})
.map(|item: ItemEntity| {
self.apply_item_modifications(item)
})
.collect::<Vec<_>>()
.await).await
}
}
Loading…
Cancel
Save