improvements
This commit is contained in:
@@ -12,15 +12,16 @@ export class State {
|
|||||||
turn = $state(-1); // -1 not my turn, 0 might be, 1 is
|
turn = $state(-1); // -1 not my turn, 0 might be, 1 is
|
||||||
socket: Socket;
|
socket: Socket;
|
||||||
|
|
||||||
constructor(hostname: string) {
|
constructor(hostname: string, newSession: boolean = true) {
|
||||||
let session = sessionStorage.getItem('session');
|
// let session = sessionStorage.getItem('session');
|
||||||
|
|
||||||
this.socket = io(`ws://${hostname}:3000/`, {
|
this.socket = io(`ws://${hostname}:3000/`, {
|
||||||
transports: ['websocket'],
|
transports: ['websocket'],
|
||||||
auth: { session }
|
auth: { session: !newSession ? sessionStorage.getItem('session') : null }
|
||||||
});
|
});
|
||||||
|
|
||||||
this.socket.on('connect', () => {
|
this.socket.on('connect', () => {
|
||||||
|
console.log(this.socket.id);
|
||||||
sessionStorage.setItem('session', this.socket.id!);
|
sessionStorage.setItem('session', this.socket.id!);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -43,7 +43,7 @@
|
|||||||
class="btn btn-error text-xl"
|
class="btn btn-error text-xl"
|
||||||
onclick={() => {
|
onclick={() => {
|
||||||
gameState.socket.emit('leave');
|
gameState.socket.emit('leave');
|
||||||
gameState = new State(window.location.hostname);
|
gameState = new State(window.location.hostname, true);
|
||||||
}}>Leave</button
|
}}>Leave</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -4,7 +4,9 @@ CREATE TYPE STAT AS ENUM ('waiting', 'p1turn', 'p2turn');
|
|||||||
CREATE TABLE IF NOT EXISTS players (
|
CREATE TABLE IF NOT EXISTS players (
|
||||||
id CHAR(16) PRIMARY KEY,
|
id CHAR(16) PRIMARY KEY,
|
||||||
board CHAR(10) [10],
|
board CHAR(10) [10],
|
||||||
room_code CHAR(4) NOT NULL
|
room_code CHAR(4) NOT NULL,
|
||||||
|
abandoned BOOLEAN DEFAULT FALSE NOT NULL,
|
||||||
|
time TIMESTAMP DEFAULT NOW() NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS rooms (
|
CREATE TABLE IF NOT EXISTS rooms (
|
||||||
@@ -24,12 +26,6 @@ CREATE TABLE IF NOT EXISTS rooms (
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS abandoned_players (
|
|
||||||
time TIMESTAMP PRIMARY KEY,
|
|
||||||
id CHAR(16) NOT NULL,
|
|
||||||
CONSTRAINT fk_player_id FOREIGN KEY (id) REFERENCES players (id) ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE players
|
ALTER TABLE players
|
||||||
ADD CONSTRAINT fk_room_code FOREIGN KEY (room_code) REFERENCES rooms (code) ON DELETE
|
ADD CONSTRAINT fk_room_code FOREIGN KEY (room_code) REFERENCES rooms (code) ON DELETE
|
||||||
SET NULL;
|
SET NULL;
|
||||||
@@ -40,22 +36,5 @@ SET NULL ON UPDATE CASCADE,
|
|||||||
ADD CONSTRAINT fk_player2 FOREIGN KEY (player2_id) REFERENCES players (id) ON DELETE
|
ADD CONSTRAINT fk_player2 FOREIGN KEY (player2_id) REFERENCES players (id) ON DELETE
|
||||||
SET NULL ON UPDATE CASCADE;
|
SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
-- delete room if both players are null
|
|
||||||
CREATE OR REPLACE FUNCTION delete_room() RETURNS TRIGGER AS $$ BEGIN IF (
|
|
||||||
SELECT player1_id IS NULL
|
|
||||||
AND player2_id IS NULL
|
|
||||||
FROM rooms
|
|
||||||
WHERE code = OLD.room_code
|
|
||||||
) THEN
|
|
||||||
DELETE FROM rooms
|
|
||||||
WHERE code = OLD.room_code;
|
|
||||||
END IF;
|
|
||||||
RETURN OLD;
|
|
||||||
END;
|
|
||||||
$$ LANGUAGE plpgsql;
|
|
||||||
|
|
||||||
CREATE TRIGGER delete_room_trigger
|
|
||||||
AFTER DELETE ON players FOR EACH ROW EXECUTE FUNCTION delete_room();
|
|
||||||
|
|
||||||
CREATE INDEX idx_player_room_code ON players (room_code);
|
CREATE INDEX idx_player_room_code ON players (room_code);
|
||||||
CREATE INDEX idx_room_status ON rooms (stat);
|
CREATE INDEX idx_room_status ON players (abandoned);
|
39
migrations/0002_triggers.sql
Normal file
39
migrations/0002_triggers.sql
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
-- delete room if both players are null
|
||||||
|
CREATE OR REPLACE FUNCTION delete_room() RETURNS TRIGGER AS $$ BEGIN IF (
|
||||||
|
SELECT player1_id IS NULL
|
||||||
|
AND player2_id IS NULL
|
||||||
|
FROM rooms
|
||||||
|
WHERE code = OLD.room_code
|
||||||
|
) THEN
|
||||||
|
DELETE FROM rooms
|
||||||
|
WHERE code = OLD.room_code;
|
||||||
|
END IF;
|
||||||
|
RETURN OLD;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER delete_room_trigger
|
||||||
|
AFTER DELETE ON players FOR EACH ROW EXECUTE FUNCTION delete_room();
|
||||||
|
|
||||||
|
|
||||||
|
-- retain only 1000 recent abandoned players according to timestamp
|
||||||
|
CREATE OR REPLACE FUNCTION delete_player() RETURNS TRIGGER AS $$ BEGIN IF (
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM players
|
||||||
|
WHERE abandoned = TRUE
|
||||||
|
) > 10000 THEN
|
||||||
|
DELETE FROM players
|
||||||
|
WHERE id IN (
|
||||||
|
SELECT id
|
||||||
|
FROM players
|
||||||
|
WHERE abandoned = TRUE
|
||||||
|
ORDER BY time DESC OFFSET 10000
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
RETURN OLD;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
CREATE TRIGGER delete_player_trigger
|
||||||
|
AFTER
|
||||||
|
INSERT ON players FOR EACH ROW EXECUTE FUNCTION delete_player();
|
56
src/game.rs
56
src/game.rs
@@ -1,3 +1,4 @@
|
|||||||
|
use rand::Rng;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use socketioxide::socket::Sid;
|
use socketioxide::socket::Sid;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -20,6 +21,8 @@ pub enum Error {
|
|||||||
NotInRoom,
|
NotInRoom,
|
||||||
#[error("Invalid Move")]
|
#[error("Invalid Move")]
|
||||||
InvalidMove,
|
InvalidMove,
|
||||||
|
#[error("Code Generation Limit Reached")]
|
||||||
|
CodeGenerationLimitReached,
|
||||||
#[error("SQL Error\n{0:?}")]
|
#[error("SQL Error\n{0:?}")]
|
||||||
Sqlx(#[from] sqlx::Error),
|
Sqlx(#[from] sqlx::Error),
|
||||||
}
|
}
|
||||||
@@ -41,8 +44,28 @@ pub async fn room_if_player_exists(sid: &str, pool: &sqlx::PgPool) -> Result<Opt
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> {
|
async fn generate_code(pool: &sqlx::PgPool) -> Result<String> {
|
||||||
|
for _ in 0..50 {
|
||||||
|
let code: String = rand::thread_rng()
|
||||||
|
.sample_iter(&rand::distributions::Alphanumeric)
|
||||||
|
.take(ROOM_CODE_LENGTH)
|
||||||
|
.map(|x| char::to_ascii_uppercase(&(x as char)))
|
||||||
|
.collect();
|
||||||
|
if sqlx::query!(r"SELECT code FROM rooms WHERE code = $1", code)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await?
|
||||||
|
.is_none()
|
||||||
|
{
|
||||||
|
return Ok(code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(Error::CodeGenerationLimitReached)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_room(sid: Sid, pool: &sqlx::PgPool) -> Result<String> {
|
||||||
delete_sid(sid.as_str(), pool).await?;
|
delete_sid(sid.as_str(), pool).await?;
|
||||||
|
let code = generate_code(&pool).await?;
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r"WITH new_user AS (INSERT INTO players (id, room_code) VALUES ($1, $2) RETURNING id) INSERT INTO rooms (player1_id, code) SELECT $1, $2 FROM new_user",
|
r"WITH new_user AS (INSERT INTO players (id, room_code) VALUES ($1, $2) RETURNING id) INSERT INTO rooms (player1_id, code) SELECT $1, $2 FROM new_user",
|
||||||
sid.as_str(),
|
sid.as_str(),
|
||||||
@@ -50,7 +73,7 @@ pub async fn add_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()>
|
|||||||
)
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> {
|
pub async fn join_room(sid: Sid, code: String, pool: &sqlx::PgPool) -> Result<()> {
|
||||||
@@ -286,7 +309,11 @@ pub async fn attack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_sid(oldsid: &str, newsid: &str, pool: &sqlx::PgPool) -> Result<()> {
|
pub async fn update_sid(oldsid: &str, newsid: &str, pool: &sqlx::PgPool) -> Result<()> {
|
||||||
sqlx::query!(r"UPDATE players SET id = $1 WHERE id = $2", newsid, oldsid)
|
sqlx::query!(
|
||||||
|
r"UPDATE players SET id = $1, abandoned = FALSE WHERE id = $2",
|
||||||
|
newsid,
|
||||||
|
oldsid
|
||||||
|
)
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -300,10 +327,7 @@ pub async fn delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn to_delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<()> {
|
pub async fn to_delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<()> {
|
||||||
sqlx::query!(
|
sqlx::query!(r"UPDATE players SET abandoned = TRUE WHERE id = $1", sid)
|
||||||
r"INSERT INTO abandoned_players (time, id) VALUES (NOW(), $1)",
|
|
||||||
sid
|
|
||||||
)
|
|
||||||
.execute(pool)
|
.execute(pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -311,21 +335,9 @@ pub async fn to_delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<()> {
|
|||||||
|
|
||||||
pub async fn in_delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<bool> {
|
pub async fn in_delete_sid(sid: &str, pool: &sqlx::PgPool) -> Result<bool> {
|
||||||
Ok(
|
Ok(
|
||||||
sqlx::query!(r"SELECT id FROM abandoned_players WHERE id = $1", sid)
|
sqlx::query!(r"SELECT abandoned FROM players WHERE id = $1", sid)
|
||||||
.fetch_optional(pool)
|
.fetch_one(pool)
|
||||||
.await?
|
.await?
|
||||||
.is_some(),
|
.abandoned,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn delete_abandoned(pool: &sqlx::PgPool) -> Result<()> {
|
|
||||||
sqlx::query!(
|
|
||||||
r"DELETE FROM players
|
|
||||||
WHERE id IN (SELECT id FROM abandoned_players
|
|
||||||
ORDER BY time DESC
|
|
||||||
OFFSET 1000)"
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
Ok(()) // TODO: REMOVE duliplcates id from abandoned
|
|
||||||
}
|
|
||||||
|
32
src/main.rs
32
src/main.rs
@@ -32,10 +32,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let pool = sqlx::postgres::PgPool::connect(&url).await?;
|
let pool = sqlx::postgres::PgPool::connect(&url).await?;
|
||||||
sqlx::migrate!("./migrations").run(&pool).await?;
|
sqlx::migrate!("./migrations").run(&pool).await?;
|
||||||
sqlx::query("DELETE FROM players").execute(&pool).await?;
|
sqlx::query("DELETE FROM players").execute(&pool).await?;
|
||||||
sqlx::query("DELETE FROM abandoned_players")
|
|
||||||
.execute(&pool)
|
|
||||||
.await?;
|
|
||||||
sqlx::query("DELETE FROM rooms").execute(&pool).await?;
|
|
||||||
let (layer, io) = SocketIo::builder().with_state(pool).build_layer();
|
let (layer, io) = SocketIo::builder().with_state(pool).build_layer();
|
||||||
|
|
||||||
io.ns("/", on_connect);
|
io.ns("/", on_connect);
|
||||||
@@ -90,18 +86,15 @@ async fn on_connect(socket: SocketRef, Data(auth): Data<AuthPayload>, pool: Stat
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let room: String = rand::thread_rng()
|
let room = match add_room(socket.id, &pool).await {
|
||||||
.sample_iter(&rand::distributions::Alphanumeric)
|
Err(e) => {
|
||||||
.take(ROOM_CODE_LENGTH)
|
|
||||||
.map(|x| char::to_ascii_uppercase(&(x as char)))
|
|
||||||
.collect();
|
|
||||||
tracing::info!("Creating room: {:?}", room);
|
|
||||||
// TODO: Handle duplicates
|
|
||||||
|
|
||||||
if let Err(e) = add_room(socket.id, room.clone(), &pool).await {
|
|
||||||
tracing::error!("{:?}", e);
|
tracing::error!("{:?}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Ok(c) => c,
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::info!("Creating room: {:?}", room);
|
||||||
socket.leave_all().unwrap();
|
socket.leave_all().unwrap();
|
||||||
socket.join(room.clone()).unwrap();
|
socket.join(room.clone()).unwrap();
|
||||||
emit_update_room(
|
emit_update_room(
|
||||||
@@ -202,17 +195,17 @@ async fn on_connect(socket: SocketRef, Data(auth): Data<AuthPayload>, pool: Stat
|
|||||||
"leave",
|
"leave",
|
||||||
|socket: SocketRef, pool: State<PgPool>| async move {
|
|socket: SocketRef, pool: State<PgPool>| async move {
|
||||||
tracing::info!("Leaving Rooms: {:?}", socket.id);
|
tracing::info!("Leaving Rooms: {:?}", socket.id);
|
||||||
leave_and_inform(&socket, &pool).await;
|
leave_and_inform(&socket, &pool, true).await;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
socket.on_disconnect(|socket: SocketRef, pool: State<PgPool>| async move {
|
socket.on_disconnect(|socket: SocketRef, pool: State<PgPool>| async move {
|
||||||
tracing::info!("Disconnecting: {:?}", socket.id);
|
tracing::info!("Disconnecting: {:?}", socket.id);
|
||||||
leave_and_inform(&socket, &pool).await;
|
leave_and_inform(&socket, &pool, false).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn leave_and_inform(socket: &SocketRef, pool: &PgPool) {
|
async fn leave_and_inform(socket: &SocketRef, pool: &PgPool, delete: bool) {
|
||||||
let room = socket
|
let room = socket
|
||||||
.rooms()
|
.rooms()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -225,7 +218,12 @@ async fn leave_and_inform(socket: &SocketRef, pool: &PgPool) {
|
|||||||
let ops = socket.within(room.clone());
|
let ops = socket.within(room.clone());
|
||||||
socket.leave_all().unwrap();
|
socket.leave_all().unwrap();
|
||||||
emit_update_room(socket, &room.to_string(), ops.sockets().unwrap().len());
|
emit_update_room(socket, &room.to_string(), ops.sockets().unwrap().len());
|
||||||
if let Err(e) = to_delete_sid(socket.id.as_str(), pool).await {
|
let sid = socket.id.as_str();
|
||||||
|
if let Err(e) = if delete {
|
||||||
|
delete_sid(sid, &pool).await
|
||||||
|
} else {
|
||||||
|
to_delete_sid(sid, &pool).await
|
||||||
|
} {
|
||||||
tracing::error!("{:?}", e);
|
tracing::error!("{:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user