DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

87f3f31d9d46d0ac597b7c6d58c1b5c36d21aa1a — Jonni Liljamo 1 year, 9 months ago ccb3974
feat(schema, shared, api): move all types to shared, separate schema
 * schema is now stored in a separate crate to avoid dependency cycles
 * added note in README.md about handling unwraps
25 files changed, 204 insertions(+), 212 deletions(-)

M Cargo.lock
M Cargo.toml
M README.md
M api/Cargo.toml
M api/diesel.toml
M api/src/actions/game/all_forming.rs
M api/src/actions/game/create.rs
M api/src/actions/game/my_games.rs
M api/src/actions/user/create.rs
M api/src/actions/user/login.rs
M api/src/handlers/user/create.rs
M api/src/handlers/user/login.rs
M api/src/main.rs
D api/src/models/action.rs
D api/src/models/game.rs
D api/src/models/gamedata.rs
D api/src/models/user.rs
A schema/Cargo.toml
A schema/LICENSE
R api/src/models/mod.rs => schema/src/lib.rs
R api/src/schema.rs => schema/src/schema.rs
M shared/Cargo.toml
M shared/src/api/user/mod.rs
M shared/src/types/game.rs
M shared/src/types/user.rs
M Cargo.lock => Cargo.lock +13 -12
@@ 546,6 546,7 @@ dependencies = [
 "email_address",
 "log",
 "pretty_env_logger",
 "schema",
 "serde",
 "serde_json",
 "shared",


@@ 5226,6 5227,13 @@ dependencies = [
]

[[package]]
name = "schema"
version = "0.1.0"
dependencies = [
 "diesel",
]

[[package]]
name = "scoped-tls"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5344,17 5352,6 @@ dependencies = [
]

[[package]]
name = "serde_repr"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e"
dependencies = [
 "proc-macro2",
 "quote",
 "syn",
]

[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"


@@ 5448,11 5445,15 @@ dependencies = [
name = "shared"
version = "0.1.0"
dependencies = [
 "chrono",
 "diesel",
 "naia-bevy-shared",
 "reqwest",
 "schema",
 "serde",
 "serde_repr",
 "serde_json",
 "thiserror",
 "uuid 1.3.0",
]

[[package]]

M Cargo.toml => Cargo.toml +1 -0
@@ 2,6 2,7 @@
resolver = "2"

members = [
  "schema",
  "shared",
  "client",
  "server",

M README.md => README.md +3 -1
@@ 14,6 14,8 @@ and just send an event to the client with the new one, so they can update\
it in the clients presistent storage, right? This can happen automatically\
in the background.

### **Quite a few unwraps in use**
All should be handled, with sane error responses.

## **Shortest possible explanation of the structure**
```


@@ 59,4 61,4 @@ Commits after 09.01.2023 MUST follow [Conventional Commits 1.0.0](https://www.co

Things to remember:
* everything lowercase
* allowed scopes are `shared`, `client`, `server` and `api`
* allowed scopes are `schema`, `shared`, `client`, `server` and `api`

M api/Cargo.toml => api/Cargo.toml +1 -0
@@ 10,6 10,7 @@ license = "GPL-3.0-only"
publish = false

[dependencies]
laurelin_schema = { package = "schema", path = "../schema" }
laurelin_shared = { package = "shared", path = "../shared" }

log = "0.4.17"

M api/diesel.toml => api/diesel.toml +1 -1
@@ 2,7 2,7 @@
# see https://diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/schema.rs"
file = "../schema/src/schema.rs"

[migrations_directory]
dir = "migrations"

M api/src/actions/game/all_forming.rs => api/src/actions/game/all_forming.rs +5 -3
@@ 7,9 7,11 @@
 */

use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use laurelin_shared::{error::api::APIError, types::game::GAMESTATE_FORMING};

use crate::{models::Game, schema::games};
use laurelin_schema::schema::games;
use laurelin_shared::{
    error::api::APIError,
    types::game::{Game, GAMESTATE_FORMING},
};

pub(crate) fn all_forming(conn: &mut PgConnection) -> Result<Vec<Game>, APIError> {
    let games_res = games::table

M api/src/actions/game/create.rs => api/src/actions/game/create.rs +5 -6
@@ 7,13 7,12 @@
 */

use diesel::{PgConnection, RunQueryDsl};
use laurelin_shared::{error::api::APIError, types::game::GAMESTATE_FORMING};
use uuid::Uuid;

use crate::{
    models::{Game, GameData, InsertableGame, InsertableGameData},
    schema::{gamedata, games},
use laurelin_schema::schema::{gamedata, games};
use laurelin_shared::{
    error::api::APIError,
    types::game::{Game, GameData, InsertableGame, InsertableGameData, GAMESTATE_FORMING},
};
use uuid::Uuid;

pub(crate) fn create(conn: &mut PgConnection, user_id: &str) -> Result<Game, APIError> {
    let new_gamedata = InsertableGameData {

M api/src/actions/game/my_games.rs => api/src/actions/game/my_games.rs +2 -3
@@ 7,11 7,10 @@
 */

use diesel::{BoolExpressionMethods, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use laurelin_shared::error::api::APIError;
use laurelin_schema::schema::games;
use laurelin_shared::{error::api::APIError, types::game::Game};
use uuid::Uuid;

use crate::{models::Game, schema::games};

pub(crate) fn my_games(conn: &mut PgConnection, user_id: &str) -> Result<Vec<Game>, APIError> {
    let games_res = games::table
        .filter(

M api/src/actions/user/create.rs => api/src/actions/user/create.rs +4 -5
@@ 11,11 11,10 @@ use argon2::{
    Argon2, PasswordHasher,
};
use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use laurelin_shared::error::api::APIError;

use crate::{
    models::{InsertableUser, User},
    schema::users,
use laurelin_schema::schema::users;
use laurelin_shared::{
    error::api::APIError,
    types::user::{InsertableUser, User},
};

pub(crate) fn create(conn: &mut PgConnection, user: &InsertableUser) -> Result<User, APIError> {

M api/src/actions/user/login.rs => api/src/actions/user/login.rs +4 -5
@@ 8,11 8,10 @@

use argon2::{Argon2, PasswordHash, PasswordVerifier};
use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use laurelin_shared::error::api::APIError;

use crate::{
    models::{User, UserCredentials},
    schema::users,
use laurelin_schema::schema::users;
use laurelin_shared::{
    error::api::APIError,
    types::user::{User, UserCredentials},
};

pub(crate) fn login(

M api/src/handlers/user/create.rs => api/src/handlers/user/create.rs +3 -3
@@ 10,15 10,15 @@ use actix_session::Session;
use actix_web::{post, web, HttpResponse, Responder};
use email_address::EmailAddress;

use laurelin_shared::error::api::APIError;
use laurelin_shared::{error::api::APIError, types::user::InsertableUser};

use crate::{actions, models, PgPool};
use crate::{actions, PgPool};

#[post("/api/user")]
pub(crate) async fn create(
    pool: web::Data<PgPool>,
    session: Session,
    user: web::Json<models::InsertableUser>,
    user: web::Json<InsertableUser>,
) -> impl Responder {
    if user.username.len() < 3 {
        return HttpResponse::BadRequest().json(APIError::UserUsernameTooShort);

M api/src/handlers/user/login.rs => api/src/handlers/user/login.rs +2 -2
@@ 8,9 8,9 @@

use actix_session::Session;
use actix_web::{post, web, HttpResponse, Responder};
use laurelin_shared::error::api::APIError;
use laurelin_shared::{error::api::APIError, types::user::UserCredentials};

use crate::{actions, models::UserCredentials, PgPool};
use crate::{actions, PgPool};

#[post("/api/user/login")]
pub(crate) async fn login(

M api/src/main.rs => api/src/main.rs +0 -3
@@ 52,11 52,8 @@ fn run_migrations(conn: &mut PgConnection) {
    conn.run_pending_migrations(MIGRATIONS).unwrap();
}

mod schema;

mod actions;
mod handlers;
mod models;
mod session;

#[actix_web::main]

D api/src/models/action.rs => api/src/models/action.rs +0 -34
@@ 1,34 0,0 @@
/*
 * This file is part of laurelin/api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use chrono::NaiveDateTime;
use diesel::{Insertable, Queryable};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::schema::actions;

#[derive(Serialize, Queryable)]
pub(crate) struct Action {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub game_data_id: Uuid,
    pub invoker: Uuid,
    pub data: serde_json::Value,
    pub timestamp: NaiveDateTime,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=actions)]
pub(crate) struct InsertableAction {
    pub game_data_id: Uuid,
    pub invoker: Uuid,
    pub data: serde_json::Value,
    pub timestamp: NaiveDateTime,
}

D api/src/models/game.rs => api/src/models/game.rs +0 -36
@@ 1,36 0,0 @@
/*
 * This file is part of laurelin/api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use chrono::NaiveDateTime;
use diesel::{Insertable, Queryable};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::schema::games;

#[derive(Serialize, Queryable)]
pub(crate) struct Game {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub host_id: Uuid,
    pub guest_id: Option<Uuid>,
    pub state: i16,
    pub ended_at: Option<NaiveDateTime>,
    pub game_data_id: Uuid,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=games)]
pub(crate) struct InsertableGame {
    pub host_id: Uuid,
    pub guest_id: Option<Uuid>,
    pub state: i16,
    pub ended_at: Option<NaiveDateTime>,
    pub game_data_id: Uuid,
}

D api/src/models/gamedata.rs => api/src/models/gamedata.rs +0 -36
@@ 1,36 0,0 @@
/*
 * This file is part of laurelin/api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use chrono::NaiveDateTime;
use diesel::{Insertable, Queryable};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::schema::gamedata;

#[derive(Serialize, Queryable)]
pub(crate) struct GameData {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub turn: i16,
    pub host_hand: Option<serde_json::Value>,
    pub host_deck: Option<serde_json::Value>,
    pub guest_hand: Option<serde_json::Value>,
    pub guest_deck: Option<serde_json::Value>,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=gamedata)]
pub(crate) struct InsertableGameData {
    pub turn: i16,
    pub host_hand: Option<serde_json::Value>,
    pub host_deck: Option<serde_json::Value>,
    pub guest_hand: Option<serde_json::Value>,
    pub guest_deck: Option<serde_json::Value>,
}

D api/src/models/user.rs => api/src/models/user.rs +0 -40
@@ 1,40 0,0 @@
/*
 * This file is part of laurelin/api
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

use chrono::NaiveDateTime;
use diesel::{Insertable, Queryable};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::schema::users;

#[derive(Serialize, Queryable)]
pub(crate) struct User {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub username: String,
    pub email: String,
    #[allow(dead_code)]
    #[serde(skip_serializing)]
    pub password: String,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=users)]
pub(crate) struct InsertableUser {
    pub username: String,
    pub email: String,
    pub password: String,
}

#[derive(Deserialize)]
pub(crate) struct UserCredentials {
    pub email: String,
    pub password: String,
}

A schema/Cargo.toml => schema/Cargo.toml +17 -0
@@ 0,0 1,17 @@
[package]
name = "schema"
version = "0.1.0"
authors = ["Jonni Liljamo <jonni@liljamo.com>"]
edition = "2021"
description = "Schema for laurelin"
readme = "README.md"
repository = "https://git.src.quest/~skye/deck-builder/"
license = "GPL-3.0-only"
publish = false

[dependencies]

[dependencies.diesel]
version = "2.0.3"
default-features = false
features = [ "with-deprecated", "32-column-tables", "postgres", "r2d2", "chrono", "uuid", "serde_json" ]

A schema/LICENSE => schema/LICENSE +8 -0
@@ 0,0 1,8 @@
Schema for a deck building game.
Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.

R api/src/models/mod.rs => schema/src/lib.rs +2 -12
@@ 1,19 1,9 @@
/*
 * This file is part of laurelin/api
 * This file is part of laurelin/schema
 * Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
 *
 * Licensed under GPL-3.0-only.
 * See LICENSE for licensing information.
 */

mod user;
pub(crate) use user::*;

mod action;
pub(crate) use action::*;

mod game;
pub(crate) use game::*;

mod gamedata;
pub(crate) use gamedata::*;
pub mod schema;

R api/src/schema.rs => schema/src/schema.rs +0 -0
M shared/Cargo.toml => shared/Cargo.toml +21 -2
@@ 10,15 10,34 @@ license = "GPL-3.0-only"
publish = false

[dependencies]
laurelin_schema = { package = "schema", path = "../schema" }

naia-bevy-shared = "0.19.0"

thiserror = "1.0.38"

reqwest = { version = "0.11.14", features = ["blocking", "json", "cookies" ] }

serde_repr = "0.1.10"

[dependencies.serde]
version = "1.0.152"
default-features = false
features = [ "derive" ]

[dependencies.serde_json]
version = "1.0.94"
default-features = false
features = [ "std" ]

[dependencies.chrono]
version = "0.4.23"
default-features = false
features = [ "alloc", "std", "serde" ]

[dependencies.diesel]
version = "2.0.3"
default-features = false
features = [ "with-deprecated", "32-column-tables", "postgres", "r2d2", "chrono", "uuid", "serde_json" ]

[dependencies.uuid]
version = "1.3.0"
features = [ "std", "v4", "serde" ]

M shared/src/api/user/mod.rs => shared/src/api/user/mod.rs +3 -3
@@ 9,7 9,7 @@
use reqwest;
use serde::{Deserialize, Serialize};

use crate::{error::api::APIError, types::user::User};
use crate::{error::api::APIError, types::user::UserPub};

use super::macros::extract_cookie;



@@ 23,7 23,7 @@ pub struct PostLogin {
#[serde(untagged)]
pub enum ResponseLogin {
    Error(APIError),
    Ok(User),
    Ok(UserPub),
}

#[derive(Deserialize)]


@@ 61,7 61,7 @@ pub struct PostRegister {
#[serde(untagged)]
pub enum ResponseRegister {
    Error(APIError),
    Ok(User),
    Ok(UserPub),
}

#[derive(Deserialize)]

M shared/src/types/game.rs => shared/src/types/game.rs +74 -0
@@ 6,7 6,81 @@
 * See LICENSE for licensing information.
 */

use chrono::NaiveDateTime;
use diesel::{Insertable, Queryable};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

use laurelin_schema::schema::{actions, gamedata, games};

pub const GAMESTATE_FORMING: i16 = 0;
pub const GAMESTATE_INPROGRESS: i16 = 1;
pub const GAMESTATE_FINISHED: i16 = 2;
pub const GAMESTATE_CANCELLED: i16 = 3;

pub const GAMETURN_HOST: i16 = 0;
pub const GAMETURN_GUEST: i16 = 1;

#[derive(Serialize, Deserialize, Queryable)]
pub struct Game {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub host_id: Uuid,
    pub guest_id: Option<Uuid>,
    pub state: i16,
    pub ended_at: Option<NaiveDateTime>,
    pub game_data_id: Uuid,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=games)]
pub struct InsertableGame {
    pub host_id: Uuid,
    pub guest_id: Option<Uuid>,
    pub state: i16,
    pub ended_at: Option<NaiveDateTime>,
    pub game_data_id: Uuid,
}

#[derive(Serialize, Deserialize, Queryable)]
pub struct Action {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub game_data_id: Uuid,
    pub invoker: Uuid,
    pub data: serde_json::Value,
    pub timestamp: NaiveDateTime,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=actions)]
pub struct InsertableAction {
    pub game_data_id: Uuid,
    pub invoker: Uuid,
    pub data: serde_json::Value,
    pub timestamp: NaiveDateTime,
}

#[derive(Serialize, Deserialize, Queryable)]
pub struct GameData {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub turn: i16,
    pub host_hand: Option<serde_json::Value>,
    pub host_deck: Option<serde_json::Value>,
    pub guest_hand: Option<serde_json::Value>,
    pub guest_deck: Option<serde_json::Value>,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=gamedata)]
pub struct InsertableGameData {
    pub turn: i16,
    pub host_hand: Option<serde_json::Value>,
    pub host_deck: Option<serde_json::Value>,
    pub guest_hand: Option<serde_json::Value>,
    pub guest_deck: Option<serde_json::Value>,
}

M shared/src/types/user.rs => shared/src/types/user.rs +35 -5
@@ 6,13 6,43 @@
 * See LICENSE for licensing information.
 */

use serde::Deserialize;
use chrono::NaiveDateTime;
use diesel::{Insertable, Queryable};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Deserialize)]
use laurelin_schema::schema::users;

#[derive(Serialize, Deserialize, Queryable)]
pub struct User {
    pub id: String,
    pub created_at: String,
    pub updated_at: String,
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub username: String,
    pub email: String,
    #[serde(skip_serializing)]
    pub password: String,
}

#[derive(Deserialize)]
pub struct UserPub {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub username: String,
    pub email: String,
}

#[derive(Deserialize, Insertable)]
#[diesel(table_name=users)]
pub struct InsertableUser {
    pub username: String,
    pub email: String,
    pub password: String,
}

#[derive(Deserialize)]
pub struct UserCredentials {
    pub email: String,
    pub password: String,
}