Compare commits
No commits in common. "c99c7af369135dff689dcfaaee510782d5d0dcb4" and "cd4afbafe2e8d344ab3ef06a2ce7af4e86c52bdf" have entirely different histories.
c99c7af369
...
cd4afbafe2
3
.env
3
.env
|
@ -1,2 +1 @@
|
||||||
DATABASE_URL="sqlite://tenebrous.sqlite"
|
DATABASE_URL="./tenebrous.sqlite"
|
||||||
SQLX_OFFLINE="true"
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/target
|
/target
|
||||||
todo.org
|
todo.org
|
||||||
*.sqlite*
|
*.sqlite
|
||||||
*.sqlite.*
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
16
Cargo.toml
16
Cargo.toml
|
@ -4,15 +4,6 @@ version = "0.1.0"
|
||||||
authors = ["jeff <jeff@agnos.is>"]
|
authors = ["jeff <jeff@agnos.is>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
default-run = "tenebrous"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tenebrous-migrate"
|
|
||||||
path = "src/migrate.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "tenebrous"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
prost-build = "0.6"
|
prost-build = "0.6"
|
||||||
|
@ -23,15 +14,14 @@ serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
erased-serde = "0.3"
|
erased-serde = "0.3"
|
||||||
|
diesel = "1.4"
|
||||||
|
diesel-derive-enum = { version = "1", features = ["sqlite"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
rust-argon2 = "0.8"
|
rust-argon2 = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
strum = { version = "0.20", features = ["derive"] }
|
strum = { version = "0.20", features = ["derive"] }
|
||||||
sqlx = { version = "0.4.2", features = [ "offline", "sqlite", "runtime-tokio-native-tls" ] }
|
|
||||||
refinery = { version = "0.3", features = ["rusqlite"]}
|
|
||||||
barrel = { version = "0.6", features = ["sqlite3"] }
|
|
||||||
|
|
||||||
[dependencies.rocket]
|
[dependencies.rocket]
|
||||||
git = "https://github.com/SergioBenitez/Rocket"
|
git = "https://github.com/SergioBenitez/Rocket"
|
||||||
|
@ -42,4 +32,4 @@ features = ["secrets"]
|
||||||
git = "https://github.com/SergioBenitez/Rocket"
|
git = "https://github.com/SergioBenitez/Rocket"
|
||||||
branch = "master"
|
branch = "master"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = [ "tera_templates", "serve" ]
|
features = [ "tera_templates", "diesel_sqlite_pool", "serve" ]
|
41
README.md
41
README.md
|
@ -7,7 +7,9 @@ Currently under heavy development.
|
||||||
## Build Instructions
|
## Build Instructions
|
||||||
|
|
||||||
These are very basic build instructions. They assume you already have
|
These are very basic build instructions. They assume you already have
|
||||||
cargo set up and installed.
|
cargo set up and installed. Building the application requires **Rust
|
||||||
|
Nightly!** See [rustup documentation][rustup] for more, particularly
|
||||||
|
the part about using [Rust Nightly][nightly].
|
||||||
|
|
||||||
### Install Dependencies
|
### Install Dependencies
|
||||||
|
|
||||||
|
@ -21,19 +23,18 @@ Install dependencies. The exact method depends on your OS.
|
||||||
|
|
||||||
### Initial Setup
|
### Initial Setup
|
||||||
|
|
||||||
Follow these instructions from the root of the repository.
|
Follow these instructions from the root of the repository. Set up database:
|
||||||
|
|
||||||
Set up database:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo install --version=0.2.0 sqlx-cli
|
cargo install diesel_cli --no-default-features --features sqlite
|
||||||
cargo run --bin tenebrous-migrate
|
diesel setup
|
||||||
|
diesel migration run
|
||||||
```
|
```
|
||||||
|
|
||||||
### Run Application
|
### Run Application
|
||||||
|
|
||||||
If you are using `rustup`, then it should automatically switch to the
|
If you are using `rustup`, then it should automatically switch to the
|
||||||
stable version of Rust in this repository. This is because of the
|
nightly version of Rust in this repository. This is because of the
|
||||||
`rust-toolchain` file.
|
`rust-toolchain` file.
|
||||||
|
|
||||||
Command line "instructions" to build and run the application:
|
Command line "instructions" to build and run the application:
|
||||||
|
@ -42,27 +43,9 @@ Command line "instructions" to build and run the application:
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
The sqlite database is created in the directory `cargo run` was
|
The sqlite database is currently always created in the same directory
|
||||||
invoked from by default. You can also pass a path to a different
|
that `cargo run` was invoked from, so make sure you invoke it from the
|
||||||
location as a single argument to the program.
|
same place every time.
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
Development instructions.
|
|
||||||
|
|
||||||
To set up a local database, or run migrations, run:
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo run --bin tenebrous-migrate
|
|
||||||
```
|
|
||||||
|
|
||||||
### Database Queries and Migrations
|
|
||||||
|
|
||||||
When adding/updating a compile-checked query or a migration, you need
|
|
||||||
to update the SQLx data JSON file:
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo sqlx prepare -- --bin tenebrous
|
|
||||||
```
|
|
||||||
|
|
||||||
[rustup]: https://rust-lang.github.io/rustup/index.html
|
[rustup]: https://rust-lang.github.io/rustup/index.html
|
||||||
|
[nightly]: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE characters;
|
|
@ -0,0 +1,9 @@
|
||||||
|
CREATE TABLE characters(
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
viewable BOOLEAN NOT NULL,
|
||||||
|
character_name TEXT NOT NULL,
|
||||||
|
data_type TEXT CHECK(data_type IN ('chronicles_of_darkness_v1', 'changeling_v1')) NOT NULL,
|
||||||
|
data_version INTEGER NOT NULL,
|
||||||
|
data BLOB NOT NULL
|
||||||
|
);
|
|
@ -0,0 +1 @@
|
||||||
|
DROP TABLE users;
|
|
@ -0,0 +1,5 @@
|
||||||
|
CREATE TABLE users (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
username TEXT NOT NULL,
|
||||||
|
password TEXT NOT NULL
|
||||||
|
);
|
165
sqlx-data.json
165
sqlx-data.json
|
@ -1,165 +0,0 @@
|
||||||
{
|
|
||||||
"db": "SQLite",
|
|
||||||
"492e1e087edc6eff4004033227e3f3510f1165c3ec7626e89b695c760bc113d2": {
|
|
||||||
"query": "SELECT id as \"id: _\",\n user_id as \"user_id: _\",\n data_type as \"data_type: _\",\n data_version as \"data_version: _\",\n viewable, character_name\n FROM characters WHERE user_id = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id: _",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_id: _",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "data_type: _",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "data_version: _",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "viewable",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "character_name",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"7a55ee917cf1ec732b10b2844dd5a3d0089c177b0eb183932fd73e20387a1610": {
|
|
||||||
"query": "SELECT id as \"id: _\", username, password FROM users WHERE username = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id: _",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "password",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"d7d1fc9ceff3b7659c8f04fd5b574104d1866cb1ea67d9534b6f3f4064699fb6": {
|
|
||||||
"query": "SELECT id as \"id: _\", username, password FROM users WHERE id = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id: _",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "username",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "password",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"e3c4b224ce8ce70a4d7709af7abe82a6f53bff834460fa7d32bb0b6daea94565": {
|
|
||||||
"query": "SELECT id as \"id: _\",\n user_id as \"user_id: _\",\n viewable, character_name, data,\n data_type as \"data_type: _\",\n data_version as \"data_version: _\"\n FROM characters WHERE id = ?",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id: _",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "user_id: _",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "viewable",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Bool"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "character_name",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "data",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Blob"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "data_type: _",
|
|
||||||
"ordinal": 5,
|
|
||||||
"type_info": "Null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "data_version: _",
|
|
||||||
"ordinal": 6,
|
|
||||||
"type_info": "Int64"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 1
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
184
src/db.rs
184
src/db.rs
|
@ -1,137 +1,119 @@
|
||||||
use crate::models::characters::{Character, NewCharacter, StrippedCharacter};
|
use crate::models::characters::{Character, NewCharacter, StrippedCharacter};
|
||||||
use crate::models::users::{NewUser, User};
|
use crate::models::users::{NewUser, User};
|
||||||
use sqlx::sqlite::{SqliteConnectOptions, SqlitePool, SqlitePoolOptions};
|
use crate::schema::characters;
|
||||||
use sqlx::ConnectOptions;
|
use diesel::prelude::*;
|
||||||
use std::str::FromStr;
|
use rocket_contrib::databases::diesel;
|
||||||
|
|
||||||
/// Type alias for the Rocket-managed singleton database connection.
|
#[database("tenebrous_db")]
|
||||||
pub type TenebrousDbConn<'a> = rocket::State<'a, SqlitePool>;
|
pub(crate) struct TenebrousDbConn(SqliteConnection);
|
||||||
|
|
||||||
/// Create a connection pool to the database.
|
|
||||||
pub(crate) async fn create_pool(db_path: &str) -> Result<SqlitePool, crate::errors::Error> {
|
|
||||||
//Create database if missing.
|
|
||||||
let conn = SqliteConnectOptions::from_str(&format!("sqlite://{}", db_path))?
|
|
||||||
.create_if_missing(true)
|
|
||||||
.connect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
drop(conn);
|
|
||||||
|
|
||||||
//Return actual conncetion pool.
|
|
||||||
SqlitePoolOptions::new()
|
|
||||||
.max_connections(5)
|
|
||||||
.connect(db_path)
|
|
||||||
.await
|
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
pub(crate) trait Dao {
|
pub(crate) trait Dao {
|
||||||
async fn load_user_by_id(&self, id: i32) -> sqlx::Result<Option<User>>;
|
async fn load_user_by_id(&self, id: i32) -> QueryResult<Option<User>>;
|
||||||
|
|
||||||
async fn load_user(&self, for_username: &str) -> sqlx::Result<Option<User>>;
|
async fn load_user(&self, for_username: String) -> QueryResult<Option<User>>;
|
||||||
|
|
||||||
async fn insert_user(&self, new_user: NewUser<'_>) -> sqlx::Result<User>;
|
async fn insert_user(&self, new_user: NewUser) -> QueryResult<User>;
|
||||||
|
|
||||||
async fn load_character_list(&self, for_user_id: i32) -> sqlx::Result<Vec<StrippedCharacter>>;
|
async fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<StrippedCharacter>>;
|
||||||
|
|
||||||
async fn load_character(&self, character_id: i32) -> sqlx::Result<Option<Character>>;
|
async fn load_character(&self, character_id: i32) -> QueryResult<Option<Character>>;
|
||||||
|
|
||||||
async fn insert_character(&self, new_character: NewCharacter<'_>) -> sqlx::Result<()>;
|
async fn insert_character(&self, new_character: NewCharacter) -> QueryResult<()>;
|
||||||
|
|
||||||
async fn update_character_sheet<'a>(&self, character: &'a Character) -> sqlx::Result<()>;
|
async fn update_character_sheet(&self, character: Character) -> QueryResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO is:
|
type StrippedCharacterColumns = (
|
||||||
// - use compile time queries
|
characters::id,
|
||||||
// - find replacement for diesel migrations
|
characters::user_id,
|
||||||
|
characters::viewable,
|
||||||
|
characters::character_name,
|
||||||
|
characters::data_type,
|
||||||
|
characters::data_version,
|
||||||
|
);
|
||||||
|
|
||||||
|
const STRIPPED_CHARACTER_COLUMNS: StrippedCharacterColumns = (
|
||||||
|
characters::id,
|
||||||
|
characters::user_id,
|
||||||
|
characters::viewable,
|
||||||
|
characters::character_name,
|
||||||
|
characters::data_type,
|
||||||
|
characters::data_version,
|
||||||
|
);
|
||||||
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl Dao for SqlitePool {
|
impl Dao for TenebrousDbConn {
|
||||||
async fn load_user_by_id(&self, user_id: i32) -> sqlx::Result<Option<User>> {
|
async fn load_user_by_id(&self, user_id: i32) -> QueryResult<Option<User>> {
|
||||||
sqlx::query_as!(
|
use crate::schema::users::dsl::*;
|
||||||
User,
|
self.run(move |conn| users.filter(id.eq(user_id)).first(conn).optional())
|
||||||
r#"SELECT id as "id: _", username, password FROM users WHERE id = ?"#,
|
|
||||||
user_id
|
|
||||||
)
|
|
||||||
.fetch_optional(self)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_user(&self, for_username: &str) -> sqlx::Result<Option<User>> {
|
async fn load_user(&self, for_username: String) -> QueryResult<Option<User>> {
|
||||||
sqlx::query_as!(
|
use crate::schema::users::dsl::*;
|
||||||
User,
|
|
||||||
r#"SELECT id as "id: _", username, password FROM users WHERE username = ?"#,
|
self.run(move |conn| {
|
||||||
for_username
|
users
|
||||||
)
|
.filter(username.eq(for_username))
|
||||||
.fetch_optional(self)
|
.first(conn)
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn insert_user(&self, new_user: NewUser<'_>) -> sqlx::Result<User> {
|
async fn insert_user(&self, new_user: NewUser) -> QueryResult<User> {
|
||||||
sqlx::query("INSERT INTO users (username, password) values (?, ?)")
|
self.run(move |conn| {
|
||||||
.bind(new_user.username)
|
diesel::insert_into(users).values(&new_user).execute(conn)?;
|
||||||
.bind(new_user.password)
|
|
||||||
.execute(self)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
self.load_user(new_user.username)
|
use crate::schema::users::dsl::*;
|
||||||
.await
|
users.filter(username.eq(new_user.username)).first(conn)
|
||||||
.and_then(|user| user.ok_or(sqlx::Error::RowNotFound))
|
})
|
||||||
}
|
|
||||||
|
|
||||||
async fn load_character_list(&self, for_user_id: i32) -> sqlx::Result<Vec<StrippedCharacter>> {
|
|
||||||
sqlx::query_as!(
|
|
||||||
StrippedCharacter,
|
|
||||||
r#"SELECT id as "id: _",
|
|
||||||
user_id as "user_id: _",
|
|
||||||
data_type as "data_type: _",
|
|
||||||
data_version as "data_version: _",
|
|
||||||
viewable, character_name
|
|
||||||
FROM characters WHERE user_id = ?"#,
|
|
||||||
for_user_id
|
|
||||||
)
|
|
||||||
.fetch_all(self)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_character(&self, character_id: i32) -> sqlx::Result<Option<Character>> {
|
async fn load_character_list(&self, for_user_id: i32) -> QueryResult<Vec<StrippedCharacter>> {
|
||||||
sqlx::query_as!(
|
use crate::schema::characters::dsl::*;
|
||||||
Character,
|
|
||||||
r#"SELECT id as "id: _",
|
self.run(move |conn| {
|
||||||
user_id as "user_id: _",
|
characters
|
||||||
viewable, character_name, data,
|
.filter(user_id.eq(for_user_id))
|
||||||
data_type as "data_type: _",
|
.select(STRIPPED_CHARACTER_COLUMNS)
|
||||||
data_version as "data_version: _"
|
.load(conn)
|
||||||
FROM characters WHERE id = ?"#,
|
})
|
||||||
character_id
|
|
||||||
)
|
|
||||||
.fetch_optional(self)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn insert_character(&self, new_character: NewCharacter<'_>) -> sqlx::Result<()> {
|
async fn load_character(&self, character_id: i32) -> QueryResult<Option<Character>> {
|
||||||
sqlx::query(
|
use crate::schema::characters::dsl::*;
|
||||||
"INSERT INTO characters
|
|
||||||
(user_id, viewable, character_name, data_type, data_version, data)
|
self.run(move |conn| {
|
||||||
values (?, ?, ?, ?, ?, ?)",
|
characters
|
||||||
)
|
.filter(id.eq(character_id))
|
||||||
.bind(new_character.user_id)
|
.first(conn)
|
||||||
.bind(new_character.viewable)
|
.optional()
|
||||||
.bind(new_character.character_name)
|
})
|
||||||
.bind(new_character.data_type)
|
.await
|
||||||
.bind(new_character.data_version)
|
}
|
||||||
.bind(new_character.data)
|
|
||||||
.execute(self)
|
async fn insert_character(&self, new_character: NewCharacter) -> QueryResult<()> {
|
||||||
|
self.run(|conn| {
|
||||||
|
diesel::insert_into(characters::table)
|
||||||
|
.values(new_character)
|
||||||
|
.execute(conn)
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_character_sheet<'a>(&self, character: &'a Character) -> sqlx::Result<()> {
|
async fn update_character_sheet(&self, character: Character) -> QueryResult<()> {
|
||||||
sqlx::query("UPDATE characters set data = ? where id = ?")
|
use crate::schema::characters::dsl::*;
|
||||||
.bind(&character.data)
|
self.run(move |conn| {
|
||||||
.bind(character.id)
|
diesel::update(&character)
|
||||||
.execute(self)
|
.set(data.eq(&character.data))
|
||||||
|
.execute(conn)
|
||||||
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -20,6 +20,9 @@ pub enum Error {
|
||||||
#[error("invalid input")]
|
#[error("invalid input")]
|
||||||
InvalidInput,
|
InvalidInput,
|
||||||
|
|
||||||
|
#[error("query error: {0}")]
|
||||||
|
QueryError(#[from] diesel::result::Error),
|
||||||
|
|
||||||
#[error("serialization error: {0}")]
|
#[error("serialization error: {0}")]
|
||||||
SerializationError(#[from] prost::EncodeError),
|
SerializationError(#[from] prost::EncodeError),
|
||||||
|
|
||||||
|
@ -28,12 +31,6 @@ pub enum Error {
|
||||||
|
|
||||||
#[error("i/o error: {0}")]
|
#[error("i/o error: {0}")]
|
||||||
IoError(#[from] std::io::Error),
|
IoError(#[from] std::io::Error),
|
||||||
|
|
||||||
#[error("query error: {0}")]
|
|
||||||
QueryError(#[from] sqlx::Error),
|
|
||||||
|
|
||||||
#[error("rocket error: {0}")]
|
|
||||||
RocketError(#[from] rocket::error::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -4,6 +4,9 @@ extern crate rocket;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate rocket_contrib;
|
extern crate rocket_contrib;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
|
||||||
// Seemingly necessary to get serde::Serialize into scope for Prost
|
// Seemingly necessary to get serde::Serialize into scope for Prost
|
||||||
// code generation.
|
// code generation.
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
@ -11,28 +14,16 @@ extern crate serde_derive;
|
||||||
|
|
||||||
use rocket_contrib::serve::StaticFiles;
|
use rocket_contrib::serve::StaticFiles;
|
||||||
use rocket_contrib::templates::Template;
|
use rocket_contrib::templates::Template;
|
||||||
use std::env;
|
|
||||||
|
|
||||||
pub mod catchers;
|
pub mod catchers;
|
||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod migrator;
|
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
#[rocket::main]
|
#[rocket::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), rocket::error::Error> {
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
let db_path: &str = match &args[..] {
|
|
||||||
[_, path] => path.as_ref(),
|
|
||||||
[_, _, ..] => panic!("Expected exactly 0 or 1 argument"),
|
|
||||||
_ => "tenebrous.sqlite",
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Using database: {}", db_path);
|
|
||||||
|
|
||||||
migrator::migrate(db_path).await?;
|
|
||||||
|
|
||||||
let root_routes: Vec<rocket::Route> = {
|
let root_routes: Vec<rocket::Route> = {
|
||||||
routes::root::routes()
|
routes::root::routes()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
|
@ -46,8 +37,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
|
||||||
rocket::ignite()
|
rocket::ignite()
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
//.attach(db::TenebrousDbConn::fairing())
|
.attach(db::TenebrousDbConn::fairing())
|
||||||
.manage(crate::db::create_pool(db_path).await?)
|
|
||||||
.mount("/", root_routes)
|
.mount("/", root_routes)
|
||||||
.mount("/characters", character_routes)
|
.mount("/characters", character_routes)
|
||||||
.mount("/api", api_routes)
|
.mount("/api", api_routes)
|
||||||
|
@ -62,5 +52,4 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
.register(catchers)
|
.register(catchers)
|
||||||
.launch()
|
.launch()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.into())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
use std::env;
|
|
||||||
|
|
||||||
pub mod migrator;
|
|
||||||
|
|
||||||
#[rocket::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let args: Vec<String> = env::args().collect();
|
|
||||||
let db_path: &str = match &args[..] {
|
|
||||||
[_, path] => path.as_ref(),
|
|
||||||
[_, _, ..] => panic!("Expected exactly 0 or 1 argument"),
|
|
||||||
_ => "tenebrous.sqlite",
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Using database: {}", db_path);
|
|
||||||
|
|
||||||
crate::migrator::migrate(db_path).await
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
use refinery::config::{Config, ConfigDbType};
|
|
||||||
use sqlx::sqlite::SqliteConnectOptions;
|
|
||||||
use sqlx::ConnectOptions;
|
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
pub mod migrations;
|
|
||||||
|
|
||||||
/// Run database migrations against the sqlite database.
|
|
||||||
pub(crate) async fn migrate(db_path: &str) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
//Create database if missing.
|
|
||||||
let conn = SqliteConnectOptions::from_str(&format!("sqlite://{}", db_path))?
|
|
||||||
.create_if_missing(true)
|
|
||||||
.connect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
drop(conn);
|
|
||||||
|
|
||||||
let mut conn = Config::new(ConfigDbType::Sqlite).set_db_path(db_path);
|
|
||||||
println!("Running migrations");
|
|
||||||
migrations::runner().run(&mut conn)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
use barrel::backend::Sqlite;
|
|
||||||
use barrel::{types, Migration};
|
|
||||||
|
|
||||||
pub fn migration() -> String {
|
|
||||||
let mut m = Migration::new();
|
|
||||||
println!("Applying migration: {}", file!());
|
|
||||||
|
|
||||||
m.create_table("users", |t| {
|
|
||||||
t.add_column("id", types::primary());
|
|
||||||
t.add_column("username", types::text());
|
|
||||||
t.add_column("password", types::text());
|
|
||||||
});
|
|
||||||
|
|
||||||
m.make::<Sqlite>()
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
use barrel::backend::Sqlite;
|
|
||||||
use barrel::{types, Migration};
|
|
||||||
|
|
||||||
pub fn migration() -> String {
|
|
||||||
let mut m = Migration::new();
|
|
||||||
println!("Applying migration: {}", file!());
|
|
||||||
|
|
||||||
m.create_table("characters", move |t| {
|
|
||||||
let db_enum = r#"CHECK(data_type IN ('chronicles_of_darkness_v1', 'changeling_v1'))"#;
|
|
||||||
t.add_column("id", types::primary());
|
|
||||||
t.add_column("user_id", types::integer());
|
|
||||||
t.add_column("viewable", types::boolean());
|
|
||||||
t.add_column("character_name", types::text());
|
|
||||||
t.add_column("data_type", types::custom(db_enum));
|
|
||||||
t.add_column("data_version", types::integer());
|
|
||||||
t.add_column("data", types::custom("BLOB"));
|
|
||||||
});
|
|
||||||
|
|
||||||
m.make::<Sqlite>()
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
use refinery::include_migration_mods;
|
|
||||||
include_migration_mods!("src/migrator/migrations");
|
|
|
@ -1,6 +1,8 @@
|
||||||
use crate::errors::Error;
|
use crate::errors::Error;
|
||||||
use crate::models::proto::cofd::*;
|
use crate::models::proto::cofd::*;
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
|
use crate::schema::characters;
|
||||||
|
use diesel_derive_enum::DbEnum;
|
||||||
use prost::bytes::BytesMut;
|
use prost::bytes::BytesMut;
|
||||||
use serde_derive::Serialize;
|
use serde_derive::Serialize;
|
||||||
use strum::{EnumIter, EnumString};
|
use strum::{EnumIter, EnumString};
|
||||||
|
@ -39,8 +41,7 @@ pub(crate) trait Visibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, PartialEq, Clone, Copy, EnumIter, EnumString, sqlx::Type)]
|
#[derive(DbEnum, Debug, Serialize, PartialEq, Clone, Copy, EnumIter, EnumString)]
|
||||||
#[sqlx(rename_all = "snake_case")]
|
|
||||||
pub enum CharacterDataType {
|
pub enum CharacterDataType {
|
||||||
ChroniclesOfDarknessV1,
|
ChroniclesOfDarknessV1,
|
||||||
ChangelingV1,
|
ChangelingV1,
|
||||||
|
@ -72,7 +73,7 @@ impl CharacterDataType {
|
||||||
|
|
||||||
/// An entry that appears in a user's character list. Properties are
|
/// An entry that appears in a user's character list. Properties are
|
||||||
/// in order of table columns.
|
/// in order of table columns.
|
||||||
#[derive(Serialize, Debug, sqlx::FromRow)]
|
#[derive(Serialize, Debug, Queryable, Identifiable, AsChangeset)]
|
||||||
pub struct Character {
|
pub struct Character {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
|
@ -144,7 +145,7 @@ impl Character {
|
||||||
|
|
||||||
/// Same as regular character type, but without the actual protobuf
|
/// Same as regular character type, but without the actual protobuf
|
||||||
/// data loaded into memory.
|
/// data loaded into memory.
|
||||||
#[derive(Serialize, Debug, sqlx::FromRow)]
|
#[derive(Serialize, Debug, Queryable)]
|
||||||
pub struct StrippedCharacter {
|
pub struct StrippedCharacter {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
|
@ -166,11 +167,13 @@ impl Visibility for StrippedCharacter {
|
||||||
|
|
||||||
/// Represents insert of a new character into the database. Property
|
/// Represents insert of a new character into the database. Property
|
||||||
/// names correspond to columns.
|
/// names correspond to columns.
|
||||||
pub struct NewCharacter<'a> {
|
#[derive(Insertable)]
|
||||||
|
#[table_name = "characters"]
|
||||||
|
pub struct NewCharacter {
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
pub viewable: bool,
|
pub viewable: bool,
|
||||||
pub character_name: &'a str,
|
pub character_name: String,
|
||||||
pub data_type: CharacterDataType,
|
pub data_type: CharacterDataType,
|
||||||
pub data_version: i32,
|
pub data_version: i32,
|
||||||
pub data: &'a [u8],
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::db::{Dao, TenebrousDbConn};
|
use crate::db::{Dao, TenebrousDbConn};
|
||||||
|
use crate::schema::users;
|
||||||
use argon2::{self, Config, Error as ArgonError};
|
use argon2::{self, Config, Error as ArgonError};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rocket::outcome::IntoOutcome;
|
use rocket::outcome::IntoOutcome;
|
||||||
|
@ -11,7 +12,7 @@ pub(crate) fn hash_password(raw_password: &str) -> Result<String, ArgonError> {
|
||||||
argon2::hash_encoded(raw_password.as_bytes(), &salt, &config)
|
argon2::hash_encoded(raw_password.as_bytes(), &salt, &config)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Serialize, Debug, sqlx::FromRow)]
|
#[derive(Eq, PartialEq, Serialize, Debug, Queryable)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
@ -24,11 +25,10 @@ impl User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn attempt_load_user<'a>(db: &'a TenebrousDbConn<'a>, id: i32) -> Option<User> {
|
async fn attempt_load_user<'a>(db: &'a TenebrousDbConn, id: i32) -> Option<User> {
|
||||||
db.load_user_by_id(id).await.ok().flatten()
|
db.load_user_by_id(id).await.ok().flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait implementation to get the logged in user.
|
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl<'a, 'r> FromRequest<'a, 'r> for &'a User {
|
impl<'a, 'r> FromRequest<'a, 'r> for &'a User {
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
@ -53,7 +53,9 @@ impl<'a, 'r> FromRequest<'a, 'r> for &'a User {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NewUser<'a> {
|
#[derive(Insertable)]
|
||||||
pub username: &'a str,
|
#[table_name = "users"]
|
||||||
pub password: &'a str,
|
pub struct NewUser {
|
||||||
|
pub username: String,
|
||||||
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,13 +24,13 @@ pub(crate) fn routes() -> Vec<rocket::Route> {
|
||||||
/// logged in, the owner of the character is not found, or the logged
|
/// logged in, the owner of the character is not found, or the logged
|
||||||
/// in user does not have the permission to access this character.
|
/// in user does not have the permission to access this character.
|
||||||
async fn load_character(
|
async fn load_character(
|
||||||
conn: &TenebrousDbConn<'_>,
|
conn: &TenebrousDbConn,
|
||||||
logged_in_user: Option<&User>,
|
logged_in_user: Option<&User>,
|
||||||
owner: String,
|
owner: String,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
) -> Result<Character, Error> {
|
) -> Result<Character, Error> {
|
||||||
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
||||||
let owner = conn.load_user(&owner).await?.ok_or(Error::NotFound)?;
|
let owner = conn.load_user(owner).await?.ok_or(Error::NotFound)?;
|
||||||
let character: Character = conn
|
let character: Character = conn
|
||||||
.load_character(character_id)
|
.load_character(character_id)
|
||||||
.await?
|
.await?
|
||||||
|
@ -71,7 +71,7 @@ mod cofd {
|
||||||
owner: String,
|
owner: String,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
attr_update: Proto<Attribute>,
|
attr_update: Proto<Attribute>,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
logged_in_user: Option<&User>,
|
logged_in_user: Option<&User>,
|
||||||
) -> Result<&'a str, Error> {
|
) -> Result<&'a str, Error> {
|
||||||
let mut character = load_character(&conn, logged_in_user, owner, character_id).await?;
|
let mut character = load_character(&conn, logged_in_user, owner, character_id).await?;
|
||||||
|
@ -96,7 +96,7 @@ mod cofd {
|
||||||
);
|
);
|
||||||
|
|
||||||
character.update_data(sheet)?;
|
character.update_data(sheet)?;
|
||||||
conn.update_character_sheet(&character).await?;
|
conn.update_character_sheet(character).await?;
|
||||||
Ok("lol")
|
Ok("lol")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@ mod cofd {
|
||||||
owner: String,
|
owner: String,
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
info: Proto<Skills>,
|
info: Proto<Skills>,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
) -> &'a str {
|
) -> &'a str {
|
||||||
"lol"
|
"lol"
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,10 +50,10 @@ fn registration_error_redirect<S: AsRef<str>>(message: S) -> Flash<Redirect> {
|
||||||
async fn login(
|
async fn login(
|
||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
login: Form<Login>,
|
login: Form<Login>,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
) -> Result<Redirect, Flash<Redirect>> {
|
) -> Result<Redirect, Flash<Redirect>> {
|
||||||
let user = conn
|
let user = conn
|
||||||
.load_user(&login.username)
|
.load_user(login.username.clone())
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
error!("login - error loading user user: {}", e);
|
error!("login - error loading user user: {}", e);
|
||||||
|
@ -104,9 +104,12 @@ fn register_page(flash: Option<FlashMessage>) -> Template {
|
||||||
async fn register(
|
async fn register(
|
||||||
mut cookies: &CookieJar<'_>,
|
mut cookies: &CookieJar<'_>,
|
||||||
registration: Form<Registration>,
|
registration: Form<Registration>,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
) -> Result<Redirect, Flash<Redirect>> {
|
) -> Result<Redirect, Flash<Redirect>> {
|
||||||
let existing_user = conn.load_user(®istration.username).await.map_err(|e| {
|
let existing_user = conn
|
||||||
|
.load_user(registration.username.clone())
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
error!("registration - error loading existing user: {}", e);
|
error!("registration - error loading existing user: {}", e);
|
||||||
registration_error_redirect("There was an error attempting to register.")
|
registration_error_redirect("There was an error attempting to register.")
|
||||||
})?;
|
})?;
|
||||||
|
@ -124,8 +127,8 @@ async fn register(
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let user = NewUser {
|
let user = NewUser {
|
||||||
username: ®istration.username,
|
username: registration.username.clone(),
|
||||||
password: &hashed_pw,
|
password: hashed_pw,
|
||||||
};
|
};
|
||||||
|
|
||||||
let user = conn.insert_user(user).await.map_err(|e| {
|
let user = conn.insert_user(user).await.map_err(|e| {
|
||||||
|
|
|
@ -50,10 +50,10 @@ fn view_character_template(user: &User, character: Character) -> Result<Template
|
||||||
async fn view_character(
|
async fn view_character(
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
username: String,
|
username: String,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
logged_in_user: Option<&User>,
|
logged_in_user: Option<&User>,
|
||||||
) -> Result<Template, Error> {
|
) -> Result<Template, Error> {
|
||||||
let user = &conn.load_user(&username).await?.ok_or(Error::NotFound)?;
|
let user = &conn.load_user(username).await?.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
let character = conn
|
let character = conn
|
||||||
.load_character(character_id)
|
.load_character(character_id)
|
||||||
|
|
|
@ -25,9 +25,9 @@ pub(super) async fn edit_character_page(
|
||||||
character_id: i32,
|
character_id: i32,
|
||||||
owner: String,
|
owner: String,
|
||||||
logged_in_user: Option<&User>,
|
logged_in_user: Option<&User>,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
) -> Result<Template, Error> {
|
) -> Result<Template, Error> {
|
||||||
let owner = conn.load_user(&owner).await?.ok_or(Error::NotFound)?;
|
let owner = conn.load_user(owner).await?.ok_or(Error::NotFound)?;
|
||||||
|
|
||||||
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
let logged_in_user = logged_in_user.ok_or(Error::NotLoggedIn)?;
|
||||||
let character = conn
|
let character = conn
|
||||||
|
|
|
@ -59,7 +59,7 @@ impl NewCharacterContext {
|
||||||
async fn create_new_character(
|
async fn create_new_character(
|
||||||
form: &Form<NewCharacterForm>,
|
form: &Form<NewCharacterForm>,
|
||||||
user_id: i32,
|
user_id: i32,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let system: CharacterDataType = *form.system.as_ref().unwrap();
|
let system: CharacterDataType = *form.system.as_ref().unwrap();
|
||||||
let sheet: Vec<u8> = system.create_data()?.to_vec();
|
let sheet: Vec<u8> = system.create_data()?.to_vec();
|
||||||
|
@ -67,10 +67,10 @@ async fn create_new_character(
|
||||||
let insert = NewCharacter {
|
let insert = NewCharacter {
|
||||||
user_id: user_id,
|
user_id: user_id,
|
||||||
viewable: true,
|
viewable: true,
|
||||||
character_name: &form.name,
|
character_name: form.name.clone(),
|
||||||
data_type: system,
|
data_type: system,
|
||||||
data_version: 1,
|
data_version: 1,
|
||||||
data: &sheet,
|
data: sheet,
|
||||||
};
|
};
|
||||||
|
|
||||||
conn.insert_character(insert).await?;
|
conn.insert_character(insert).await?;
|
||||||
|
@ -97,7 +97,7 @@ pub(super) fn new_character_page(_logged_in_user: &User) -> Result<Template, Err
|
||||||
pub(super) async fn new_character_submit(
|
pub(super) async fn new_character_submit(
|
||||||
form: Form<NewCharacterForm>,
|
form: Form<NewCharacterForm>,
|
||||||
logged_in_user: &User,
|
logged_in_user: &User,
|
||||||
conn: TenebrousDbConn<'_>,
|
conn: TenebrousDbConn,
|
||||||
) -> Result<Redirect, Template> {
|
) -> Result<Redirect, Template> {
|
||||||
if let Err(e) = &form.system {
|
if let Err(e) = &form.system {
|
||||||
return Err(render_error(&form, e.to_string().clone()));
|
return Err(render_error(&form, e.to_string().clone()));
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub struct UserHomeContext<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn user_index(user: &User, conn: TenebrousDbConn<'_>) -> Result<Template, Error> {
|
async fn user_index(user: &User, conn: TenebrousDbConn) -> Result<Template, Error> {
|
||||||
let characters: Vec<StrippedCharacter> = conn
|
let characters: Vec<StrippedCharacter> = conn
|
||||||
.load_character_list(user.id)
|
.load_character_list(user.id)
|
||||||
.await?
|
.await?
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
use crate::models::characters::*;
|
||||||
|
|
||||||
|
characters (id) {
|
||||||
|
id -> Integer,
|
||||||
|
user_id -> Integer,
|
||||||
|
viewable -> Bool,
|
||||||
|
character_name -> Text,
|
||||||
|
data_type -> CharacterDataTypeMapping,
|
||||||
|
data_version -> Integer,
|
||||||
|
data -> Binary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
use crate::models::characters::*;
|
||||||
|
|
||||||
|
users (id) {
|
||||||
|
id -> Integer,
|
||||||
|
username -> Text,
|
||||||
|
password -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!(
|
||||||
|
characters,
|
||||||
|
users,
|
||||||
|
);
|
Loading…
Reference in New Issue