Add crates for desktop, mobile and simple ui
This commit is contained in:
parent
18de94b3c9
commit
1bdaa45f52
43 changed files with 3013 additions and 1352 deletions
3016
Cargo.lock
generated
3016
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
16
Cargo.toml
16
Cargo.toml
|
@ -15,6 +15,7 @@ async-graphql = { version = "7.0", features = ["chrono"] }
|
|||
chrono = {version = "0.4", features = ["serde"]}
|
||||
chrono-tz = {version = "0.10.3", features = ["serde"]}
|
||||
clap = {version = "4.5", features = ["derive"]}
|
||||
dioxus = { version = "0.6.0" }
|
||||
dirs = "6.0.0"
|
||||
dotenv = "0.15.0"
|
||||
lazy-supplements-core.path = "lazy-supplements/lazy-supplements-core"
|
||||
|
@ -24,8 +25,7 @@ progress-pile-client = { path = "progress-pile-client", default-features = false
|
|||
progress-pile-core = { path = "progress-pile-core", default-features = false }
|
||||
progress-pile-migration-client.path = "progress-pile-migration-client"
|
||||
progress-pile-migration-core.path = "progress-pile-migration-core"
|
||||
progress-pile-migration-server.path = "progress-pile-migration-server"
|
||||
progress-pile-server.path = "progress-pile-server"
|
||||
progress-pile-simple-ui.path = "progress-pile-simple-ui"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.20.0"
|
||||
thiserror = "2.0.12"
|
||||
|
@ -52,3 +52,15 @@ features = [
|
|||
"sqlx-sqlite",
|
||||
]
|
||||
|
||||
[profile]
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
|
||||
[profile.server-dev]
|
||||
inherits = "dev"
|
||||
|
||||
[profile.android-dev]
|
||||
inherits = "dev"
|
||||
|
||||
|
|
0
progress-pile-core/src/ui/mod.rs
Normal file
0
progress-pile-core/src/ui/mod.rs
Normal file
0
progress-pile-core/src/ui/simple/mod.rs
Normal file
0
progress-pile-core/src/ui/simple/mod.rs
Normal file
7
progress-pile-desktop/.gitignore
vendored
Normal file
7
progress-pile-desktop/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target
|
||||
.DS_Store
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
|
@ -1,10 +1,17 @@
|
|||
[package]
|
||||
name = "progress-pile-desktop"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
description.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
version = "0.1.0"
|
||||
authors = ["fluo10 <fluo10.dev@fireturtle.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
lazy-supplements-desktop.workspace = true
|
||||
dioxus.workspace = true
|
||||
progress-pile-simple-ui.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["desktop"]
|
||||
web = ["dioxus/web"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
mobile = ["dioxus/mobile"]
|
||||
|
|
21
progress-pile-desktop/Dioxus.toml
Normal file
21
progress-pile-desktop/Dioxus.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[application]
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "progress-pile-desktop"
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# Additional CSS style files
|
||||
style = []
|
||||
|
||||
# Additional JavaScript files
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Javascript code file
|
||||
# serve: [dev-server] only
|
||||
script = []
|
25
progress-pile-desktop/README.md
Normal file
25
progress-pile-desktop/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Development
|
||||
|
||||
Your new bare-bones project includes minimal organization with a single `main.rs` file and a few assets.
|
||||
|
||||
```
|
||||
project/
|
||||
├─ assets/ # Any assets that are used by the app should be placed here
|
||||
├─ src/
|
||||
│ ├─ main.rs # main.rs is the entry point to your application and currently contains all components for the app
|
||||
├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project
|
||||
```
|
||||
|
||||
### Serving Your App
|
||||
|
||||
Run the following command in the root of your project to start developing with the default platform:
|
||||
|
||||
```bash
|
||||
dx serve
|
||||
```
|
||||
|
||||
To run for a different platform, use the `--platform platform` flag. E.g.
|
||||
```bash
|
||||
dx serve --platform desktop
|
||||
```
|
||||
|
8
progress-pile-desktop/clippy.toml
Normal file
8
progress-pile-desktop/clippy.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
await-holding-invalid-types = [
|
||||
"generational_box::GenerationalRef",
|
||||
{ path = "generational_box::GenerationalRef", reason = "Reads should not be held over an await point. This will cause any writes to fail while the await is pending since the read borrow is still active." },
|
||||
"generational_box::GenerationalRefMut",
|
||||
{ path = "generational_box::GenerationalRefMut", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
"dioxus_signals::Write",
|
||||
{ path = "dioxus_signals::Write", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
]
|
|
@ -1,3 +1,3 @@
|
|||
fn main() {
|
||||
println!("Hello, world!");
|
||||
dioxus::launch(progress_pile_simple_ui::App);
|
||||
}
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "progress-pile-migration-server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "1", features = ["attributes", "tokio1"] }
|
||||
progress-pile-migration-core.workspace = true
|
||||
|
||||
[dependencies.sea-orm-migration]
|
||||
version = "1.1.0"
|
||||
features = [
|
||||
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
|
||||
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
|
||||
# e.g.
|
||||
# "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
|
||||
# "sqlx-postgres", # `DATABASE_DRIVER` feature
|
||||
]
|
|
@ -1,41 +0,0 @@
|
|||
# Running Migrator CLI
|
||||
|
||||
- Generate a new migration file
|
||||
```sh
|
||||
cargo run -- generate MIGRATION_NAME
|
||||
```
|
||||
- Apply all pending migrations
|
||||
```sh
|
||||
cargo run
|
||||
```
|
||||
```sh
|
||||
cargo run -- up
|
||||
```
|
||||
- Apply first 10 pending migrations
|
||||
```sh
|
||||
cargo run -- up -n 10
|
||||
```
|
||||
- Rollback last applied migrations
|
||||
```sh
|
||||
cargo run -- down
|
||||
```
|
||||
- Rollback last 10 applied migrations
|
||||
```sh
|
||||
cargo run -- down -n 10
|
||||
```
|
||||
- Drop all tables from the database, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- fresh
|
||||
```
|
||||
- Rollback all applied migrations, then reapply all migrations
|
||||
```sh
|
||||
cargo run -- refresh
|
||||
```
|
||||
- Rollback all applied migrations
|
||||
```sh
|
||||
cargo run -- reset
|
||||
```
|
||||
- Check the status of all migrations
|
||||
```sh
|
||||
cargo run -- status
|
||||
```
|
|
@ -1,12 +0,0 @@
|
|||
pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20220101_000001_create_table;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![Box::new(m20220101_000001_create_table::Migration)]
|
||||
}
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
use progress_pile_migration_core::m20220101_000001_create_table::{
|
||||
TableMigration,
|
||||
ProgressCategory as DefaultProgressCategory,
|
||||
ProgressEntry as DefaultProgressEntry,
|
||||
PK_PROGRESS_CATEGORY,
|
||||
PK_PROGRESS_ENTITY,
|
||||
};
|
||||
use sea_orm_migration::{prelude::*, schema::*};
|
||||
|
||||
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
User::up(manager).await?;
|
||||
AccessToken::up(manager).await?;
|
||||
ProgressCategory::up(manager).await?;
|
||||
ProgressEntry::up(manager).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
ProgressEntry::down(manager).await?;
|
||||
ProgressCategory::down(manager).await?;
|
||||
AccessToken::down(manager).await?;
|
||||
User::down(manager).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum User {
|
||||
Table,
|
||||
Id,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
LoginName,
|
||||
PasswordHash,
|
||||
}
|
||||
|
||||
static IDX_USER_LOGIN_NAME: &str = "idx_user_login_name";
|
||||
static IDX_USER_CREATED_AT: &str = "idx_user_created_at";
|
||||
static IDX_USER_UPDATED_AT: &str = "idx_user_updated_at";
|
||||
static IDX_USER_DELETED_AT: &str = "idx_user_deleted_at";
|
||||
|
||||
impl TableMigration for User {
|
||||
|
||||
fn table_create_statement() -> TableCreateStatement{
|
||||
Table::create()
|
||||
.table(Self::Table)
|
||||
.if_not_exists()
|
||||
.col(pk_auto(Self::Id))
|
||||
.col(timestamp_with_time_zone(Self::CreatedAt))
|
||||
.col(timestamp_with_time_zone(Self::UpdatedAt))
|
||||
.col(timestamp_with_time_zone_null(Self::DeletedAt))
|
||||
.col(string_uniq(Self::LoginName))
|
||||
.col(string(Self::PasswordHash))
|
||||
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
vec![
|
||||
Index::create().name(IDX_USER_LOGIN_NAME)
|
||||
.table(Self::Table)
|
||||
.col(Self::LoginName)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_USER_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col((Self::CreatedAt, IndexOrder::Desc))
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_USER_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col((Self::UpdatedAt, IndexOrder::Desc))
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_USER_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col((Self::DeletedAt, IndexOrder::Desc))
|
||||
.to_owned(),
|
||||
]
|
||||
}
|
||||
|
||||
fn table_drop_statement() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum AccessToken{
|
||||
Table,
|
||||
Id,
|
||||
UserId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
ExpiredAt,
|
||||
TokenValue,
|
||||
Note,
|
||||
}
|
||||
|
||||
static IDX_ACCESS_TOKEN_TOKEN_VALUE: &str = "idx_access_token_token_value";
|
||||
static IDX_ACCESS_TOKEN_CREATED_AT: &str = "idx_access_token_created_at";
|
||||
static IDX_ACCESS_TOKEN_UPDATED_AT: &str = "idx_access_token_updated_at";
|
||||
static IDX_ACCESS_TOKEN_EXPIRED_AT: &str = "idx_access_token_expired_at";
|
||||
static IDX_ACCESS_TOKEN_USER_ID_CREATED_AT: &str = "idx_access_token_user_id_created_at";
|
||||
static IDX_ACCESS_TOKEN_USER_ID_UPDATED_AT: &str = "idx_access_token_user_id_updated_at";
|
||||
static IDX_ACCESS_TOKEN_USER_ID_EXPIRED_AT: &str = "idx_access_token_user_id_expired_at";
|
||||
static FK_ACCESS_TOKEN_USER: &str = "fk_access_token_user";
|
||||
|
||||
impl TableMigration for AccessToken {
|
||||
|
||||
fn table_create_statement() -> TableCreateStatement {
|
||||
Table::create()
|
||||
.table(Self::Table)
|
||||
.if_not_exists()
|
||||
.col(pk_auto(Self::Id))
|
||||
.col(integer(Self::UserId))
|
||||
.col(timestamp_with_time_zone(Self::CreatedAt))
|
||||
.col(timestamp_with_time_zone(Self::UpdatedAt))
|
||||
.col(timestamp_with_time_zone_null(Self::ExpiredAt))
|
||||
.col(string(Self::TokenValue))
|
||||
.col(string(Self::Note))
|
||||
.foreign_key(ForeignKey::create()
|
||||
.name(FK_ACCESS_TOKEN_USER)
|
||||
.from(Self::Table, Self::UserId)
|
||||
.to(User::Table, User::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
)
|
||||
.to_owned()
|
||||
}
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
vec![
|
||||
Index::create().name(IDX_ACCESS_TOKEN_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_ACCESS_TOKEN_EXPIRED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::ExpiredAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_ACCESS_TOKEN_TOKEN_VALUE)
|
||||
.table(Self::Table)
|
||||
.col(Self::TokenValue)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_ACCESS_TOKEN_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_ACCESS_TOKEN_USER_ID_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_ACCESS_TOKEN_USER_ID_EXPIRED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::ExpiredAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_ACCESS_TOKEN_USER_ID_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
]
|
||||
}
|
||||
fn table_drop_statement() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressCategory {
|
||||
Table,
|
||||
UserId,
|
||||
}
|
||||
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_NAME: &str = "idx_progress_category_user_id_name";
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_CREATED_AT: &str = "idx_progress_category_user_id_created_at";
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_UPDATED_AT: &str = "idx_progress_category_user_id_updated_at";
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_DELETED_AT: &str = "idx_progress_category_user_id_deleted_at";
|
||||
static FK_PROGRESS_CATEGORY_USER: &str = "fk_progress_category_user";
|
||||
|
||||
impl TableMigration for ProgressCategory {
|
||||
|
||||
fn table_create_statement() -> TableCreateStatement{
|
||||
let mut tcs = DefaultProgressCategory::table_create_statement();
|
||||
tcs.col(integer(Self::UserId));
|
||||
tcs.foreign_key(ForeignKey::create().name(FK_PROGRESS_CATEGORY_USER).from(Self::Table, Self::UserId)
|
||||
.to(User::Table, User::Id));
|
||||
tcs.primary_key(Index::create().name(PK_PROGRESS_CATEGORY).col(Self::UserId).col(DefaultProgressCategory::Id));
|
||||
tcs
|
||||
}
|
||||
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
[DefaultProgressCategory::index_create_statements(), vec![
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressCategory::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressCategory::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_NAME)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressCategory::Name)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressCategory::UpdatedAt)
|
||||
.to_owned(),
|
||||
]].concat()
|
||||
}
|
||||
fn table_drop_statement() -> TableDropStatement {
|
||||
DefaultProgressCategory::table_drop_statement()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressEntry {
|
||||
Table,
|
||||
UserId,
|
||||
}
|
||||
|
||||
static IDX_PROGRESS_ENTITY_USER_ID_CREATED_AT: &str = "idx_progress_entity_user_id_created_at";
|
||||
static IDX_PROGRESS_ENTITY_USER_ID_UPDATED_AT: &str = "idx_progress_entity_user_id_updated_at";
|
||||
static IDX_PROGRESS_ENTITY_USER_ID_DELETED_AT: &str = "idx_progress_entity_user_id_deleted_at";
|
||||
static IDX_PROGRESS_ENTITY_USER_ID_PROGRESSED_AT: &str = "idx_progress_entity_user_id_progressed_at";
|
||||
static FK_PROGRESS_ENTITY_PROGRESS_CATEGORY: &str = "fk_progress_entity_progress_category";
|
||||
static FK_PROGRESS_ENTITY_USER: &str = "fk_progress_entity_user";
|
||||
|
||||
impl TableMigration for ProgressEntry {
|
||||
|
||||
fn table_create_statement() -> TableCreateStatement{
|
||||
let mut tcs: TableCreateStatement = DefaultProgressEntry::table_create_statement();
|
||||
tcs.col(integer(Self::UserId));
|
||||
tcs.foreign_key(ForeignKey::create()
|
||||
.name(FK_PROGRESS_ENTITY_USER)
|
||||
.from(Self::Table, Self::UserId)
|
||||
.to(User::Table, User::Id)
|
||||
);
|
||||
tcs.primary_key(Index::create().name(PK_PROGRESS_ENTITY).col(Self::UserId).col(DefaultProgressEntry::Id));
|
||||
|
||||
tcs
|
||||
|
||||
}
|
||||
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
let mut default = DefaultProgressEntry::index_create_statements();
|
||||
default.append(&mut vec![
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressEntry::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressEntry::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_PROGRESSED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressEntry::ProgressedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(DefaultProgressEntry::UpdatedAt)
|
||||
.to_owned(),
|
||||
]);
|
||||
default
|
||||
}
|
||||
|
||||
fn table_drop_statement() -> TableDropStatement {
|
||||
DefaultProgressEntry::table_drop_statement()
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
cli::run_cli(progress_pile_migration_server::Migrator).await;
|
||||
}
|
7
progress-pile-mobile/.gitignore
vendored
Normal file
7
progress-pile-mobile/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target
|
||||
.DS_Store
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
17
progress-pile-mobile/Cargo.toml
Normal file
17
progress-pile-mobile/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "progress-pile-mobile"
|
||||
version = "0.1.0"
|
||||
authors = ["fluo10 <fluo10.dev@fireturtle.net>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus.workspace = true
|
||||
progress-pile-simple-ui.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["mobile"]
|
||||
web = ["dioxus/web"]
|
||||
desktop = ["dioxus/desktop"]
|
||||
mobile = ["dioxus/mobile"]
|
21
progress-pile-mobile/Dioxus.toml
Normal file
21
progress-pile-mobile/Dioxus.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[application]
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "progress-pile-mobile"
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# Additional CSS style files
|
||||
style = []
|
||||
|
||||
# Additional JavaScript files
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Javascript code file
|
||||
# serve: [dev-server] only
|
||||
script = []
|
25
progress-pile-mobile/README.md
Normal file
25
progress-pile-mobile/README.md
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Development
|
||||
|
||||
Your new bare-bones project includes minimal organization with a single `main.rs` file and a few assets.
|
||||
|
||||
```
|
||||
project/
|
||||
├─ assets/ # Any assets that are used by the app should be placed here
|
||||
├─ src/
|
||||
│ ├─ main.rs # main.rs is the entry point to your application and currently contains all components for the app
|
||||
├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project
|
||||
```
|
||||
|
||||
### Serving Your App
|
||||
|
||||
Run the following command in the root of your project to start developing with the default platform:
|
||||
|
||||
```bash
|
||||
dx serve
|
||||
```
|
||||
|
||||
To run for a different platform, use the `--platform platform` flag. E.g.
|
||||
```bash
|
||||
dx serve --platform desktop
|
||||
```
|
||||
|
8
progress-pile-mobile/clippy.toml
Normal file
8
progress-pile-mobile/clippy.toml
Normal file
|
@ -0,0 +1,8 @@
|
|||
await-holding-invalid-types = [
|
||||
"generational_box::GenerationalRef",
|
||||
{ path = "generational_box::GenerationalRef", reason = "Reads should not be held over an await point. This will cause any writes to fail while the await is pending since the read borrow is still active." },
|
||||
"generational_box::GenerationalRefMut",
|
||||
{ path = "generational_box::GenerationalRefMut", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
"dioxus_signals::Write",
|
||||
{ path = "dioxus_signals::Write", reason = "Write should not be held over an await point. This will cause any reads or writes to fail while the await is pending since the write borrow is still active." },
|
||||
]
|
5
progress-pile-mobile/src/main.rs
Normal file
5
progress-pile-mobile/src/main.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(progress_pile_simple_ui::App);
|
||||
}
|
|
@ -1,26 +0,0 @@
|
|||
[package]
|
||||
name = "progress-pile-server"
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
argon2 = "0.5.3"
|
||||
async-graphql.workspace = true
|
||||
axum = "0.8.4"
|
||||
clap = {workspace = true, features = ["derive"]}
|
||||
progress-pile-core = {workspace = true, features = ["desktop"]}
|
||||
progress-pile-migration-server.workspace = true
|
||||
chrono = {workspace = true}
|
||||
sea-orm = { workspace = true, features = ["sqlx-postgres"] }
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
toml.workspace = true
|
||||
uuid.workspace = true
|
||||
rand = "0.9.1"
|
||||
async-graphql-axum = "7.0.16"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
|
@ -1,45 +0,0 @@
|
|||
use argon2::{
|
||||
Argon2,
|
||||
PasswordHasher,
|
||||
PasswordVerifier,
|
||||
password_hash::{
|
||||
Salt,
|
||||
SaltString,
|
||||
PasswordHash,
|
||||
rand_core::OsRng,
|
||||
},
|
||||
};
|
||||
use chrono::format::parse;
|
||||
use crate::error::Error;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
pub fn try_hash_password(password: &str) -> Result<String, Error> {
|
||||
let mut rng = OsRng::default();
|
||||
let salt_string= SaltString::generate(&mut rng);
|
||||
let salt = salt_string.as_salt();
|
||||
let hash = Argon2::default().hash_password(password.as_bytes(), salt).or(Err(Error::PasswordHash("Hashing password with salt".to_string())))?;
|
||||
Ok(hash.to_string())
|
||||
}
|
||||
|
||||
pub fn try_verify_password(password: &str, password_hash: &str) -> Result<bool, Error> {
|
||||
let parsed_hash = PasswordHash::new(password_hash).or(Err(Error::PasswordHash("Failed to parse password hash string".to_string())))?;
|
||||
match Argon2::default().verify_password(password.as_bytes(), &parsed_hash) {
|
||||
Ok(_) => Ok(true),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use argon2::password_hash::rand_core::OsRng;
|
||||
|
||||
use super::*;
|
||||
#[test]
|
||||
fn test_password() {
|
||||
let valid_password = "valid";
|
||||
let invalid_password = "invalid";
|
||||
let hash = try_hash_password(valid_password).unwrap();
|
||||
assert!(try_verify_password(valid_password, &hash).unwrap());
|
||||
assert!(!try_verify_password(invalid_password, &hash).unwrap());
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
use crate::config::PartialServerConfig;
|
||||
use clap::Parser;
|
||||
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::config::ServerConfig;
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Cli {
|
||||
#[arg(short, long)]
|
||||
pub config_file: Option<PathBuf>,
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use clap::Args;
|
||||
use sea_orm::ConnectOptions;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct DatabaseConfig {
|
||||
pub url: String,
|
||||
pub max_connections: Option<u32>,
|
||||
pub min_connections: Option<u32>,
|
||||
pub connect_timeout: Option<Duration>,
|
||||
pub acquire_timeout: Option<Duration>,
|
||||
pub idle_timeout: Option<Duration>,
|
||||
pub max_lifetime: Option<Duration>,
|
||||
pub sqlx_logging: bool,
|
||||
}
|
||||
|
||||
impl Into<ConnectOptions> for &DatabaseConfig {
|
||||
fn into(self) -> ConnectOptions {
|
||||
let mut opt = ConnectOptions::new(&self.url);
|
||||
if let Some(x) = self.max_connections {
|
||||
opt.max_connections(x);
|
||||
}
|
||||
if let Some(x) = self.min_connections {
|
||||
opt.min_connections(x);
|
||||
}
|
||||
if let Some(x) = self.connect_timeout {
|
||||
opt.connect_timeout(x);
|
||||
}
|
||||
if let Some(x) = self.acquire_timeout {
|
||||
opt.acquire_timeout(x);
|
||||
}
|
||||
if let Some(x) = self.idle_timeout {
|
||||
opt.idle_timeout(x);
|
||||
}
|
||||
if let Some(x) = self.max_lifetime {
|
||||
opt.max_lifetime(x);
|
||||
}
|
||||
opt.sqlx_logging(self.sqlx_logging);
|
||||
|
||||
opt
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PartialDatabaseConfig> for DatabaseConfig{
|
||||
type Error = Error;
|
||||
fn try_from(p: PartialDatabaseConfig) -> Result<DatabaseConfig, Self::Error> {
|
||||
Ok(DatabaseConfig{
|
||||
url: p.url.ok_or(Error::MissingConfig("url".to_string()))?,
|
||||
max_connections: p.max_connections,
|
||||
min_connections: p.min_connections,
|
||||
connect_timeout: p.connect_timeout,
|
||||
acquire_timeout: p.acquire_timeout,
|
||||
idle_timeout: p.idle_timeout,
|
||||
max_lifetime: p.max_lifetime,
|
||||
sqlx_logging: p.sqlx_logging.ok_or(Error::MissingConfig("sqlx_logging".to_string()))?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
||||
#[derive(Args)]
|
||||
pub struct PartialDatabaseConfig {
|
||||
#[arg(long)]
|
||||
pub url: Option<String>,
|
||||
#[arg(long)]
|
||||
pub max_connections: Option<u32>,
|
||||
#[arg(long)]
|
||||
pub min_connections: Option<u32>,
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub connect_timeout: Option<Duration>,
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub acquire_timeout: Option<Duration>,
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub idle_timeout: Option<Duration>,
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub max_lifetime: Option<Duration>,
|
||||
#[arg(long)]
|
||||
pub sqlx_logging: Option<bool>
|
||||
}
|
||||
|
||||
fn parse_duration(arg: &str) -> Result<std::time::Duration, Error> {
|
||||
let seconds = arg.parse()?;
|
||||
Ok(std::time::Duration::from_secs(seconds))
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
mod database;
|
||||
|
||||
pub use database::{
|
||||
DatabaseConfig,
|
||||
PartialDatabaseConfig
|
||||
};
|
||||
|
||||
pub struct ServerConfig {}
|
||||
|
||||
pub struct PartialServerConfig {}
|
|
@ -1,55 +0,0 @@
|
|||
use core::time;
|
||||
|
||||
use async_graphql::*;
|
||||
use chrono::Local;
|
||||
use sea_orm::entity::{
|
||||
Set,
|
||||
prelude::*
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel,)]
|
||||
#[sea_orm(table_name = "access_token")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
#[sea_orm(indexed)]
|
||||
pub user_id: i32,
|
||||
#[sea_orm(indexed)]
|
||||
pub token_value: String,
|
||||
pub note: String,
|
||||
#[sea_orm(indexed)]
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub expired_at: Option<DateTimeWithTimeZone>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
User,
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
impl ActiveModel {
|
||||
pub fn new() -> Self {
|
||||
let timestamp = Local::now().fixed_offset();
|
||||
Self{
|
||||
note: Set("".to_string()),
|
||||
created_at: Set(timestamp),
|
||||
updated_at: Set(timestamp),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
|
@ -1,91 +0,0 @@
|
|||
mod access_token;
|
||||
mod progress_category;
|
||||
mod progress_entry;
|
||||
mod user;
|
||||
|
||||
|
||||
pub use access_token::{
|
||||
ActiveModel as AccessTokenActiveModel,
|
||||
Column as AccessTokenColumn,
|
||||
Entity as AccessTokenEntity,
|
||||
Model as AccessTokenModel,
|
||||
};
|
||||
|
||||
pub use progress_category::{
|
||||
ActiveModel as ProgressCategoryActiveModel,
|
||||
Column as ProgressCategoryColumn,
|
||||
Entity as ProgressCategoryEntity,
|
||||
Model as ProgressCategoryModel,
|
||||
};
|
||||
|
||||
pub use progress_entry::{
|
||||
ActiveModel as ProgressEntryActiveModel,
|
||||
Column as ProgressEntryColumn,
|
||||
Entity as ProgressEntryEntity,
|
||||
Model as ProgressEntryModel,
|
||||
};
|
||||
|
||||
|
||||
pub use user::{
|
||||
ActiveModel as UserActiveModel,
|
||||
Column as UserColumn,
|
||||
Entity as UserEntity,
|
||||
Model as UserModel,
|
||||
};
|
||||
|
||||
|
||||
pub use progress_pile_core::entity::*;
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use std::time::Duration;
|
||||
use chrono::{offset, FixedOffset, Local, TimeZone};
|
||||
use sea_orm::{entity::*, query::*, ConnectOptions, Database};
|
||||
use crate::{entity::*, global::GLOBAL};
|
||||
|
||||
#[tokio::test]
|
||||
async fn check_database_connection() {
|
||||
|
||||
let db = GLOBAL.get_or_init_temporary_database().await;
|
||||
assert!(db.ping().await.is_ok());
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn check_insert_entity() {
|
||||
let db = GLOBAL.get_or_init_temporary_database().await;
|
||||
|
||||
let timestamp = Local::now().fixed_offset();
|
||||
|
||||
let user = UserActiveModel{
|
||||
login_name: Set("admin".to_owned()),
|
||||
password_hash: Set("admin".to_owned()),
|
||||
created_at: Set(timestamp),
|
||||
updated_at: Set(timestamp),
|
||||
..UserActiveModel::new()
|
||||
}.insert(db).await.unwrap();
|
||||
|
||||
|
||||
let access_token = AccessTokenActiveModel{
|
||||
user_id: Set(user.id),
|
||||
..Default::default()
|
||||
}.insert(db).await.unwrap();
|
||||
|
||||
let progress_category = ProgressCategoryActiveModel{
|
||||
user_id: Set(user.id),
|
||||
name: Set("test_category".to_string()),
|
||||
..Default::default()
|
||||
}.insert(db)
|
||||
.await.unwrap();
|
||||
|
||||
ProgressEntryActiveModel {
|
||||
user_id: Set(user.id),
|
||||
progress_category_id: Set(progress_category.id),
|
||||
..Default::default()
|
||||
}.insert(db)
|
||||
.await.unwrap();
|
||||
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
use async_graphql::*;
|
||||
use chrono::Local;
|
||||
use sea_orm::{entity::prelude::*, ActiveValue::Set};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel,)]
|
||||
#[sea_orm(table_name = "progress_category")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub user_id: i32,
|
||||
#[sea_orm(indexed)]
|
||||
pub name: String,
|
||||
#[sea_orm(indexed)]
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub deleted_at: Option<DateTimeWithTimeZone>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::progress_entry::Entity")]
|
||||
ProgressEntry,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
User,
|
||||
}
|
||||
|
||||
impl Related<super::progress_entry::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ProgressEntry.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
impl ActiveModel {
|
||||
pub fn new() -> Self {
|
||||
let timestamp: DateTimeWithTimeZone = Local::now().fixed_offset();
|
||||
Self{
|
||||
id: Set(Uuid::new_v4()),
|
||||
created_at: Set(timestamp),
|
||||
updated_at: Set(timestamp),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
use async_graphql::*;
|
||||
use chrono::Local;
|
||||
use sea_orm::{entity::prelude::*, ActiveValue::Set};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "progress_entry")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub id: Uuid,
|
||||
#[sea_orm(primary_key, auto_increment = false)]
|
||||
pub user_id: i32,
|
||||
#[sea_orm(indexed)]
|
||||
pub progress_category_id: Uuid,
|
||||
#[sea_orm(indexed)]
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub deleted_at: Option<DateTimeWithTimeZone>,
|
||||
#[sea_orm(indexed)]
|
||||
pub progressed_at: DateTimeWithTimeZone,
|
||||
pub quantity: i32,
|
||||
pub note: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::progress_category::Entity",
|
||||
from = "Column::ProgressCategoryId",
|
||||
to = "super::progress_category::Column::Id"
|
||||
)]
|
||||
ProgressCategory,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::user::Entity",
|
||||
from = "Column::UserId",
|
||||
to = "super::user::Column::Id"
|
||||
)]
|
||||
User,
|
||||
}
|
||||
|
||||
|
||||
impl Related<super::progress_category::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ProgressCategory.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::user::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::User.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
impl ActiveModel {
|
||||
pub fn new() -> Self {
|
||||
let timestamp: DateTimeWithTimeZone = Local::now().fixed_offset();
|
||||
Self{
|
||||
id: Set(Uuid::new_v4()),
|
||||
created_at: Set(timestamp),
|
||||
updated_at: Set(timestamp),
|
||||
progressed_at: Set(timestamp),
|
||||
quantity: Set(1),
|
||||
note: Set("".to_string()),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,63 +0,0 @@
|
|||
use async_graphql::SimpleObject;
|
||||
use chrono::{DateTime, FixedOffset, Local,};
|
||||
use sea_orm::{entity::prelude::*, ActiveValue::Set};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel,)]
|
||||
#[sea_orm(table_name = "user")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
#[sea_orm(unique, indexed)]
|
||||
pub login_name: String,
|
||||
pub password_hash: String,
|
||||
#[sea_orm(indexed)]
|
||||
pub created_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub updated_at: DateTimeWithTimeZone,
|
||||
#[sea_orm(indexed)]
|
||||
pub deleted_at: Option<DateTimeWithTimeZone>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, DeriveRelation, EnumIter)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::access_token::Entity")]
|
||||
AccessToken,
|
||||
#[sea_orm(has_many = "super::progress_category::Entity")]
|
||||
ProgressCategory,
|
||||
#[sea_orm(has_many = "super::progress_entry::Entity")]
|
||||
ProgressEntry,
|
||||
}
|
||||
|
||||
impl Related<super::access_token::Entity> for Model {
|
||||
fn to() -> RelationDef {
|
||||
Relation::AccessToken.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::progress_category::Entity> for Model {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ProgressCategory.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::progress_entry::Entity> for Model {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ProgressEntry.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModel {
|
||||
pub fn new() -> Self {
|
||||
let timestamp = Local::now().fixed_offset();
|
||||
Self {
|
||||
created_at: Set(timestamp),
|
||||
updated_at: Set(timestamp),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
use progress_pile_migration_server::{Migrator, MigratorTrait};
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
use crate::error::Error;
|
||||
use tokio::sync::OnceCell;
|
||||
use progress_pile_core::global::GlobalDatabase;
|
||||
|
||||
use super::Global;
|
||||
|
||||
impl GlobalDatabase for Global {
|
||||
fn get_database(&self) -> Option<&DatabaseConnection> {
|
||||
self.database.get()
|
||||
}
|
||||
async fn get_or_try_init_database(&self) -> Result<&DatabaseConnection, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_or_try_init_database_with_connect_options<T>(&self, options: T) -> Result<&DatabaseConnection, Error> where
|
||||
T: Into<ConnectOptions>,
|
||||
{
|
||||
Ok(self.database.get_or_try_init(|| async {
|
||||
let db = Database::connect(options).await?;
|
||||
Migrator::up(&db, None).await?;
|
||||
Ok::<DatabaseConnection, Error>(db)
|
||||
}).await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::global::GLOBAL;
|
||||
|
||||
use super::*;
|
||||
|
||||
pub static TEST_DATABASE_URL: LazyLock<String> = LazyLock::new(|| {
|
||||
let mut temp_path = tempfile::NamedTempFile::new().unwrap().into_temp_path();
|
||||
temp_path.disable_cleanup(true);
|
||||
let url = "sqlite://".to_string() + temp_path.as_os_str().to_str().unwrap() + "?mode=rwc";
|
||||
println!("{}", &url);
|
||||
url
|
||||
});
|
||||
|
||||
impl Global {
|
||||
pub async fn get_or_init_temporary_database(&self) -> &DatabaseConnection {
|
||||
|
||||
self.get_or_try_init_database_with_connect_options(&*TEST_DATABASE_URL).await.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn connect_database () {
|
||||
let db = GLOBAL.get_or_init_temporary_database().await;
|
||||
assert!(db.ping().await.is_ok());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
use crate::config::ServerConfig;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
|
||||
mod database;
|
||||
|
||||
pub static GLOBAL: Global = Global{
|
||||
config: OnceCell::const_new(),
|
||||
database: OnceCell::const_new(),
|
||||
};
|
||||
pub struct Global {
|
||||
config: OnceCell<ServerConfig>,
|
||||
database: OnceCell<DatabaseConnection>,
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
mod mutation;
|
||||
mod query;
|
||||
|
||||
pub use progress_pile_core::graphql::*;
|
||||
|
||||
pub use mutation::Mutation;
|
||||
pub use query::Query;
|
||||
|
||||
use async_graphql::{EmptySubscription, Schema};
|
||||
use async_graphql_axum::GraphQL;
|
||||
use axum::{routing::get, Router};
|
||||
|
||||
|
||||
pub fn build_schema() -> Schema<Query, Mutation, EmptySubscription>{
|
||||
Schema::build(Query, Mutation, EmptySubscription).finish()
|
||||
}
|
||||
|
||||
pub fn build_service() -> GraphQL<Schema<Query, Mutation, EmptySubscription>> {
|
||||
GraphQL::new(build_schema())
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
use async_graphql::*;
|
||||
|
||||
use crate::{auth::try_hash_password, entity::UserActiveModel};
|
||||
|
||||
pub struct Mutation;
|
||||
|
||||
#[Object]
|
||||
impl Mutation {
|
||||
async fn login(&self, username:String, password: String) -> Result<String> {
|
||||
todo!()
|
||||
}
|
||||
async fn create_user(&self, username:String, password: String) -> Result<String> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
use async_graphql::{
|
||||
*,
|
||||
http::GraphiQLSource,
|
||||
};
|
||||
use axum::{
|
||||
response::{Html, IntoResponse},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use crate::{entity::{UserEntity, UserModel},};
|
||||
|
||||
pub struct Query;
|
||||
|
||||
#[Object]
|
||||
impl Query {
|
||||
pub async fn user(&self, user_name: String) -> Result<Option<String>> {
|
||||
todo!()
|
||||
}
|
||||
pub async fn users(&self) -> Result<Vec<String>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
pub mod cli;
|
||||
pub mod auth;
|
||||
pub mod config;
|
||||
pub mod entity;
|
||||
pub mod global;
|
||||
pub mod graphql;
|
||||
pub use progress_pile_core::error;
|
||||
|
||||
pub use cli::Cli;
|
||||
use async_graphql::{EmptySubscription, Schema};
|
||||
use async_graphql_axum::{
|
||||
GraphQL,
|
||||
|
||||
};
|
||||
use axum::{routing::get, Router};
|
||||
use crate::graphql::build_service;
|
||||
|
||||
|
||||
pub fn build_app() -> Router {
|
||||
let router = Router::new()
|
||||
.route_service("/graphql", build_service());
|
||||
router
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
use async_graphql::{http::{playground_source, GraphQLPlaygroundConfig}, *};
|
||||
use async_graphql_axum::GraphQL;
|
||||
|
||||
use progress_pile_server::{build_app, cli::Cli};
|
||||
use axum::{response::{Html, IntoResponse}, routing::get, Router};
|
||||
use clap::Parser;
|
||||
|
||||
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
axum::serve(
|
||||
tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(),
|
||||
crate::build_app()
|
||||
).await.unwrap()
|
||||
}
|
||||
|
||||
|
7
progress-pile-simple-ui/Cargo.toml
Normal file
7
progress-pile-simple-ui/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
|||
[package]
|
||||
name = "progress-pile-simple-ui"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
dioxus.workspace = true
|
BIN
progress-pile-simple-ui/assets/favicon.ico
Normal file
BIN
progress-pile-simple-ui/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
20
progress-pile-simple-ui/assets/header.svg
Normal file
20
progress-pile-simple-ui/assets/header.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 23 KiB |
46
progress-pile-simple-ui/assets/main.css
Normal file
46
progress-pile-simple-ui/assets/main.css
Normal file
|
@ -0,0 +1,46 @@
|
|||
/* App-wide styling */
|
||||
body {
|
||||
background-color: #0f1116;
|
||||
color: #ffffff;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
#hero {
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#links {
|
||||
width: 400px;
|
||||
text-align: left;
|
||||
font-size: x-large;
|
||||
color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#links a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
margin-top: 20px;
|
||||
margin: 10px 0px;
|
||||
border: white 1px solid;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#links a:hover {
|
||||
background-color: #1f1f1f;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#header {
|
||||
max-width: 1200px;
|
||||
}
|
||||
|
||||
|
||||
|
33
progress-pile-simple-ui/src/lib.rs
Normal file
33
progress-pile-simple-ui/src/lib.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use dioxus::prelude::*;
|
||||
|
||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||
const MAIN_CSS: Asset = asset!("/assets/main.css");
|
||||
const HEADER_SVG: Asset = asset!("/assets/header.svg");
|
||||
|
||||
#[component]
|
||||
pub fn App() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
Hero {}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Hero() -> Element {
|
||||
rsx! {
|
||||
div {
|
||||
id: "hero",
|
||||
img { src: HEADER_SVG, id: "header" }
|
||||
div { id: "links",
|
||||
a { href: "https://dioxuslabs.com/learn/0.6/", "📚 Learn Dioxus" }
|
||||
a { href: "https://dioxuslabs.com/awesome", "🚀 Awesome Dioxus" }
|
||||
a { href: "https://github.com/dioxus-community/", "📡 Community Libraries" }
|
||||
a { href: "https://github.com/DioxusLabs/sdk", "⚙️ Dioxus Development Kit" }
|
||||
a { href: "https://marketplace.visualstudio.com/items?itemName=DioxusLabs.dioxus", "💫 VSCode Extension" }
|
||||
a { href: "https://discord.gg/XgGxMSkvUM", "👋 Community Discord" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue