tangled
alpha
login
or
join now
eldridge.cam
/
cartography
0
fork
atom
Trading card city builder game?
0
fork
atom
overview
issues
pulls
pipelines
add socket messages for watching deck
eldridge.cam
1 month ago
008a37ce
e9ced0a6
verified
This commit was signed with the committer's
known signature
.
eldridge.cam
SSH Key Fingerprint:
SHA256:MAgO4sya2MgvdgUjSGKAO0lQ9X2HQp1Jb+x/Tpeeims=
+439
-84
17 changed files
expand all
collapse all
unified
split
.sqlx
query-240ff3ffa4ed17228f5086f5bddf96b8d4bf1b084ff64e30eec76393f9d85af9.json
query-6ef359bb0fbc129554338b1da2351226e560c969d8a4ec60c850a365a929d36f.json
query-874cf1a7157ea7e92f38f15821d0bd2c0b87498ca3a7c2e5075da8ba21ac7598.json
query-a2bf75bad5e8c991ad1018f31d66d23e04235e2098c1412d266b0020bb2bdbd6.json
query-c6d8c3e06b08f41bdca9bed9d9a4ec85afa461106431b0a71f7a6bf4a8233d32.json
query-ca7449b8b88332d81b20055a751ed8b2ebaf6d109927342d472a4a032603449c.json
query-d7a61570b34e911183eec8a3fd0b0c6ad64ef81a6b0581e5116aa3e0c1a52f2c.json
query-f407778375bbf66b6a52e9436d5ba8dea93f5456ef42d5c102f3c9d273ba853e.json
packages
cartography
src
actor
deck_state
mod.rs
field_state
mod.rs
mod.rs
player_socket
mod.rs
watch_deck.rs
watch_field.rs
api
ws.rs
bus.rs
dto
mod.rs
+34
.sqlx/query-240ff3ffa4ed17228f5086f5bddf96b8d4bf1b084ff64e30eec76393f9d85af9.json
···
1
1
+
{
2
2
+
"db_name": "PostgreSQL",
3
3
+
"query": "SELECT id, tile_type_id, name FROM tiles WHERE id = $1",
4
4
+
"describe": {
5
5
+
"columns": [
6
6
+
{
7
7
+
"ordinal": 0,
8
8
+
"name": "id",
9
9
+
"type_info": "Int8"
10
10
+
},
11
11
+
{
12
12
+
"ordinal": 1,
13
13
+
"name": "tile_type_id",
14
14
+
"type_info": "Text"
15
15
+
},
16
16
+
{
17
17
+
"ordinal": 2,
18
18
+
"name": "name",
19
19
+
"type_info": "Text"
20
20
+
}
21
21
+
],
22
22
+
"parameters": {
23
23
+
"Left": [
24
24
+
"Int8"
25
25
+
]
26
26
+
},
27
27
+
"nullable": [
28
28
+
false,
29
29
+
false,
30
30
+
false
31
31
+
]
32
32
+
},
33
33
+
"hash": "240ff3ffa4ed17228f5086f5bddf96b8d4bf1b084ff64e30eec76393f9d85af9"
34
34
+
}
+39
.sqlx/query-6ef359bb0fbc129554338b1da2351226e560c969d8a4ec60c850a365a929d36f.json
···
1
1
+
{
2
2
+
"db_name": "PostgreSQL",
3
3
+
"query": "\n SELECT id, species_id, name\n FROM citizens\n INNER JOIN card_accounts ON card_accounts.card_id = citizens.id\n WHERE card_accounts.account_id = $1\n ",
4
4
+
"describe": {
5
5
+
"columns": [
6
6
+
{
7
7
+
"ordinal": 0,
8
8
+
"name": "id",
9
9
+
"type_info": "Int8"
10
10
+
},
11
11
+
{
12
12
+
"ordinal": 1,
13
13
+
"name": "species_id",
14
14
+
"type_info": "Text"
15
15
+
},
16
16
+
{
17
17
+
"ordinal": 2,
18
18
+
"name": "name",
19
19
+
"type_info": "Text"
20
20
+
}
21
21
+
],
22
22
+
"parameters": {
23
23
+
"Left": [
24
24
+
{
25
25
+
"Custom": {
26
26
+
"name": "citext",
27
27
+
"kind": "Simple"
28
28
+
}
29
29
+
}
30
30
+
]
31
31
+
},
32
32
+
"nullable": [
33
33
+
false,
34
34
+
false,
35
35
+
false
36
36
+
]
37
37
+
},
38
38
+
"hash": "6ef359bb0fbc129554338b1da2351226e560c969d8a4ec60c850a365a929d36f"
39
39
+
}
+44
.sqlx/query-874cf1a7157ea7e92f38f15821d0bd2c0b87498ca3a7c2e5075da8ba21ac7598.json
···
1
1
+
{
2
2
+
"db_name": "PostgreSQL",
3
3
+
"query": "\n SELECT cards.card_type_id, card_types.class AS \"class: CardClass\"\n FROM cards\n INNER JOIN card_types ON card_types.id = cards.card_type_id\n INNER JOIN card_accounts ON card_accounts.card_id = cards.id\n WHERE cards.id = $1 AND card_accounts.account_id = $2\n ",
4
4
+
"describe": {
5
5
+
"columns": [
6
6
+
{
7
7
+
"ordinal": 0,
8
8
+
"name": "card_type_id",
9
9
+
"type_info": "Text"
10
10
+
},
11
11
+
{
12
12
+
"ordinal": 1,
13
13
+
"name": "class: CardClass",
14
14
+
"type_info": {
15
15
+
"Custom": {
16
16
+
"name": "card_class",
17
17
+
"kind": {
18
18
+
"Enum": [
19
19
+
"tile",
20
20
+
"citizen"
21
21
+
]
22
22
+
}
23
23
+
}
24
24
+
}
25
25
+
}
26
26
+
],
27
27
+
"parameters": {
28
28
+
"Left": [
29
29
+
"Int8",
30
30
+
{
31
31
+
"Custom": {
32
32
+
"name": "citext",
33
33
+
"kind": "Simple"
34
34
+
}
35
35
+
}
36
36
+
]
37
37
+
},
38
38
+
"nullable": [
39
39
+
false,
40
40
+
false
41
41
+
]
42
42
+
},
43
43
+
"hash": "874cf1a7157ea7e92f38f15821d0bd2c0b87498ca3a7c2e5075da8ba21ac7598"
44
44
+
}
+39
.sqlx/query-a2bf75bad5e8c991ad1018f31d66d23e04235e2098c1412d266b0020bb2bdbd6.json
···
1
1
+
{
2
2
+
"db_name": "PostgreSQL",
3
3
+
"query": "\n SELECT id, tile_type_id, name\n FROM tiles\n INNER JOIN card_accounts ON card_accounts.card_id = tiles.id\n WHERE card_accounts.account_id = $1\n ",
4
4
+
"describe": {
5
5
+
"columns": [
6
6
+
{
7
7
+
"ordinal": 0,
8
8
+
"name": "id",
9
9
+
"type_info": "Int8"
10
10
+
},
11
11
+
{
12
12
+
"ordinal": 1,
13
13
+
"name": "tile_type_id",
14
14
+
"type_info": "Text"
15
15
+
},
16
16
+
{
17
17
+
"ordinal": 2,
18
18
+
"name": "name",
19
19
+
"type_info": "Text"
20
20
+
}
21
21
+
],
22
22
+
"parameters": {
23
23
+
"Left": [
24
24
+
{
25
25
+
"Custom": {
26
26
+
"name": "citext",
27
27
+
"kind": "Simple"
28
28
+
}
29
29
+
}
30
30
+
]
31
31
+
},
32
32
+
"nullable": [
33
33
+
false,
34
34
+
false,
35
35
+
false
36
36
+
]
37
37
+
},
38
38
+
"hash": "a2bf75bad5e8c991ad1018f31d66d23e04235e2098c1412d266b0020bb2bdbd6"
39
39
+
}
+28
.sqlx/query-c6d8c3e06b08f41bdca9bed9d9a4ec85afa461106431b0a71f7a6bf4a8233d32.json
···
1
1
+
{
2
2
+
"db_name": "PostgreSQL",
3
3
+
"query": "SELECT name FROM fields WHERE id = $1 AND account_id = $2",
4
4
+
"describe": {
5
5
+
"columns": [
6
6
+
{
7
7
+
"ordinal": 0,
8
8
+
"name": "name",
9
9
+
"type_info": "Text"
10
10
+
}
11
11
+
],
12
12
+
"parameters": {
13
13
+
"Left": [
14
14
+
"Int8",
15
15
+
{
16
16
+
"Custom": {
17
17
+
"name": "citext",
18
18
+
"kind": "Simple"
19
19
+
}
20
20
+
}
21
21
+
]
22
22
+
},
23
23
+
"nullable": [
24
24
+
false
25
25
+
]
26
26
+
},
27
27
+
"hash": "c6d8c3e06b08f41bdca9bed9d9a4ec85afa461106431b0a71f7a6bf4a8233d32"
28
28
+
}
+34
.sqlx/query-ca7449b8b88332d81b20055a751ed8b2ebaf6d109927342d472a4a032603449c.json
···
1
1
+
{
2
2
+
"db_name": "PostgreSQL",
3
3
+
"query": "SELECT id, species_id, name FROM citizens WHERE id = $1",
4
4
+
"describe": {
5
5
+
"columns": [
6
6
+
{
7
7
+
"ordinal": 0,
8
8
+
"name": "id",
9
9
+
"type_info": "Int8"
10
10
+
},
11
11
+
{
12
12
+
"ordinal": 1,
13
13
+
"name": "species_id",
14
14
+
"type_info": "Text"
15
15
+
},
16
16
+
{
17
17
+
"ordinal": 2,
18
18
+
"name": "name",
19
19
+
"type_info": "Text"
20
20
+
}
21
21
+
],
22
22
+
"parameters": {
23
23
+
"Left": [
24
24
+
"Int8"
25
25
+
]
26
26
+
},
27
27
+
"nullable": [
28
28
+
false,
29
29
+
false,
30
30
+
false
31
31
+
]
32
32
+
},
33
33
+
"hash": "ca7449b8b88332d81b20055a751ed8b2ebaf6d109927342d472a4a032603449c"
34
34
+
}
-22
.sqlx/query-d7a61570b34e911183eec8a3fd0b0c6ad64ef81a6b0581e5116aa3e0c1a52f2c.json
···
1
1
-
{
2
2
-
"db_name": "PostgreSQL",
3
3
-
"query": "SELECT name FROM fields WHERE id = $1",
4
4
-
"describe": {
5
5
-
"columns": [
6
6
-
{
7
7
-
"ordinal": 0,
8
8
-
"name": "name",
9
9
-
"type_info": "Text"
10
10
-
}
11
11
-
],
12
12
-
"parameters": {
13
13
-
"Left": [
14
14
-
"Int8"
15
15
-
]
16
16
-
},
17
17
-
"nullable": [
18
18
-
false
19
19
-
]
20
20
-
},
21
21
-
"hash": "d7a61570b34e911183eec8a3fd0b0c6ad64ef81a6b0581e5116aa3e0c1a52f2c"
22
22
-
}
-28
.sqlx/query-f407778375bbf66b6a52e9436d5ba8dea93f5456ef42d5c102f3c9d273ba853e.json
···
1
1
-
{
2
2
-
"db_name": "PostgreSQL",
3
3
-
"query": "\n SELECT id, card_type_id\n FROM cards\n INNER JOIN pack_contents ON pack_contents.card_id = cards.id\n WHERE pack_contents.pack_id = $1\n ",
4
4
-
"describe": {
5
5
-
"columns": [
6
6
-
{
7
7
-
"ordinal": 0,
8
8
-
"name": "id",
9
9
-
"type_info": "Int8"
10
10
-
},
11
11
-
{
12
12
-
"ordinal": 1,
13
13
-
"name": "card_type_id",
14
14
-
"type_info": "Text"
15
15
-
}
16
16
-
],
17
17
-
"parameters": {
18
18
-
"Left": [
19
19
-
"Int8"
20
20
-
]
21
21
-
},
22
22
-
"nullable": [
23
23
-
false,
24
24
-
false
25
25
-
]
26
26
-
},
27
27
-
"hash": "f407778375bbf66b6a52e9436d5ba8dea93f5456ef42d5c102f3c9d273ba853e"
28
28
-
}
+170
packages/cartography/src/actor/deck_state/mod.rs
···
1
1
+
use super::player_socket::Response;
2
2
+
use super::{AddCardToDeck, Unsubscribe};
3
3
+
use crate::bus::{Bus, BusExt};
4
4
+
use crate::db::CardClass;
5
5
+
use kameo::prelude::*;
6
6
+
use serde::{Deserialize, Serialize};
7
7
+
use sqlx::PgPool;
8
8
+
use tokio::sync::mpsc::UnboundedSender;
9
9
+
10
10
+
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
11
11
+
pub struct Tile {
12
12
+
pub id: i64,
13
13
+
pub tile_type_id: String,
14
14
+
pub name: String,
15
15
+
}
16
16
+
17
17
+
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize)]
18
18
+
pub struct Citizen {
19
19
+
pub id: i64,
20
20
+
pub species_id: String,
21
21
+
pub name: String,
22
22
+
}
23
23
+
24
24
+
#[derive(Serialize, Deserialize, Clone, Debug)]
25
25
+
pub struct DeckState {
26
26
+
pub tiles: Vec<Tile>,
27
27
+
pub citizens: Vec<Citizen>,
28
28
+
}
29
29
+
30
30
+
pub struct DeckWatcher {
31
31
+
state: DeckState,
32
32
+
account_id: String,
33
33
+
db: PgPool,
34
34
+
tx: UnboundedSender<Response>,
35
35
+
}
36
36
+
37
37
+
impl Actor for DeckWatcher {
38
38
+
type Args = (PgPool, UnboundedSender<Response>, ActorRef<Bus>, String);
39
39
+
type Error = anyhow::Error;
40
40
+
41
41
+
async fn on_start(
42
42
+
(db, tx, bus, account_id): Self::Args,
43
43
+
actor_ref: ActorRef<Self>,
44
44
+
) -> Result<Self, Self::Error> {
45
45
+
let mut conn = db.acquire().await?;
46
46
+
47
47
+
bus.listen::<AddCardToDeck, _>(&actor_ref).await?;
48
48
+
49
49
+
let tiles = sqlx::query_as!(
50
50
+
Tile,
51
51
+
r#"
52
52
+
SELECT id, tile_type_id, name
53
53
+
FROM tiles
54
54
+
INNER JOIN card_accounts ON card_accounts.card_id = tiles.id
55
55
+
WHERE card_accounts.account_id = $1
56
56
+
"#,
57
57
+
account_id
58
58
+
)
59
59
+
.fetch_all(&mut *conn)
60
60
+
.await?;
61
61
+
let citizens = sqlx::query_as!(
62
62
+
Citizen,
63
63
+
r#"
64
64
+
SELECT id, species_id, name
65
65
+
FROM citizens
66
66
+
INNER JOIN card_accounts ON card_accounts.card_id = citizens.id
67
67
+
WHERE card_accounts.account_id = $1
68
68
+
"#,
69
69
+
account_id
70
70
+
)
71
71
+
.fetch_all(&mut *conn)
72
72
+
.await?;
73
73
+
let state = DeckState { tiles, citizens };
74
74
+
75
75
+
tx.send(Response::PutDeckState(state.clone()))?;
76
76
+
77
77
+
Ok(Self {
78
78
+
state,
79
79
+
db,
80
80
+
tx,
81
81
+
account_id,
82
82
+
})
83
83
+
}
84
84
+
}
85
85
+
86
86
+
impl DeckWatcher {
87
87
+
async fn send_patch(&self, previous: &serde_json::Value) -> anyhow::Result<()> {
88
88
+
let next = serde_json::to_value(&self.state).unwrap();
89
89
+
let patch = json_patch::diff(previous, &next);
90
90
+
self.tx.send(Response::PatchState(patch))?;
91
91
+
Ok(())
92
92
+
}
93
93
+
94
94
+
async fn add_card(&mut self, card_id: i64) -> anyhow::Result<()> {
95
95
+
let mut conn = self.db.acquire().await?;
96
96
+
let card = sqlx::query!(
97
97
+
r#"
98
98
+
SELECT cards.card_type_id, card_types.class AS "class: CardClass"
99
99
+
FROM cards
100
100
+
INNER JOIN card_types ON card_types.id = cards.card_type_id
101
101
+
INNER JOIN card_accounts ON card_accounts.card_id = cards.id
102
102
+
WHERE cards.id = $1 AND card_accounts.account_id = $2
103
103
+
"#,
104
104
+
card_id,
105
105
+
self.account_id
106
106
+
)
107
107
+
.fetch_one(&mut *conn)
108
108
+
.await?;
109
109
+
110
110
+
let previous = serde_json::to_value(&self.state).unwrap();
111
111
+
match card.class {
112
112
+
CardClass::Tile => {
113
113
+
let tile = sqlx::query_as!(
114
114
+
Tile,
115
115
+
"SELECT id, tile_type_id, name FROM tiles WHERE id = $1",
116
116
+
card_id
117
117
+
)
118
118
+
.fetch_one(&mut *conn)
119
119
+
.await?;
120
120
+
self.state.tiles.retain(|tile| tile.id != card_id);
121
121
+
self.state.tiles.push(tile);
122
122
+
}
123
123
+
CardClass::Citizen => {
124
124
+
let citizen = sqlx::query_as!(
125
125
+
Citizen,
126
126
+
"SELECT id, species_id, name FROM citizens WHERE id = $1",
127
127
+
card_id
128
128
+
)
129
129
+
.fetch_one(&mut *conn)
130
130
+
.await?;
131
131
+
self.state.citizens.retain(|citizen| citizen.id != card_id);
132
132
+
self.state.citizens.push(citizen);
133
133
+
}
134
134
+
}
135
135
+
self.send_patch(&previous).await?;
136
136
+
Ok(())
137
137
+
}
138
138
+
}
139
139
+
140
140
+
impl Message<AddCardToDeck> for DeckWatcher {
141
141
+
type Reply = ();
142
142
+
143
143
+
async fn handle(
144
144
+
&mut self,
145
145
+
msg: AddCardToDeck,
146
146
+
_ctx: &mut Context<Self, Self::Reply>,
147
147
+
) -> Self::Reply {
148
148
+
#[expect(
149
149
+
clippy::collapsible_if,
150
150
+
reason = "confusing use of side effects when collapsed"
151
151
+
)]
152
152
+
if msg.account_id == self.account_id {
153
153
+
if let Err(error) = self.add_card(msg.card_id).await {
154
154
+
tracing::error!("{}", error);
155
155
+
panic!("aborting deck watcher: {}", error);
156
156
+
}
157
157
+
}
158
158
+
}
159
159
+
}
160
160
+
161
161
+
impl Message<Unsubscribe> for DeckWatcher {
162
162
+
type Reply = ();
163
163
+
async fn handle(
164
164
+
&mut self,
165
165
+
_msg: Unsubscribe,
166
166
+
ctx: &mut kameo::prelude::Context<Self, Self::Reply>,
167
167
+
) -> Self::Reply {
168
168
+
ctx.stop();
169
169
+
}
170
170
+
}
+8
-3
packages/cartography/src/actor/field_state/mod.rs
···
40
40
db: PgPool,
41
41
tx: UnboundedSender<Response>,
42
42
field_id: i64,
43
43
+
account_id: &str,
43
44
) -> anyhow::Result<Self> {
44
45
let mut conn = db.acquire().await?;
45
46
46
46
-
let field = sqlx::query!("SELECT name FROM fields WHERE id = $1", field_id)
47
47
-
.fetch_one(&mut *conn)
48
48
-
.await?;
47
47
+
let field = sqlx::query!(
48
48
+
"SELECT name FROM fields WHERE id = $1 AND account_id = $2",
49
49
+
field_id,
50
50
+
account_id
51
51
+
)
52
52
+
.fetch_one(&mut *conn)
53
53
+
.await?;
49
54
let tiles = sqlx::query_as!(
50
55
FieldTile,
51
56
r#"
+1
-1
packages/cartography/src/actor/mod.rs
···
1
1
+
pub mod deck_state;
1
2
pub mod field_state;
2
3
pub mod player_socket;
3
4
···
5
6
pub struct Unsubscribe;
6
7
7
8
#[derive(Clone, Debug)]
8
8
-
#[expect(dead_code, reason = "stub for later")]
9
9
pub struct AddCardToDeck {
10
10
pub account_id: String,
11
11
pub card_id: i64,
+10
-3
packages/cartography/src/actor/player_socket/mod.rs
···
1
1
use super::field_state::FieldState;
2
2
-
use crate::actor::Unsubscribe;
2
2
+
use crate::actor::{Unsubscribe, deck_state::DeckState};
3
3
use crate::api::ws::ProtocolV1Message;
4
4
+
use crate::bus::Bus;
4
5
use crate::dto::Account;
5
6
use futures::Stream;
6
7
use json_patch::Patch;
···
14
15
15
16
mod authenticate;
16
17
mod unsubscribe;
18
18
+
mod watch_deck;
17
19
mod watch_field;
18
20
19
21
#[derive(Serialize, Deserialize, Clone, Debug)]
20
22
#[serde(tag = "type", content = "data")]
21
23
pub enum Request {
22
24
Authenticate(String),
25
25
+
WatchDeck,
23
26
WatchField(i64),
24
27
Unsubscribe,
25
28
}
···
29
32
pub enum Response {
30
33
Authenticated(Account),
31
34
PutFieldState(FieldState),
32
32
-
PatchFieldState(Vec<Patch>),
35
35
+
PutDeckState(DeckState),
36
36
+
PatchState(Patch),
33
37
}
34
38
35
39
#[derive(Actor)]
36
40
pub struct PlayerSocket {
37
41
db: PgPool,
42
42
+
bus: ActorRef<Bus>,
38
43
account_id: Option<String>,
39
44
subscriptions: HashMap<Uuid, Recipient<Unsubscribe>>,
40
45
}
41
46
42
47
impl PlayerSocket {
43
43
-
pub fn build(db: PgPool) -> Self {
48
48
+
pub fn build(db: PgPool, bus: ActorRef<Bus>) -> Self {
44
49
Self {
45
50
db,
51
51
+
bus,
46
52
account_id: None,
47
53
subscriptions: HashMap::default(),
48
54
}
···
80
86
let result = match request.data {
81
87
Request::Authenticate(account_id) => self.authenticate(tx, account_id).await,
82
88
Request::WatchField(field_id) => self.watch_field(tx, request.id, field_id).await,
89
89
+
Request::WatchDeck => self.watch_deck(tx, request.id).await,
83
90
Request::Unsubscribe => self.unsubscribe(request.id).await,
84
91
};
85
92
if let Err(error) = result {
+21
packages/cartography/src/actor/player_socket/watch_deck.rs
···
1
1
+
use super::super::Unsubscribe;
2
2
+
use super::super::deck_state::DeckWatcher;
3
3
+
use super::{PlayerSocket, Response};
4
4
+
use kameo::actor::Spawn;
5
5
+
use tokio::sync::mpsc::UnboundedSender;
6
6
+
use uuid::Uuid;
7
7
+
8
8
+
impl PlayerSocket {
9
9
+
pub(super) async fn watch_deck(
10
10
+
&mut self,
11
11
+
tx: UnboundedSender<Response>,
12
12
+
message_id: Uuid,
13
13
+
) -> anyhow::Result<()> {
14
14
+
let account_id = self.require_authentication()?;
15
15
+
let actor =
16
16
+
DeckWatcher::spawn((self.db.clone(), tx, self.bus.clone(), account_id.to_owned()));
17
17
+
let unsubscriber = actor.recipient::<Unsubscribe>();
18
18
+
self.subscriptions.insert(message_id, unsubscriber);
19
19
+
Ok(())
20
20
+
}
21
21
+
}
+4
-2
packages/cartography/src/actor/player_socket/watch_field.rs
···
12
12
message_id: Uuid,
13
13
field_id: i64,
14
14
) -> anyhow::Result<()> {
15
15
-
let _account_id = self.require_authentication()?; // TODO: only allow watching your own fields
16
16
-
let actor = FieldWatcher::spawn(FieldWatcher::build(self.db.clone(), tx, field_id).await?);
15
15
+
let account_id = self.require_authentication()?;
16
16
+
let actor = FieldWatcher::spawn(
17
17
+
FieldWatcher::build(self.db.clone(), tx, field_id, account_id).await?,
18
18
+
);
17
19
let unsubscriber = actor.recipient::<Unsubscribe>();
18
20
self.subscriptions.insert(message_id, unsubscriber);
19
21
Ok(())
+7
-2
packages/cartography/src/api/ws.rs
···
1
1
use crate::actor::player_socket::{PlayerSocket, Request, Response};
2
2
+
use crate::bus::Bus;
2
3
use axum::Extension;
3
4
use axum::extract::ws::{CloseFrame, Message, WebSocket, WebSocketUpgrade};
4
5
use futures::StreamExt;
···
47
48
Closed(#[error(not(source))] CloseFrame),
48
49
}
49
50
50
50
-
pub async fn v1(ws: WebSocketUpgrade, db: Extension<PgPool>) -> axum::response::Response {
51
51
+
pub async fn v1(
52
52
+
ws: WebSocketUpgrade,
53
53
+
db: Extension<PgPool>,
54
54
+
bus: Extension<ActorRef<Bus>>,
55
55
+
) -> axum::response::Response {
51
56
let ws = ws.protocols([JSON_PROTOCOL, MESSAGEPACK_PROTOCOL]);
52
57
let protocol = ws
53
58
.selected_protocol()
···
60
65
let (ws_sender, ws_receiver) = socket.split();
61
66
futures::pin_mut!(ws_sender);
62
67
63
63
-
let actor = PlayerSocket::spawn(PlayerSocket::build((*db).clone()));
68
68
+
let actor = PlayerSocket::spawn(PlayerSocket::build((*db).clone(), (*bus).clone()));
64
69
let result = ws_receiver
65
70
.filter_map(|msg| async move { msg.ok() })
66
71
.map(|msg| match msg {
-1
packages/cartography/src/bus.rs
···
79
79
pub struct Listen<T: Any + Send + Sync>(Recipient<T>);
80
80
pub struct Notify<T: Any + Send + Sync + Clone>(T);
81
81
82
82
-
#[cfg_attr(not(test), expect(dead_code))]
83
82
pub trait BusExt {
84
83
async fn listen<T: Any + Send + Sync, A: Actor + Message<T>>(
85
84
&self,
-22
packages/cartography/src/dto/mod.rs
···
69
69
pub id: i64,
70
70
pub card_type_id: String,
71
71
}
72
72
-
73
73
-
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, ToSchema)]
74
74
-
#[serde(tag = "class")]
75
75
-
#[expect(dead_code)]
76
76
-
pub enum CardData {
77
77
-
#[serde(rename = "Tile")]
78
78
-
Tile(Tile),
79
79
-
#[serde(rename = "Citizen")]
80
80
-
Citizen(Citizen),
81
81
-
}
82
82
-
83
83
-
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, ToSchema)]
84
84
-
pub struct Tile {
85
85
-
pub tile_type_id: String,
86
86
-
pub name: String,
87
87
-
}
88
88
-
89
89
-
#[derive(PartialEq, Clone, Debug, Serialize, Deserialize, ToSchema)]
90
90
-
pub struct Citizen {
91
91
-
pub species_id: String,
92
92
-
pub name: String,
93
93
-
}