Compare commits
10 commits
43f36b81a9
...
034215bf36
Author | SHA1 | Date | |
---|---|---|---|
034215bf36 | |||
f1cc49caf8 | |||
614e1fc865 | |||
b3c0bf53a4 | |||
dead6c133c | |||
de3708a291 | |||
a0356c5ed0 | |||
40450c72fe | |||
381f3ffcfc | |||
5700d19a85 |
72 changed files with 5398 additions and 1258 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.png filter=lfs diff=lfs merge=lfs -text
|
3831
Cargo.lock
generated
3831
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
11
Cargo.toml
11
Cargo.toml
|
@ -8,15 +8,16 @@ chrono-tz = {version = "0.10.3", features = ["serde"]}
|
|||
clap = {version = "4.5", features = ["derive"]}
|
||||
dirs = "6.0.0"
|
||||
dotenv = "0.15.0"
|
||||
progress-pile-cli.path = "progress-pile-cli"
|
||||
progress-pile-client.path = "progress-pile-client"
|
||||
progress-pile-core = { path = "progress-pile-core" }
|
||||
progress-pile-migration = { path = "progress-pile-migration", default-features = false }
|
||||
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"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tempfile = "3.20.0"
|
||||
thiserror = "2.0.12"
|
||||
tokio = "1.44.2"
|
||||
tokio = {version = "1.44.2", features = ["macros", "sync"] }
|
||||
toml = "0.8.22"
|
||||
uuid = { version = "1.16.0", features = [ "serde", "v4" ] }
|
||||
|
||||
|
|
13
progress-pile-app/Cargo.toml
Normal file
13
progress-pile-app/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "progress-pile-app"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
desktop = ["progress-pile-client/desktop"]
|
||||
|
||||
[dependencies]
|
||||
bevy.version = "0.16.0"
|
||||
progress-pile-client.workspace = true
|
BIN
progress-pile-app/assets/textures/Game Icons/exitRight.png
(Stored with Git LFS)
Normal file
BIN
progress-pile-app/assets/textures/Game Icons/exitRight.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
progress-pile-app/assets/textures/Game Icons/right.png
(Stored with Git LFS)
Normal file
BIN
progress-pile-app/assets/textures/Game Icons/right.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
progress-pile-app/assets/textures/Game Icons/wrench.png
(Stored with Git LFS)
Normal file
BIN
progress-pile-app/assets/textures/Game Icons/wrench.png
(Stored with Git LFS)
Normal file
Binary file not shown.
56
progress-pile-app/src/main.rs
Normal file
56
progress-pile-app/src/main.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
mod plugins;
|
||||
pub mod themes;
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
|
||||
enum AppState {
|
||||
#[default]
|
||||
Loading,
|
||||
MainView,
|
||||
Menu,
|
||||
List,
|
||||
Graph
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
|
||||
enum DisplayQuality {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
|
||||
struct Volume(u32);
|
||||
|
||||
#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)]
|
||||
enum ViewerMode {
|
||||
PokerTip2D,
|
||||
PokerTip3D,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.insert_resource(DisplayQuality::Medium)
|
||||
.insert_resource(Volume(7))
|
||||
.insert_resource(ViewerMode::PokerTip2D)
|
||||
.init_state::<AppState>()
|
||||
.add_systems(Startup, setup)
|
||||
.add_plugins((plugins::loading::loading_plugin, plugins::main_menu::menu_plugin, plugins::main_view::main_view_plugin))
|
||||
.run();
|
||||
|
||||
}
|
||||
|
||||
fn setup(mut commands: Commands) {
|
||||
commands.spawn(Camera2d);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Generic system that takes a component as a parameter, and will despawn all entities with that component
|
||||
fn despawn_screen<T: Component>(to_despawn: Query<Entity, With<T>>, mut commands: Commands) {
|
||||
for entity in &to_despawn {
|
||||
commands.entity(entity).despawn();
|
||||
}
|
||||
}
|
59
progress-pile-app/src/plugins/loading.rs
Normal file
59
progress-pile-app/src/plugins/loading.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
use crate::{despawn_screen, AppState};
|
||||
|
||||
// This plugin will display a splash screen with Bevy logo for 1 second before switching to the menu
|
||||
pub fn loading_plugin(app: &mut App) {
|
||||
// As this plugin is managing the splash screen, it will focus on the state `GameState::Splash`
|
||||
app
|
||||
// When entering the state, spawn everything needed for this screen
|
||||
.add_systems(OnEnter(AppState::Loading), loading_setup)
|
||||
// While in this state, run the `countdown` system
|
||||
.add_systems(Update, countdown.run_if(in_state(AppState::Loading)))
|
||||
// When exiting the state, despawn everything that was spawned for this screen
|
||||
.add_systems(OnExit(AppState::Loading), despawn_screen::<OnLoadingScreen>);
|
||||
}
|
||||
|
||||
// Tag component used to tag entities added on the splash screen
|
||||
#[derive(Component)]
|
||||
struct OnLoadingScreen;
|
||||
|
||||
// Newtype to use a `Timer` for this screen as a resource
|
||||
#[derive(Resource, Deref, DerefMut)]
|
||||
struct LoadingTimer(Timer);
|
||||
|
||||
fn loading_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let icon = asset_server.load("textures/Game Icons/wrench.png");
|
||||
// Display the logo
|
||||
commands.spawn((
|
||||
Node {
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
..default()
|
||||
},
|
||||
OnLoadingScreen,
|
||||
children![(
|
||||
ImageNode::new(icon),
|
||||
Node {
|
||||
// This will set the logo to be 200px wide, and auto adjust its height
|
||||
width: Val::Px(200.0),
|
||||
..default()
|
||||
},
|
||||
)],
|
||||
));
|
||||
// Insert the timer as a resource
|
||||
commands.insert_resource(LoadingTimer(Timer::from_seconds(1.0, TimerMode::Once)));
|
||||
}
|
||||
|
||||
// Tick the timer, and change state when finished
|
||||
fn countdown(
|
||||
mut game_state: ResMut<NextState<AppState>>,
|
||||
time: Res<Time>,
|
||||
mut timer: ResMut<LoadingTimer>,
|
||||
) {
|
||||
if timer.tick(time.delta()).finished() {
|
||||
game_state.set(AppState::MainView);
|
||||
}
|
||||
}
|
475
progress-pile-app/src/plugins/main_menu.rs
Normal file
475
progress-pile-app/src/plugins/main_menu.rs
Normal file
|
@ -0,0 +1,475 @@
|
|||
use bevy::{
|
||||
app::AppExit,
|
||||
color::palettes::css::CRIMSON,
|
||||
ecs::spawn::{SpawnIter, SpawnWith},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
despawn_screen,
|
||||
themes::dark::{self, NORMAL_BUTTON},
|
||||
DisplayQuality,
|
||||
AppState,
|
||||
Volume,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash, States)]
|
||||
enum MenuState {
|
||||
Main,
|
||||
Settings,
|
||||
SettingsDisplay,
|
||||
SettingsVolume,
|
||||
#[default]
|
||||
Disabled,
|
||||
}
|
||||
|
||||
pub fn menu_plugin(app: &mut App) {
|
||||
app
|
||||
.init_state::<MenuState>()
|
||||
.add_systems(OnEnter(AppState::Menu), menu_setup)
|
||||
.add_systems(OnEnter(MenuState::Main), main_menu_setup)
|
||||
.add_systems(OnExit(MenuState::Main), crate::despawn_screen::<OnMainMenuScreen>)
|
||||
.add_systems(OnEnter(MenuState::Settings), settings_menu_setup)
|
||||
.add_systems(
|
||||
OnExit(MenuState::Settings),
|
||||
crate::despawn_screen::<OnSettingsMenuScreen>
|
||||
)
|
||||
.add_systems(OnEnter(MenuState::SettingsDisplay), display_settings_menu_setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
(setting_button::<DisplayQuality>.run_if(in_state(MenuState::SettingsDisplay)))
|
||||
)
|
||||
.add_systems(
|
||||
OnExit(MenuState::SettingsDisplay),
|
||||
despawn_screen::<OnDisplaySettingsMenuScreen>
|
||||
)
|
||||
.add_systems(OnEnter(MenuState::SettingsVolume), sound_settings_menu_setup)
|
||||
.add_systems(
|
||||
Update,
|
||||
(setting_button::<Volume>.run_if(in_state(MenuState::SettingsVolume)))
|
||||
)
|
||||
.add_systems(
|
||||
OnExit(MenuState::SettingsVolume),
|
||||
despawn_screen::<OnSoundSettingsMenuScreen>
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
(menu_action, button_system).run_if(in_state(AppState::Menu))
|
||||
)
|
||||
|
||||
;
|
||||
}
|
||||
#[derive(Component)]
|
||||
struct OnMainMenuScreen;
|
||||
|
||||
#[derive(Component)]
|
||||
struct OnSettingsMenuScreen;
|
||||
|
||||
#[derive(Component)]
|
||||
struct OnDisplaySettingsMenuScreen;
|
||||
|
||||
#[derive(Component)]
|
||||
struct OnSoundSettingsMenuScreen;
|
||||
|
||||
#[derive(Component)]
|
||||
struct SelectedOption;
|
||||
|
||||
|
||||
#[derive(Component)]
|
||||
enum MenuButtonAction {
|
||||
BackToMainView,
|
||||
BackToMainMenu,
|
||||
BackToSettings,
|
||||
Settings,
|
||||
SettingsDisplay,
|
||||
SettingsSound,
|
||||
Quit,
|
||||
}
|
||||
|
||||
fn button_system(
|
||||
mut interaction_query: Query<(&Interaction, &mut BackgroundColor, Option<&SelectedOption>),(Changed<Interaction>, With<Button>)>
|
||||
) {
|
||||
for (interaction, mut background_color, selected) in &mut interaction_query {
|
||||
*background_color = match (*interaction, selected) {
|
||||
(Interaction::Pressed, _) | (_, Some(_)) => dark::PRESSED_BUTTON.into(),
|
||||
(Interaction::Hovered, None) => dark::HOVERED_BUTTON.into(),
|
||||
(Interaction::None, None) => dark::NORMAL_BUTTON.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
fn setting_button<T: Resource + Component + PartialEq + Copy> (
|
||||
interaction_query: Query<(&Interaction, &T, Entity), (Changed<Interaction>, With<Button>)>,
|
||||
selected_query: Single<(Entity, &mut BackgroundColor), With<SelectedOption>>,
|
||||
mut commands: Commands,
|
||||
mut setting: ResMut<T>
|
||||
) {
|
||||
let (previous_button, mut previous_button_color) = selected_query.into_inner();
|
||||
for (interaction, button_setting, entity) in &interaction_query {
|
||||
if *interaction == Interaction::Pressed && *setting != *button_setting {
|
||||
*previous_button_color = NORMAL_BUTTON.into();
|
||||
commands.entity(previous_button).remove::<SelectedOption>();
|
||||
commands.entity(entity).insert(SelectedOption);
|
||||
*setting = *button_setting
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn menu_setup(mut menu_state: ResMut<NextState<MenuState>>) {
|
||||
menu_state.set(MenuState::Main);
|
||||
}
|
||||
|
||||
fn main_menu_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let button_node = Node {
|
||||
width: Val::Px(300.0),
|
||||
height: Val::Px(65.0),
|
||||
margin: UiRect::all(Val::Px(20.0)),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
};
|
||||
let button_icon_node = Node {
|
||||
width: Val::Px(30.0),
|
||||
position_type: PositionType::Absolute,
|
||||
left: Val::Px(10.0),
|
||||
..default()
|
||||
};
|
||||
let button_text_font = TextFont {
|
||||
font_size: 33.0,
|
||||
..default()
|
||||
};
|
||||
let right_icon = asset_server.load("textures/Game Icons/right.png");
|
||||
let wrench_icon = asset_server.load("textures/Game Icons/wrench.png");
|
||||
let exit_icon = asset_server.load("textures/Game Icons/exitRight.png");
|
||||
|
||||
|
||||
commands.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
OnMainMenuScreen,
|
||||
children![(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(dark::BACKGROUND_COLOR.into()),
|
||||
children![
|
||||
(
|
||||
Text::new("Progress Pile"),
|
||||
TextFont {
|
||||
font_size: 67.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(dark::TEXT_COLOR),
|
||||
Node {
|
||||
margin: UiRect::all(Val::Px(50.0)),
|
||||
..default()
|
||||
},
|
||||
),
|
||||
(
|
||||
Button,
|
||||
button_node.clone(),
|
||||
BackgroundColor(dark::NORMAL_BUTTON),
|
||||
MenuButtonAction::BackToMainView,
|
||||
children![
|
||||
(ImageNode::new(right_icon), button_icon_node.clone()),
|
||||
(
|
||||
Text::new("Back to main window"),
|
||||
button_text_font.clone(),
|
||||
TextColor(dark::TEXT_COLOR),
|
||||
)
|
||||
]
|
||||
),
|
||||
(
|
||||
Button,
|
||||
button_node.clone(),
|
||||
BackgroundColor(dark::NORMAL_BUTTON),
|
||||
MenuButtonAction::Settings,
|
||||
children![
|
||||
(ImageNode::new(wrench_icon), button_icon_node.clone()),
|
||||
(
|
||||
Text::new("Settings"),
|
||||
button_text_font.clone(),
|
||||
TextColor(dark::TEXT_COLOR),
|
||||
)
|
||||
]
|
||||
),
|
||||
(
|
||||
Button,
|
||||
button_node.clone(),
|
||||
BackgroundColor(dark::NORMAL_BUTTON),
|
||||
MenuButtonAction::Quit,
|
||||
children![
|
||||
(ImageNode::new(exit_icon), button_icon_node.clone()),
|
||||
(
|
||||
Text::new("Quit"),
|
||||
button_text_font.clone(),
|
||||
TextColor(dark::TEXT_COLOR),
|
||||
)
|
||||
]
|
||||
),
|
||||
]
|
||||
)]
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
fn settings_menu_setup(mut commands: Commands) {
|
||||
let button_node = Node {
|
||||
width: Val::Px(200.0),
|
||||
height: Val::Px(65.0),
|
||||
margin: UiRect::all(Val::Px(20.0)),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
};
|
||||
|
||||
let button_text_style = (
|
||||
TextFont {
|
||||
font_size: 33.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(dark::TEXT_COLOR),
|
||||
);
|
||||
|
||||
commands.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
OnSettingsMenuScreen,
|
||||
children![(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(CRIMSON.into()),
|
||||
Children::spawn(SpawnIter(
|
||||
[
|
||||
(MenuButtonAction::SettingsDisplay, "Display"),
|
||||
(MenuButtonAction::SettingsSound, "Sound"),
|
||||
(MenuButtonAction::BackToMainMenu, "Back"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(move |(action, text)| {
|
||||
(
|
||||
Button,
|
||||
button_node.clone(),
|
||||
BackgroundColor(NORMAL_BUTTON),
|
||||
action,
|
||||
children![(Text::new(text), button_text_style.clone())],
|
||||
)
|
||||
})
|
||||
))
|
||||
)],
|
||||
));
|
||||
}
|
||||
|
||||
fn display_settings_menu_setup(mut commands: Commands, display_quality: Res<DisplayQuality>) {
|
||||
fn button_node() -> Node {
|
||||
Node {
|
||||
width: Val::Px(200.0),
|
||||
height: Val::Px(65.0),
|
||||
margin: UiRect::all(Val::Px(20.0)),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
fn button_text_style() -> impl Bundle {
|
||||
(
|
||||
TextFont {
|
||||
font_size: 33.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(dark::TEXT_COLOR),
|
||||
)
|
||||
}
|
||||
|
||||
let display_quality = *display_quality;
|
||||
commands.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
OnDisplaySettingsMenuScreen,
|
||||
children![(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(CRIMSON.into()),
|
||||
children![
|
||||
// Create a new `Node`, this time not setting its `flex_direction`. It will
|
||||
// use the default value, `FlexDirection::Row`, from left to right.
|
||||
(
|
||||
Node {
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(CRIMSON.into()),
|
||||
Children::spawn((
|
||||
// Display a label for the current setting
|
||||
Spawn((Text::new("Display Quality"), button_text_style())),
|
||||
SpawnWith(move |parent: &mut ChildSpawner| {
|
||||
for quality_setting in [
|
||||
DisplayQuality::Low,
|
||||
DisplayQuality::Medium,
|
||||
DisplayQuality::High,
|
||||
] {
|
||||
let mut entity = parent.spawn((
|
||||
Button,
|
||||
Node {
|
||||
width: Val::Px(150.0),
|
||||
height: Val::Px(65.0),
|
||||
..button_node()
|
||||
},
|
||||
BackgroundColor(NORMAL_BUTTON),
|
||||
quality_setting,
|
||||
children![(
|
||||
Text::new(format!("{quality_setting:?}")),
|
||||
button_text_style(),
|
||||
)],
|
||||
));
|
||||
if display_quality == quality_setting {
|
||||
entity.insert(SelectedOption);
|
||||
}
|
||||
}
|
||||
})
|
||||
))
|
||||
),
|
||||
// Display the back button to return to the settings screen
|
||||
(
|
||||
Button,
|
||||
button_node(),
|
||||
BackgroundColor(NORMAL_BUTTON),
|
||||
MenuButtonAction::BackToSettings,
|
||||
children![(Text::new("Back"), button_text_style())]
|
||||
)
|
||||
]
|
||||
)],
|
||||
));
|
||||
}
|
||||
|
||||
fn sound_settings_menu_setup(mut commands: Commands, volume: Res<Volume>) {
|
||||
let button_node = Node {
|
||||
width: Val::Px(200.0),
|
||||
height: Val::Px(65.0),
|
||||
margin: UiRect::all(Val::Px(20.0)),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
};
|
||||
let button_text_style = (
|
||||
TextFont {
|
||||
font_size: 33.0,
|
||||
..default()
|
||||
},
|
||||
TextColor(dark::TEXT_COLOR),
|
||||
);
|
||||
|
||||
let volume = *volume;
|
||||
let button_node_clone = button_node.clone();
|
||||
commands.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::Center,
|
||||
..default()
|
||||
},
|
||||
OnSoundSettingsMenuScreen,
|
||||
children![(
|
||||
Node {
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(CRIMSON.into()),
|
||||
children![
|
||||
(
|
||||
Node {
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
BackgroundColor(CRIMSON.into()),
|
||||
Children::spawn((
|
||||
Spawn((Text::new("Volume"), button_text_style.clone())),
|
||||
SpawnWith(move |parent: &mut ChildSpawner| {
|
||||
for volume_setting in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] {
|
||||
let mut entity = parent.spawn((
|
||||
Button,
|
||||
Node {
|
||||
width: Val::Px(30.0),
|
||||
height: Val::Px(65.0),
|
||||
..button_node_clone.clone()
|
||||
},
|
||||
BackgroundColor(NORMAL_BUTTON),
|
||||
Volume(volume_setting),
|
||||
));
|
||||
if volume == Volume(volume_setting) {
|
||||
entity.insert(SelectedOption);
|
||||
}
|
||||
}
|
||||
})
|
||||
))
|
||||
),
|
||||
(
|
||||
Button,
|
||||
button_node,
|
||||
BackgroundColor(NORMAL_BUTTON),
|
||||
MenuButtonAction::BackToSettings,
|
||||
children![(Text::new("Back"), button_text_style)]
|
||||
)
|
||||
]
|
||||
)],
|
||||
));
|
||||
}
|
||||
|
||||
fn menu_action(
|
||||
interaction_query: Query<
|
||||
(&Interaction, &MenuButtonAction),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
mut app_exit_events: EventWriter<AppExit>,
|
||||
mut menu_state: ResMut<NextState<MenuState>>,
|
||||
mut game_state: ResMut<NextState<AppState>>,
|
||||
) {
|
||||
for (interaction, menu_button_action) in &interaction_query {
|
||||
if *interaction == Interaction::Pressed {
|
||||
match menu_button_action {
|
||||
MenuButtonAction::Quit => {
|
||||
app_exit_events.write(AppExit::Success);
|
||||
}
|
||||
MenuButtonAction::BackToMainView => {
|
||||
game_state.set(AppState::MainView);
|
||||
menu_state.set(MenuState::Disabled);
|
||||
}
|
||||
MenuButtonAction::Settings => menu_state.set(MenuState::Settings),
|
||||
MenuButtonAction::SettingsDisplay => {
|
||||
menu_state.set(MenuState::SettingsDisplay);
|
||||
}
|
||||
MenuButtonAction::SettingsSound => {
|
||||
menu_state.set(MenuState::SettingsVolume);
|
||||
}
|
||||
MenuButtonAction::BackToMainMenu => menu_state.set(MenuState::Main),
|
||||
MenuButtonAction::BackToSettings => {
|
||||
menu_state.set(MenuState::Settings);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
113
progress-pile-app/src/plugins/main_view.rs
Normal file
113
progress-pile-app/src/plugins/main_view.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use bevy::{
|
||||
app::AppExit,
|
||||
color::palettes::css::CRIMSON,
|
||||
ecs::spawn::{SpawnIter, SpawnWith},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
despawn_screen,
|
||||
themes::dark::{self, NORMAL_BUTTON},
|
||||
DisplayQuality,
|
||||
AppState,
|
||||
Volume,
|
||||
};
|
||||
|
||||
pub fn main_view_plugin(app: &mut App) {
|
||||
app
|
||||
.add_systems(OnEnter(AppState::MainView), main_view_setup)
|
||||
.add_systems(OnExit(AppState::MainView), despawn_screen::<OnMainViewScreen>)
|
||||
.add_systems(
|
||||
Update,
|
||||
(menu_action, button_system).run_if(in_state(AppState::MainView))
|
||||
)
|
||||
|
||||
;
|
||||
}
|
||||
#[derive(Component)]
|
||||
struct OnMainViewScreen;
|
||||
|
||||
#[derive(Component)]
|
||||
struct SelectedOption;
|
||||
|
||||
|
||||
#[derive(Component)]
|
||||
enum MainViewButtonAction {
|
||||
OpenMainMenu,
|
||||
}
|
||||
|
||||
fn button_system(
|
||||
mut interaction_query: Query<(&Interaction, &mut BackgroundColor, Option<&SelectedOption>),(Changed<Interaction>, With<Button>)>
|
||||
) {
|
||||
for (interaction, mut background_color, selected) in &mut interaction_query {
|
||||
*background_color = match (*interaction, selected) {
|
||||
(Interaction::Pressed, _) | (_, Some(_)) => dark::PRESSED_BUTTON.into(),
|
||||
(Interaction::Hovered, None) => dark::HOVERED_BUTTON.into(),
|
||||
(Interaction::None, None) => dark::NORMAL_BUTTON.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main_view_setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
let header_node = Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Px(20.0),
|
||||
justify_content: JustifyContent::SpaceBetween,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
};
|
||||
let body_node = Node{
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Auto,
|
||||
..default()
|
||||
};
|
||||
let wrench_icon = asset_server.load("textures/Game Icons/wrench.png");
|
||||
|
||||
commands.spawn((
|
||||
Node {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
OnMainViewScreen,
|
||||
children![(
|
||||
header_node,
|
||||
children![(
|
||||
Button,
|
||||
Node{
|
||||
width: Val::Px(30.0),
|
||||
..default()
|
||||
},
|
||||
MainViewButtonAction::OpenMainMenu,
|
||||
children![(
|
||||
ImageNode::new(wrench_icon),
|
||||
Node::default()
|
||||
)
|
||||
]
|
||||
|
||||
)]
|
||||
)]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn menu_action(
|
||||
interaction_query: Query<
|
||||
(&Interaction, &MainViewButtonAction),
|
||||
(Changed<Interaction>, With<Button>),
|
||||
>,
|
||||
mut app_exit_events: EventWriter<AppExit>,
|
||||
mut game_state: ResMut<NextState<AppState>>,
|
||||
) {
|
||||
for (interaction, menu_button_action) in &interaction_query {
|
||||
if *interaction == Interaction::Pressed {
|
||||
match menu_button_action {
|
||||
MainViewButtonAction::OpenMainMenu => {
|
||||
game_state.set(AppState::Menu);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
progress-pile-app/src/plugins/mod.rs
Normal file
3
progress-pile-app/src/plugins/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
pub mod loading;
|
||||
pub mod main_menu;
|
||||
pub mod main_view;
|
7
progress-pile-app/src/themes/dark.rs
Normal file
7
progress-pile-app/src/themes/dark.rs
Normal file
|
@ -0,0 +1,7 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub const TEXT_COLOR: Color = Color::srgb(0.9, 0.9, 0.9);
|
||||
pub const BACKGROUND_COLOR: Color = Color::srgb(0.1, 0.1, 0.1);
|
||||
pub const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
|
||||
pub const HOVERED_BUTTON: Color= Color::srgb(0.25, 0.25, 0.25);
|
||||
pub const PRESSED_BUTTON: Color = Color::srgb(0.5,0.5, 0.5);
|
4
progress-pile-app/src/themes/light.rs
Normal file
4
progress-pile-app/src/themes/light.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
const TEXT_COLOR: Color = Color::srgb(0.1, 0.1, 0.1);
|
||||
const BACKGROUND_COLOR: Color = Color::srgb(0.9, 0.9, 0.9);
|
12
progress-pile-app/src/themes/mod.rs
Normal file
12
progress-pile-app/src/themes/mod.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use bevy::prelude::*;
|
||||
|
||||
pub mod dark;
|
||||
pub mod light;
|
||||
|
||||
#[derive(Resource, Debug, Component, Default, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum UiTheme {
|
||||
Light,
|
||||
Dark,
|
||||
#[default]
|
||||
Default,
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
[package]
|
||||
name = "progress-pile-cli"
|
||||
version.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
progress-pile-client = {workspace = true}
|
||||
chrono = {workspace = true}
|
||||
chrono-tz.workspace = true
|
||||
clap = {workspace = true, features = ["derive"]}
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
toml.workspace = true
|
|
@ -1,73 +0,0 @@
|
|||
use clap::{Args, Subcommand};
|
||||
use chrono_tz::Tz;
|
||||
use crate::error::Error;
|
||||
use progress_pile_client::auth::try_login;
|
||||
use progress_pile_client::config::{
|
||||
ClientConfig, ClientRemoteStorageConfig, ClientStorageConfig, Config, GlobalConfig, PartialGlobalConfig
|
||||
};
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct InitArgs {
|
||||
#[command(subcommand)]
|
||||
pub command: InitCommand,
|
||||
}
|
||||
|
||||
impl InitArgs {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
match self.command {
|
||||
InitCommand::Local(x) => x.run().await,
|
||||
InitCommand::Remote(x) => x.run().await,
|
||||
}
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
|
||||
enum InitCommand {
|
||||
Local(InitLocalArgs),
|
||||
Remote(InitRemoteArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct InitLocalArgs {
|
||||
#[arg(short, long)]
|
||||
pub time_zone: Option<Tz>,
|
||||
#[arg(short, long)]
|
||||
pub force: bool
|
||||
}
|
||||
|
||||
impl InitLocalArgs {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct InitRemoteArgs {
|
||||
pub endpoint: String,
|
||||
#[arg(short, long)]
|
||||
pub user_name: String,
|
||||
#[arg(short, long)]
|
||||
pub password: String,
|
||||
#[arg(short, long)]
|
||||
pub time_zone: Option<Tz>,
|
||||
#[arg(short, long)]
|
||||
pub force: bool,
|
||||
}
|
||||
|
||||
impl InitRemoteArgs {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
let token: String = try_login(&self.user_name, &self.password, &self.endpoint)?;
|
||||
let config: Config = Config{
|
||||
global: GlobalConfig {
|
||||
time_zone: self.time_zone.clone()
|
||||
},
|
||||
client: ClientConfig {
|
||||
storage: ClientStorageConfig::Remote(ClientRemoteStorageConfig {
|
||||
endpoint: self.endpoint,
|
||||
access_key: token,
|
||||
}),
|
||||
}
|
||||
};
|
||||
config.write_to_default_toml().await
|
||||
}
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
//mod label;
|
||||
mod init;
|
||||
mod record;
|
||||
mod user;
|
||||
|
||||
pub use progress_pile_client::error;
|
||||
|
||||
|
||||
//use label::LabelArgs;
|
||||
use record::{RecordArgs,RecordAddArgs};
|
||||
|
||||
use error::Error;
|
||||
use init::InitArgs;
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
#[command(propagate_version=true)]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum Command {
|
||||
Init(InitArgs),
|
||||
Record(RecordArgs),
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
let cli = Cli::parse();
|
||||
match cli.command {
|
||||
//Some(Commands::Add(x)) => x.run(),
|
||||
Command::Init(x) => x.run().await,
|
||||
//Some(Commands::Label(x)) => x.run(),
|
||||
Command::Record(x) => x.run(),
|
||||
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
use chrono::prelude::*;
|
||||
use clap::{Args, Subcommand};
|
||||
use crate::error::Error;
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct AchievementArgValues {
|
||||
pub label: String,
|
||||
pub value: i8,
|
||||
}
|
||||
|
||||
impl FromStr for AchievementArgValues {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Error> {
|
||||
let strvec: Vec<&str> = s.split(':').collect();
|
||||
Ok(AchievementArgValues{
|
||||
label: strvec.get(0).unwrap().to_string(),
|
||||
value: strvec.get(1).unwrap().parse()?
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct RecordAddArgs {
|
||||
#[arg(short, long)]
|
||||
pub comment: Option<String>,
|
||||
#[arg(short, long)]
|
||||
pub time: Option<DateTime<Utc>>,
|
||||
//pub achievements: Vec<String>,
|
||||
}
|
||||
|
||||
impl RecordAddArgs {
|
||||
pub fn run(self) -> Result<(), Error> {
|
||||
unimplemented!();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
pub enum RecordCommand {
|
||||
Add(RecordAddArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct RecordArgs {
|
||||
#[command(subcommand)]
|
||||
command: RecordCommand,
|
||||
}
|
||||
|
||||
impl RecordArgs {
|
||||
pub fn run(self) -> Result<(), Error> {
|
||||
match self.command {
|
||||
RecordCommand::Add(x) => x.run(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -4,23 +4,23 @@ version = "0.1.0"
|
|||
edition = "2024"
|
||||
|
||||
[features]
|
||||
default = ["clap"]
|
||||
clap = ["dep:clap"]
|
||||
default = ["desktop"]
|
||||
desktop = ["dep:clap", "progress-pile-core/desktop", "dep:progress-pile-migration-client", "dep:sea-orm", "tokio/io-util", "tokio/fs"]
|
||||
web = ["uuid/js"]
|
||||
|
||||
[dependencies]
|
||||
async-graphql.workspace = true
|
||||
chrono.workspace = true
|
||||
chrono-tz.workspace = true
|
||||
clap = { workspace = true, optional = true }
|
||||
dirs.workspace = true
|
||||
progress-pile-core.workspace = true
|
||||
progress-pile-migration = { workspace = true, features = ["client"]}
|
||||
sea-orm.workspace = true
|
||||
progress-pile-migration-client = { workspace = true, optional = true}
|
||||
sea-orm = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
toml.workspace = true
|
||||
uuid.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
[target.'cfg(not(target_family="wasm"))'.dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
|
|
27
progress-pile-client/src/cli/category.rs
Normal file
27
progress-pile-client/src/cli/category.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use clap::{Args, Subcommand};
|
||||
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct CategoryArgs {
|
||||
#[command(subcommand)]
|
||||
pub command: CategoryCommand,
|
||||
}
|
||||
|
||||
impl CategoryArgs {
|
||||
pub async fn run(self){
|
||||
self.command.run().await
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
|
||||
enum CategoryCommand {
|
||||
List,
|
||||
Add,
|
||||
Modify,
|
||||
}
|
||||
|
||||
impl CategoryCommand {
|
||||
async fn run(self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
26
progress-pile-client/src/cli/config.rs
Normal file
26
progress-pile-client/src/cli/config.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use clap::{Args, Subcommand};
|
||||
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct ConfigArgs {
|
||||
#[command(subcommand)]
|
||||
pub command: ConfigCommand,
|
||||
}
|
||||
|
||||
impl ConfigArgs {
|
||||
pub async fn run(self){
|
||||
self.command.run().await
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
|
||||
enum ConfigCommand {
|
||||
Show,
|
||||
Modify,
|
||||
}
|
||||
|
||||
impl ConfigCommand {
|
||||
async fn run(self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
16
progress-pile-client/src/cli/default.rs
Normal file
16
progress-pile-client/src/cli/default.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
use clap::{Args, Subcommand};
|
||||
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct DefaultArgs {
|
||||
#[arg(short, long)]
|
||||
pub note: String,
|
||||
pub category: String,
|
||||
pub quantity: i32,
|
||||
}
|
||||
|
||||
impl DefaultArgs {
|
||||
pub async fn run (self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
18
progress-pile-client/src/cli/login.rs
Normal file
18
progress-pile-client/src/cli/login.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
use clap::{Args, Subcommand};
|
||||
use chrono_tz::Tz;
|
||||
use crate::error::Error;
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
pub struct LoginArgs {
|
||||
pub endpoint: String,
|
||||
#[arg(short, long)]
|
||||
pub user_name: String,
|
||||
#[arg(short, long)]
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
impl LoginArgs {
|
||||
pub async fn run(self){
|
||||
todo!()
|
||||
}
|
||||
}
|
50
progress-pile-client/src/cli/mod.rs
Normal file
50
progress-pile-client/src/cli/mod.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
//mod label;
|
||||
mod category;
|
||||
mod config;
|
||||
mod default;
|
||||
mod login;
|
||||
mod user;
|
||||
|
||||
use category::CategoryArgs;
|
||||
use config::ConfigArgs;
|
||||
use default::DefaultArgs;
|
||||
use login::LoginArgs;
|
||||
use user::UserArgs;
|
||||
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||
|
||||
use std::ffi::OsString;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
#[command(propagate_version=true)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Option<Command>,
|
||||
#[command(flatten)]
|
||||
args: Option<DefaultArgs>
|
||||
}
|
||||
|
||||
impl Cli {
|
||||
pub async fn run(self) {
|
||||
if let Some(x) = self.command {
|
||||
x.run().await
|
||||
} else if let Some(x) = self.args {
|
||||
x.run().await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Subcommand)]
|
||||
enum Command {
|
||||
Category(CategoryArgs),
|
||||
Config(ConfigArgs),
|
||||
Login(LoginArgs),
|
||||
User(UserArgs),
|
||||
}
|
||||
|
||||
impl Command {
|
||||
pub async fn run(self) {
|
||||
todo!()
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use clap::{Args, Subcommand};
|
||||
use crate::error::Error;
|
||||
use progress_pile_client::config::Config;
|
||||
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
|
@ -11,7 +10,6 @@ pub struct UserArgs {
|
|||
|
||||
impl UserArgs {
|
||||
pub async fn run(self) -> Result<(), Error> {
|
||||
Config::read_from_default_toml().await?.set_global();
|
||||
match self.command {
|
||||
UserCommand::Add(x) => x.add().await,
|
||||
UserCommand::List => todo!(),
|
|
@ -1,9 +1,8 @@
|
|||
mod storage;
|
||||
mod remote;
|
||||
|
||||
use std::{fmt::{Display, Formatter}, path::{Path, PathBuf}, str::FromStr};
|
||||
|
||||
pub use progress_pile_core::config::*;
|
||||
pub use storage::*;
|
||||
pub use remote::*;
|
||||
|
||||
use crate::error::Error;
|
||||
use serde::{
|
||||
|
@ -11,26 +10,24 @@ use serde::{
|
|||
Serialize
|
||||
};
|
||||
|
||||
use tokio::{fs::{self, File}, io::AsyncWriteExt, sync::OnceCell};
|
||||
#[cfg(feature="desktop")]
|
||||
use tokio::{fs::{self, File}, io::AsyncWriteExt};
|
||||
|
||||
pub static DEFAULT_CONFIG_FILE_NAME: &str = "dpts_client.toml";
|
||||
pub static DEFAULT_CONFIG_DIR_NAME: &str = "dpts";
|
||||
|
||||
pub static CLIENT_CONFIG: OnceCell<ClientConfig> = OnceCell::const_new();
|
||||
|
||||
pub fn get_default_config_file_path() -> Result<PathBuf, Error> {
|
||||
let config_dir = dirs::config_dir().ok_or(Error::DefaultConfigDir)?;
|
||||
Ok(config_dir.join(DEFAULT_CONFIG_DIR_NAME).join(DEFAULT_CONFIG_FILE_NAME))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
|
||||
pub struct Config {
|
||||
pub client: ClientConfig,
|
||||
pub global: GlobalConfig,
|
||||
pub struct ClientConfig {
|
||||
pub remote: ClientRemoteConfig,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[cfg(feature="desktop")]
|
||||
impl ClientConfig {
|
||||
pub async fn read_from_default_toml() -> Result<Self, Error> {
|
||||
Ok(Self::read_from_toml(&get_default_config_file_path()?).await?)
|
||||
}
|
||||
|
@ -53,90 +50,9 @@ impl Config {
|
|||
pub fn to_toml(&self) -> Result<String, Error> {
|
||||
Ok(toml::to_string(self)?)
|
||||
}
|
||||
pub fn set_global(self) -> Result<(), Error> {
|
||||
CLIENT_CONFIG.set(self.client)?;
|
||||
GLOBAL_CONFIG.set(self.global)?;
|
||||
Ok(())
|
||||
}
|
||||
pub fn from_global() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
client: CLIENT_CONFIG.get().ok_or(Error::UninitializedOnceCell("CLIENT_CONFIG".to_string()))?.clone(),
|
||||
global: GLOBAL_CONFIG.get().ok_or(Error::UninitializedOnceCell("GLOBAL_CONFIG".to_string()))?.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct ClientConfig {
|
||||
pub storage: ClientStorageConfig,
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use chrono_tz::UTC;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
const LOCAL_STORAGE_CONFIG_TOML: &str = r#"[client]
|
||||
storage = "local"
|
||||
|
||||
[global]
|
||||
"#;
|
||||
static LOCAL_STORAGE_CONFIG_STRUCT: OnceCell<Config> = OnceCell::const_new();
|
||||
|
||||
async fn get_local_storage_client_config_struct() -> &'static Config {
|
||||
LOCAL_STORAGE_CONFIG_STRUCT.get_or_init(|| async {
|
||||
Config{
|
||||
client: ClientConfig{
|
||||
storage: ClientStorageConfig::Local,
|
||||
},
|
||||
global: GlobalConfig { time_zone: None },
|
||||
}
|
||||
}).await
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn deserialize_local_storage_client_config() {
|
||||
let config: Config = Config::from_toml(LOCAL_STORAGE_CONFIG_TOML).unwrap();
|
||||
assert_eq!(&config, get_local_storage_client_config_struct().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serialize_local_storage_client_config() {
|
||||
assert_eq!(LOCAL_STORAGE_CONFIG_TOML, get_local_storage_client_config_struct().await.to_toml().unwrap());
|
||||
}
|
||||
|
||||
const REMOTE_STORAGE_CONFIG_TOML: &str = r#"[client.storage.remote]
|
||||
endpoint = "https://example.com"
|
||||
access_key = "test"
|
||||
|
||||
[global]
|
||||
time_zone = "UTC"
|
||||
"#;
|
||||
static REMOTE_STORAGE_CONFIG_STRUCT: OnceCell<Config> = OnceCell::const_new();
|
||||
|
||||
async fn get_remote_storage_client_config_struct() -> &'static Config {
|
||||
REMOTE_STORAGE_CONFIG_STRUCT.get_or_init(|| async {
|
||||
Config{
|
||||
client: ClientConfig {
|
||||
storage: ClientStorageConfig::Remote(ClientRemoteStorageConfig {
|
||||
endpoint: "https://example.com".to_string(),
|
||||
access_key: "test".to_string(),
|
||||
})
|
||||
},
|
||||
global: GlobalConfig { time_zone: Some(UTC) }
|
||||
}
|
||||
}).await
|
||||
}
|
||||
#[tokio::test]
|
||||
async fn deserialize_remote_storage_client_config() { let config: Config = Config::from_toml(REMOTE_STORAGE_CONFIG_TOML).unwrap();
|
||||
assert_eq!(&config, get_remote_storage_client_config_struct().await);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn serialize_remote_storage_client_config() {
|
||||
assert_eq!(REMOTE_STORAGE_CONFIG_TOML, get_remote_storage_client_config_struct().await.to_toml().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -10,8 +10,8 @@ use std::{
|
|||
};
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
|
||||
pub struct ClientRemoteStorageConfig {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ClientRemoteConfig {
|
||||
pub endpoint: String,
|
||||
pub access_key: String,
|
||||
}
|
|
@ -24,7 +24,6 @@ mod tests {
|
|||
use chrono::Local;
|
||||
use progress_pile_core::global::GlobalDatabase;
|
||||
use sea_orm::{entity::*, ConnectOptions, Database, DatabaseConnection};
|
||||
use progress_pile_migration::{ClientMigrator, MigratorTrait};
|
||||
use uuid::Uuid;
|
||||
use crate::error::Error;
|
||||
use crate::global::GLOBAL;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use core::time;
|
||||
|
||||
use async_graphql::*;
|
||||
use chrono::Local;
|
||||
use sea_orm::entity::{
|
||||
*,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use async_graphql::*;
|
||||
use chrono::Local;
|
||||
use sea_orm::entity::{
|
||||
*,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use progress_pile_migration::{ClientMigrator, MigratorTrait};
|
||||
use progress_pile_migration_client::{Migrator, MigratorTrait};
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
use crate::error::Error;
|
||||
use tokio::sync::OnceCell;
|
||||
|
@ -19,7 +19,7 @@ impl GlobalDatabase for Global {
|
|||
{
|
||||
Ok(self.database.get_or_try_init(|| async {
|
||||
let db = Database::connect(options).await?;
|
||||
ClientMigrator::up(&db, None).await?;
|
||||
Migrator::up(&db, None).await?;
|
||||
Ok::<DatabaseConnection, Error>(db)
|
||||
}).await?)
|
||||
}
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
use crate::config::ClientConfig;
|
||||
#[cfg(feature="desktop")]
|
||||
use sea_orm::DatabaseConnection;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
|
||||
#[cfg(feature="desktop")]
|
||||
mod database;
|
||||
|
||||
pub static GLOBAL: Global = Global{
|
||||
config: OnceCell::const_new(),
|
||||
#[cfg(feature="desktop")]
|
||||
database: OnceCell::const_new(),
|
||||
};
|
||||
pub struct Global {
|
||||
config: OnceCell<ClientConfig>,
|
||||
#[cfg(feature="desktop")]
|
||||
database: OnceCell<DatabaseConnection>,
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
pub mod auth;
|
||||
#[cfg(feature="desktop")]
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
#[cfg(feature="desktop")]
|
||||
pub mod entity;
|
||||
pub mod global;
|
||||
|
||||
pub use progress_pile_core::{
|
||||
error,
|
||||
};
|
||||
pub use progress_pile_core::error;
|
||||
|
||||
|
||||
|
||||
|
|
8
progress-pile-client/src/main.rs
Normal file
8
progress-pile-client/src/main.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use clap::Parser;
|
||||
use progress_pile_client::cli::Cli;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let cli = Cli::parse();
|
||||
cli.run().await
|
||||
}
|
|
@ -6,14 +6,12 @@ license.workspace = true
|
|||
repository.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["clap",]
|
||||
clap = ["dep:clap"]
|
||||
postgres = ["sea-orm/sqlx-postgres", "sea-orm/sqlx-postgres"]
|
||||
server = ["clap", "postgres"]
|
||||
default = ["desktop"]
|
||||
desktop = ["dep:clap", "dep:sea-orm"]
|
||||
wasm = []
|
||||
|
||||
[dependencies]
|
||||
async-graphql.workspace = true
|
||||
axum = "0.8"
|
||||
chrono = {workspace = true}
|
||||
chrono-tz.workspace = true
|
||||
clap = { workspace = true, optional = true }
|
||||
|
@ -22,9 +20,9 @@ cynic = "3.10.0"
|
|||
dotenv = {workspace = true}
|
||||
iana-time-zone = "0.1.63"
|
||||
log = "0.4.27"
|
||||
sea-orm.workspace = true
|
||||
sea-orm-migration.workspace = true
|
||||
sea-orm = { workspace = true, optional = true }
|
||||
serde.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
toml.workspace = true
|
||||
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
use chrono_tz::Tz;
|
||||
#[cfg(feature="clap")]
|
||||
use clap::Args;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct GlobalConfig {
|
||||
pub time_zone: Option<Tz>,
|
||||
}
|
||||
|
||||
impl TryFrom<PartialGlobalConfig> for GlobalConfig{
|
||||
type Error = Error;
|
||||
fn try_from(p: PartialGlobalConfig) -> Result<GlobalConfig, Self::Error> {
|
||||
Ok(GlobalConfig{
|
||||
time_zone: p.time_zone,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[cfg_attr(feature="clap", derive(Args))]
|
||||
pub struct PartialGlobalConfig {
|
||||
#[cfg_attr(feature="clap", arg(short, long))]
|
||||
|
||||
pub time_zone: Option<Tz>,
|
||||
}
|
||||
|
||||
pub static GLOBAL_CONFIG: OnceCell<GlobalConfig> = OnceCell::const_new();
|
|
@ -1,14 +0,0 @@
|
|||
mod database;
|
||||
mod global;
|
||||
|
||||
pub use database::{
|
||||
DatabaseConfig,
|
||||
PartialDatabaseConfig,
|
||||
DATABASE_CONFIG,
|
||||
};
|
||||
|
||||
pub use global::{
|
||||
GlobalConfig,
|
||||
PartialGlobalConfig,
|
||||
GLOBAL_CONFIG,
|
||||
};
|
|
@ -20,6 +20,7 @@ pub enum Error {
|
|||
InitializingOnceCell(String),
|
||||
#[error("Once cell is already Initialized: {0}")]
|
||||
AlreadyInitializedOnceCell(String),
|
||||
#[cfg(feature="desktop")]
|
||||
#[error("DB Error: {0}")]
|
||||
Db(#[from]sea_orm::DbErr),
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use chrono_tz::Tz;
|
||||
|
||||
use crate::{config::DatabaseConfig, error::Error};
|
||||
use crate::error::Error;
|
||||
|
||||
pub trait GlobalConfig<T> {
|
||||
fn get_config(&self) -> Option<T>;
|
||||
fn get_or_try_init_config(&self) -> Result<T, Error>;
|
||||
fn get_database_config(&self) -> Option<DatabaseConfig>;
|
||||
fn get_or_try_init_config_from_file(&self) -> Result<T, Error>;
|
||||
fn get_or_try_init_config_from_str(&self) -> Result<T, Error>;
|
||||
fn get_time_zone(&self) -> Option<Tz>;
|
||||
}
|
|
@ -1,17 +1,9 @@
|
|||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
use crate::error::Error;
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection, DbErr};
|
||||
|
||||
use sea_orm_migration::MigratorTrait;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
pub trait GlobalDatabase {
|
||||
fn get_database(&self) -> Option<&DatabaseConnection>;
|
||||
async fn get_or_try_init_database(&self) -> Result<&DatabaseConnection, Error>;
|
||||
async fn get_or_try_init_database_with_connect_options<T>(&self, options: T) -> Result<&DatabaseConnection, Error> where
|
||||
T: Into<ConnectOptions>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
async fn get_or_try_init_database_with_connect_options<T>(&self, options: T) -> Result<&DatabaseConnection, Error> where
|
||||
T: Into<ConnectOptions>;
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
mod config;
|
||||
#[cfg(feature="desktop")]
|
||||
mod database;
|
||||
|
||||
pub use config::GlobalConfig;
|
||||
|
||||
#[cfg(feature="desktop")]
|
||||
pub use database::GlobalDatabase;
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
pub mod config;
|
||||
pub mod csv;
|
||||
pub mod entity;
|
||||
pub mod error;
|
||||
|
|
19
progress-pile-migration-client/Cargo.toml
Normal file
19
progress-pile-migration-client/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "progress-pile-migration-client"
|
||||
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
|
||||
]
|
12
progress-pile-migration-client/src/lib.rs
Normal file
12
progress-pile-migration-client/src/lib.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
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)]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
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> {
|
||||
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?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressCategory{}
|
||||
|
||||
impl TableMigration for ProgressCategory {
|
||||
fn table_create_statement() -> TableCreateStatement{
|
||||
let mut tcs = DefaultProgressCategory::table_create_statement();
|
||||
tcs.primary_key(Index::create().name(PK_PROGRESS_CATEGORY).col(DefaultProgressCategory::Id));
|
||||
tcs
|
||||
}
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
DefaultProgressCategory::index_create_statements()
|
||||
}
|
||||
fn table_drop_statement() -> TableDropStatement{
|
||||
DefaultProgressCategory::table_drop_statement()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressEntry {}
|
||||
|
||||
impl TableMigration for ProgressEntry {
|
||||
|
||||
fn table_create_statement() -> TableCreateStatement{
|
||||
let mut tcs: TableCreateStatement = DefaultProgressEntry::table_create_statement();
|
||||
tcs.primary_key(Index::create().name(PK_PROGRESS_ENTITY).col(DefaultProgressEntry::Id));
|
||||
tcs
|
||||
}
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
DefaultProgressEntry::index_create_statements()
|
||||
}
|
||||
fn table_drop_statement() -> TableDropStatement {
|
||||
DefaultProgressEntry::table_drop_statement()
|
||||
}
|
||||
}
|
6
progress-pile-migration-client/src/main.rs
Normal file
6
progress-pile-migration-client/src/main.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
cli::run_cli(progress_pile_migration_client::Migrator).await;
|
||||
}
|
9
progress-pile-migration-core/Cargo.toml
Normal file
9
progress-pile-migration-core/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "progress-pile-migration-core"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "1", features = ["attributes", "tokio1"] }
|
||||
sea-orm-migration = "1.1.0"
|
41
progress-pile-migration-core/README.md
Normal file
41
progress-pile-migration-core/README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# 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
progress-pile-migration-core/src/lib.rs
Normal file
1
progress-pile-migration-core/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod m20220101_000001_create_table;
|
|
@ -0,0 +1,145 @@
|
|||
use sea_orm_migration::{prelude::*, schema::*};
|
||||
|
||||
|
||||
pub trait TableMigration {
|
||||
async fn up<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||
manager.create_table(Self::table_create_statement()).await?;
|
||||
for statement in Self::index_create_statements().into_iter() {
|
||||
manager.create_index(statement).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn down<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||
manager.drop_table(Self::table_drop_statement()).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn table_create_statement() -> TableCreateStatement;
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement>;
|
||||
fn table_drop_statement() -> TableDropStatement;
|
||||
}
|
||||
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressCategory {
|
||||
Table,
|
||||
Id,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
Name,
|
||||
}
|
||||
|
||||
static IDX_PROGRESS_CATEGORY_NAME: &str = "idx_progress_category_name";
|
||||
static IDX_PROGRESS_CATEGORY_CREATED_AT: &str = "idx_progress_category_created_at";
|
||||
static IDX_PROGRESS_CATEGORY_UPDATED_AT: &str = "idx_progress_category_updated_at";
|
||||
static IDX_PROGRESS_CATEGORY_DELETED_AT: &str = "idx_progress_category_deleted_at";
|
||||
pub static PK_PROGRESS_CATEGORY: &str = "pk_progress_category";
|
||||
|
||||
impl TableMigration for ProgressCategory {
|
||||
|
||||
fn table_create_statement() -> TableCreateStatement {
|
||||
Table::create()
|
||||
.table(Self::Table)
|
||||
.if_not_exists()
|
||||
.col(uuid(Self::Id))
|
||||
.col(string(Self::Name))
|
||||
.col(timestamp_with_time_zone(Self::CreatedAt))
|
||||
.col(timestamp_with_time_zone(Self::UpdatedAt))
|
||||
.col(timestamp_with_time_zone_null(Self::DeletedAt))
|
||||
.to_owned()
|
||||
}
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
vec![
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_NAME)
|
||||
.table(Self::Table)
|
||||
.col(Self::Name)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
]
|
||||
}
|
||||
fn table_drop_statement() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressEntry {
|
||||
Table,
|
||||
Id,
|
||||
ProgressCategoryId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
ProgressedAt,
|
||||
Quantity,
|
||||
Note,
|
||||
}
|
||||
|
||||
static IDX_PROGRESS_ENTITY_CREATED_AT: &str = "idx_progress_entity_created_at";
|
||||
static IDX_PROGRESS_ENTITY_UPDATED_AT: &str = "idx_progress_entity_updated_at";
|
||||
static IDX_PROGRESS_ENTITY_DELETED_AT: &str = "idx_progress_entity_deleted_at";
|
||||
static IDX_PROGRESS_ENTITY_PROGRESSED_AT: &str = "idx_progress_entity_progressed_at";
|
||||
static FK_PROGRESS_ENTITY_PROGRESS_CATEGORY: &str = "fk_progress_entity_progress_category";
|
||||
pub static PK_PROGRESS_ENTITY: &str = "pk_progress_entity";
|
||||
|
||||
impl TableMigration for ProgressEntry {
|
||||
|
||||
fn table_create_statement() -> TableCreateStatement {
|
||||
Table::create()
|
||||
.table(Self::Table)
|
||||
.if_not_exists()
|
||||
.col(uuid(Self::Id))
|
||||
.col(uuid(Self::ProgressCategoryId))
|
||||
.col(timestamp_with_time_zone(Self::CreatedAt))
|
||||
.col(timestamp_with_time_zone(Self::UpdatedAt))
|
||||
.col(timestamp_with_time_zone_null(Self::DeletedAt))
|
||||
.col(timestamp_with_time_zone(Self::ProgressedAt))
|
||||
.col(integer(Self::Quantity))
|
||||
.col(string(Self::Note))
|
||||
.foreign_key(ForeignKey::create()
|
||||
.name(FK_PROGRESS_ENTITY_PROGRESS_CATEGORY)
|
||||
.from(Self::Table, Self::ProgressCategoryId)
|
||||
.to(ProgressCategory::Table, ProgressCategory::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
)
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn index_create_statements() -> Vec<IndexCreateStatement> {
|
||||
vec![
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_PROGRESSED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::ProgressedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
]
|
||||
}
|
||||
|
||||
fn table_drop_statement() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
19
progress-pile-migration-server/Cargo.toml
Normal file
19
progress-pile-migration-server/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
|||
[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
|
||||
]
|
41
progress-pile-migration-server/README.md
Normal file
41
progress-pile-migration-server/README.md
Normal file
|
@ -0,0 +1,41 @@
|
|||
# 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
|
||||
```
|
12
progress-pile-migration-server/src/lib.rs
Normal file
12
progress-pile-migration-server/src/lib.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
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)]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
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()
|
||||
}
|
||||
|
||||
|
||||
}
|
6
progress-pile-migration-server/src/main.rs
Normal file
6
progress-pile-migration-server/src/main.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
cli::run_cli(progress_pile_migration_server::Migrator).await;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
[package]
|
||||
name = "progress-pile-migration"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[features]
|
||||
default = ["client", "server"]
|
||||
client = []
|
||||
server = ["sea-orm-migration/sqlx-postgres"]
|
||||
|
||||
[dependencies]
|
||||
async-std = { version = "1", features = ["attributes", "tokio1"] }
|
||||
|
||||
[dependencies.sea-orm-migration]
|
||||
version = "1.1.0"
|
||||
features = [
|
||||
"runtime-tokio-rustls",
|
||||
"sqlx-sqlite",
|
||||
]
|
|
@ -1,52 +0,0 @@
|
|||
pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20220101_000001_create_table;
|
||||
|
||||
#[cfg(feature="client")]
|
||||
pub struct ClientMigrator;
|
||||
|
||||
#[cfg(feature="client")]
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for ClientMigrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![
|
||||
Box::new(m20220101_000001_create_table::ClientMigration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="server")]
|
||||
pub struct ServerMigrator;
|
||||
|
||||
#[cfg(feature="server")]
|
||||
#[async_trait::async_trait]
|
||||
impl MigratorTrait for ServerMigrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![
|
||||
Box::new(m20220101_000001_create_table::ServerMigration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use sea_orm_migration::sea_orm::{ConnectOptions, Database};
|
||||
|
||||
use super::*;
|
||||
#[cfg(feature="client")]
|
||||
#[async_std::test]
|
||||
async fn client_migration() {
|
||||
let db = Database::connect(ConnectOptions::new("sqlite::memory:")).await.unwrap();
|
||||
ClientMigrator::up(&db, None).await.unwrap();
|
||||
ClientMigrator::down(&db, None).await.unwrap();
|
||||
}
|
||||
#[cfg(feature="server")]
|
||||
|
||||
#[async_std::test]
|
||||
async fn server_migration() {
|
||||
let db = Database::connect(ConnectOptions::new("sqlite::memory:")).await.unwrap();
|
||||
ServerMigrator::up(&db, None).await.unwrap();
|
||||
ServerMigrator::down(&db, None).await.unwrap();
|
||||
}
|
||||
}
|
|
@ -1,532 +0,0 @@
|
|||
use sea_orm_migration::{prelude::*, schema::*};
|
||||
|
||||
#[cfg(feature="client")]
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct ClientMigration;
|
||||
|
||||
#[cfg(feature="client")]
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for ClientMigration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
ProgressCategory::up_client(manager).await?;
|
||||
ProgressEntry::up_client(manager).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
ProgressEntry::down_client(manager).await?;
|
||||
ProgressCategory::down_client(manager).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="server")]
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct ServerMigration;
|
||||
|
||||
#[cfg(feature="server")]
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for ServerMigration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
User::up_server(manager).await?;
|
||||
AccessToken::up_server(manager).await?;
|
||||
ProgressCategory::up_server(manager).await?;
|
||||
ProgressEntry::up_server(manager).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
ProgressEntry::down_server(manager).await?;
|
||||
ProgressCategory::down_server(manager).await?;
|
||||
AccessToken::down_server(manager).await?;
|
||||
User::down_server(manager).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
pub trait MigrationTableDefault {
|
||||
|
||||
fn table_create_statement_default() -> TableCreateStatement;
|
||||
fn index_create_statements_default() -> Vec<IndexCreateStatement>;
|
||||
fn table_drop_statement_default() -> TableDropStatement;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#[cfg(feature="client")]
|
||||
#[async_trait::async_trait]
|
||||
pub trait MigrationTableClient {
|
||||
async fn up_client<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||
manager.create_table(Self::table_create_statement_client()).await?;
|
||||
for statement in Self::index_create_statements_client().into_iter() {
|
||||
manager.create_index(statement).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
async fn down_client<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||
manager.drop_table(Self::table_drop_statement_client()).await?;
|
||||
Ok(())
|
||||
}
|
||||
fn table_create_statement_client() -> TableCreateStatement;
|
||||
fn index_create_statements_client() -> Vec<IndexCreateStatement>;
|
||||
fn table_drop_statement_client() -> TableDropStatement;
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature="server")]
|
||||
#[async_trait::async_trait]
|
||||
pub trait MigrationTableServer {
|
||||
|
||||
async fn up_server<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||
manager.create_table(Self::table_create_statement_server()).await?;
|
||||
for statement in Self::index_create_statements_server().into_iter() {
|
||||
manager.create_index(statement).await?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn down_server<'a>(manager: &'a SchemaManager<'a>) -> Result<(), DbErr> {
|
||||
manager.drop_table(Self::table_drop_statement_server()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn table_create_statement_server() -> TableCreateStatement;
|
||||
fn index_create_statements_server() -> Vec<IndexCreateStatement>;
|
||||
fn table_drop_statement_server() -> TableDropStatement;
|
||||
}
|
||||
|
||||
#[cfg(feature="server")]
|
||||
#[derive(DeriveIden)]
|
||||
pub enum User {
|
||||
Table,
|
||||
Id,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
LoginName,
|
||||
PasswordHash,
|
||||
}
|
||||
|
||||
#[cfg(feature="server")]
|
||||
static IDX_USER_LOGIN_NAME: &str = "idx_user_login_name";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_USER_CREATED_AT: &str = "idx_user_created_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_USER_UPDATED_AT: &str = "idx_user_updated_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_USER_DELETED_AT: &str = "idx_user_deleted_at";
|
||||
|
||||
#[cfg(feature="server")]
|
||||
impl MigrationTableServer for User {
|
||||
|
||||
fn table_create_statement_server() -> 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_server() -> 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_server() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="server")]
|
||||
#[derive(DeriveIden)]
|
||||
pub enum AccessToken{
|
||||
Table,
|
||||
Id,
|
||||
UserId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
ExpiredAt,
|
||||
TokenValue,
|
||||
Note,
|
||||
}
|
||||
|
||||
#[cfg(feature="server")]
|
||||
static IDX_ACCESS_TOKEN_TOKEN_VALUE: &str = "idx_access_token_token_value";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_ACCESS_TOKEN_CREATED_AT: &str = "idx_access_token_created_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_ACCESS_TOKEN_UPDATED_AT: &str = "idx_access_token_updated_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_ACCESS_TOKEN_EXPIRED_AT: &str = "idx_access_token_expired_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_ACCESS_TOKEN_USER_ID_CREATED_AT: &str = "idx_access_token_user_id_created_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_ACCESS_TOKEN_USER_ID_UPDATED_AT: &str = "idx_access_token_user_id_updated_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_ACCESS_TOKEN_USER_ID_EXPIRED_AT: &str = "idx_access_token_user_id_expired_at";
|
||||
#[cfg(feature="server")]
|
||||
static FK_ACCESS_TOKEN_USER: &str = "fk_access_token_user";
|
||||
|
||||
#[cfg(feature="server")]
|
||||
impl MigrationTableServer for AccessToken {
|
||||
|
||||
fn table_create_statement_server() -> 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_server() -> 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_server() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressCategory {
|
||||
Table,
|
||||
#[cfg(feature="server")]
|
||||
UserId,
|
||||
Id,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
Name,
|
||||
}
|
||||
|
||||
static IDX_PROGRESS_CATEGORY_NAME: &str = "idx_progress_category_name";
|
||||
static IDX_PROGRESS_CATEGORY_CREATED_AT: &str = "idx_progress_category_created_at";
|
||||
static IDX_PROGRESS_CATEGORY_UPDATED_AT: &str = "idx_progress_category_updated_at";
|
||||
static IDX_PROGRESS_CATEGORY_DELETED_AT: &str = "idx_progress_category_deleted_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_NAME: &str = "idx_progress_category_user_id_name";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_CREATED_AT: &str = "idx_progress_category_user_id_created_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_UPDATED_AT: &str = "idx_progress_category_user_id_updated_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_PROGRESS_CATEGORY_USER_ID_DELETED_AT: &str = "idx_progress_category_user_id_deleted_at";
|
||||
#[cfg(feature="server")]
|
||||
static FK_PROGRESS_CATEGORY_USER: &str = "fk_progress_category_user";
|
||||
static PK_PROGRESS_CATEGORY: &str = "pk_progress_category";
|
||||
|
||||
impl MigrationTableDefault for ProgressCategory {
|
||||
|
||||
fn table_create_statement_default() -> TableCreateStatement {
|
||||
Table::create()
|
||||
.table(Self::Table)
|
||||
.if_not_exists()
|
||||
.col(string(Self::Name))
|
||||
.col(timestamp_with_time_zone(Self::CreatedAt))
|
||||
.col(timestamp_with_time_zone(Self::UpdatedAt))
|
||||
.col(timestamp_with_time_zone_null(Self::DeletedAt))
|
||||
.to_owned()
|
||||
}
|
||||
fn index_create_statements_default() -> Vec<IndexCreateStatement> {
|
||||
vec![
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_NAME)
|
||||
.table(Self::Table)
|
||||
.col(Self::Name)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
]
|
||||
}
|
||||
fn table_drop_statement_default() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="client")]
|
||||
impl MigrationTableClient for ProgressCategory {
|
||||
fn table_create_statement_client() -> TableCreateStatement{
|
||||
let mut tcs = Self::table_create_statement_default();
|
||||
tcs.col(pk_uuid(Self::Id));
|
||||
tcs
|
||||
}
|
||||
fn index_create_statements_client() -> Vec<IndexCreateStatement> {
|
||||
Self::index_create_statements_default()
|
||||
}
|
||||
fn table_drop_statement_client() -> TableDropStatement{
|
||||
Self::table_drop_statement_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="server")]
|
||||
impl MigrationTableServer for ProgressCategory {
|
||||
|
||||
fn table_create_statement_server() -> TableCreateStatement{
|
||||
let mut tcs = Self::table_create_statement_default();
|
||||
tcs.col(uuid(Self::Id));
|
||||
|
||||
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(Self::Id));
|
||||
tcs
|
||||
}
|
||||
|
||||
fn index_create_statements_server() -> Vec<IndexCreateStatement> {
|
||||
[Self::index_create_statements_default(), vec![
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_NAME)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::Name)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_CATEGORY_USER_ID_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
]].concat()
|
||||
}
|
||||
fn table_drop_statement_server() -> TableDropStatement {
|
||||
Self::table_drop_statement_default()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum ProgressEntry {
|
||||
Table,
|
||||
#[cfg(feature="server")]
|
||||
UserId,
|
||||
Id,
|
||||
ProgressCategoryId,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
DeletedAt,
|
||||
ProgressedAt,
|
||||
Quantity,
|
||||
Note,
|
||||
}
|
||||
|
||||
static IDX_PROGRESS_ENTITY_CREATED_AT: &str = "idx_progress_entity_created_at";
|
||||
static IDX_PROGRESS_ENTITY_UPDATED_AT: &str = "idx_progress_entity_updated_at";
|
||||
static IDX_PROGRESS_ENTITY_DELETED_AT: &str = "idx_progress_entity_deleted_at";
|
||||
static IDX_PROGRESS_ENTITY_PROGRESSED_AT: &str = "idx_progress_entity_progressed_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_PROGRESS_ENTITY_USER_ID_CREATED_AT: &str = "idx_progress_entity_user_id_created_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_PROGRESS_ENTITY_USER_ID_UPDATED_AT: &str = "idx_progress_entity_user_id_updated_at";
|
||||
#[cfg(feature="server")]
|
||||
static IDX_PROGRESS_ENTITY_USER_ID_DELETED_AT: &str = "idx_progress_entity_user_id_deleted_at";
|
||||
#[cfg(feature="server")]
|
||||
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";
|
||||
#[cfg(feature="server")]
|
||||
static FK_PROGRESS_ENTITY_USER: &str = "fk_progress_entity_user";
|
||||
static PK_PROGRESS_ENTITY: &str = "pk_progress_entity";
|
||||
|
||||
impl MigrationTableDefault for ProgressEntry {
|
||||
|
||||
fn table_create_statement_default() -> TableCreateStatement {
|
||||
Table::create()
|
||||
.table(Self::Table)
|
||||
.if_not_exists()
|
||||
.col(uuid(Self::Id))
|
||||
.col(uuid(Self::ProgressCategoryId))
|
||||
.col(timestamp_with_time_zone(Self::CreatedAt))
|
||||
.col(timestamp_with_time_zone(Self::UpdatedAt))
|
||||
.col(timestamp_with_time_zone_null(Self::DeletedAt))
|
||||
.col(timestamp_with_time_zone(Self::ProgressedAt))
|
||||
.col(integer(Self::Quantity))
|
||||
.col(string(Self::Note))
|
||||
.foreign_key(ForeignKey::create()
|
||||
.name(FK_PROGRESS_ENTITY_PROGRESS_CATEGORY)
|
||||
.from(Self::Table, Self::ProgressCategoryId)
|
||||
.to(ProgressCategory::Table, ProgressCategory::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade)
|
||||
.on_update(ForeignKeyAction::Cascade)
|
||||
)
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
fn index_create_statements_default() -> Vec<IndexCreateStatement> {
|
||||
vec![
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_PROGRESSED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::ProgressedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
]
|
||||
}
|
||||
|
||||
fn table_drop_statement_default() -> TableDropStatement {
|
||||
Table::drop().table(Self::Table).to_owned()
|
||||
}
|
||||
}
|
||||
#[cfg(feature="client")]
|
||||
impl MigrationTableClient for ProgressEntry {
|
||||
|
||||
fn table_create_statement_client() -> TableCreateStatement{
|
||||
let mut tcs: TableCreateStatement = Self::table_create_statement_default();
|
||||
tcs.primary_key(Index::create().name(PK_PROGRESS_ENTITY).col(Self::Id));
|
||||
tcs
|
||||
}
|
||||
fn index_create_statements_client() -> Vec<IndexCreateStatement> {
|
||||
Self::index_create_statements_default()
|
||||
}
|
||||
fn table_drop_statement_client() -> TableDropStatement {
|
||||
Self::table_drop_statement_default()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
impl MigrationTableServer for ProgressEntry {
|
||||
|
||||
fn table_create_statement_server() -> TableCreateStatement{
|
||||
let mut tcs: TableCreateStatement = Self::table_create_statement_default();
|
||||
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(Self::Id));
|
||||
|
||||
tcs
|
||||
|
||||
}
|
||||
|
||||
fn index_create_statements_server() -> Vec<IndexCreateStatement> {
|
||||
let mut default = Self::index_create_statements_default();
|
||||
default.append(&mut vec![
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_CREATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::CreatedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_DELETED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::DeletedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_PROGRESSED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::ProgressedAt)
|
||||
.to_owned(),
|
||||
Index::create().name(IDX_PROGRESS_ENTITY_USER_ID_UPDATED_AT)
|
||||
.table(Self::Table)
|
||||
.col(Self::UserId)
|
||||
.col(Self::UpdatedAt)
|
||||
.to_owned(),
|
||||
]);
|
||||
default
|
||||
}
|
||||
|
||||
fn table_drop_statement_server() -> TableDropStatement {
|
||||
Self::table_drop_statement_default()
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -10,8 +10,8 @@ argon2 = "0.5.3"
|
|||
async-graphql.workspace = true
|
||||
axum = "0.8.4"
|
||||
clap = {workspace = true, features = ["derive"]}
|
||||
progress-pile-core = {workspace = true}
|
||||
progress-pile-migration = { workspace = true, features = ["server"] }
|
||||
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
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
use crate::config::PartialServerConfig;
|
||||
use clap::Parser;
|
||||
use progress_pile_core::config::{
|
||||
PartialDatabaseConfig,
|
||||
PartialGlobalConfig,
|
||||
};
|
||||
use std::{
|
||||
net::IpAddr,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use crate::config::ServerConfig;
|
||||
|
||||
#[derive(Clone, Debug, Parser)]
|
||||
#[command(version, about, long_about = None)]
|
||||
pub struct Args {
|
||||
#[command(flatten)]
|
||||
pub server: PartialServerConfig,
|
||||
|
||||
#[command(flatten)]
|
||||
pub global: PartialGlobalConfig,
|
||||
|
||||
#[command(flatten)]
|
||||
pub database: PartialDatabaseConfig,
|
||||
|
||||
#[arg(short, long)]
|
||||
pub config_file: Option<PathBuf>,
|
||||
}
|
||||
|
17
progress-pile-server/src/cli.rs
Normal file
17
progress-pile-server/src/cli.rs
Normal file
|
@ -0,0 +1,17 @@
|
|||
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,147 +0,0 @@
|
|||
use crate::error::Error;
|
||||
|
||||
use progress_pile_core::config::{
|
||||
DatabaseConfig, GlobalConfig, PartialDatabaseConfig
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
default::Default,
|
||||
str::FromStr,
|
||||
net::IpAddr,
|
||||
};
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct Config {
|
||||
global: GlobalConfig,
|
||||
database: DatabaseConfig,
|
||||
server: ServerConfig,
|
||||
}
|
||||
|
||||
impl TryFrom<PartialConfig> for Config {
|
||||
type Error = Error;
|
||||
fn try_from(p: PartialConfig) -> Result<Config, Self::Error> {
|
||||
Ok(Config {
|
||||
global: p.global.ok_or(Error::MissingConfig("global".to_string()))?,
|
||||
database: p.database.ok_or(Error::MissingConfig("global".to_string()))?,
|
||||
server: p.server.ok_or(Error::MissingConfig("global".to_string()))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct PartialConfig {
|
||||
global: Option<GlobalConfig>,
|
||||
database: Option<DatabaseConfig>,
|
||||
server: Option<ServerConfig>,
|
||||
}
|
||||
|
||||
pub static SERVER_CONFIG: OnceServerConfig = OnceServerConfig::const_new();
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
pub struct ServerConfig {
|
||||
pub listen_ips: Vec<IpAddr>,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
impl ServerConfig {
|
||||
|
||||
}
|
||||
|
||||
impl TryFrom<PartialServerConfig> for ServerConfig {
|
||||
type Error = Error;
|
||||
fn try_from(p: PartialServerConfig) -> Result<ServerConfig, Error>{
|
||||
Ok(ServerConfig{
|
||||
listen_ips: p.listen_ips.ok_or(Error::MissingConfig("listen_ips".to_string()))?,
|
||||
port: p.port.ok_or(Error::MissingConfig("port".to_string()))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct OnceServerConfig {
|
||||
inner: OnceCell<ServerConfig>,
|
||||
}
|
||||
|
||||
impl OnceServerConfig {
|
||||
const fn const_new() -> Self {
|
||||
Self {
|
||||
inner: OnceCell::const_new(),
|
||||
}
|
||||
}
|
||||
pub fn get(&self) -> Option<&ServerConfig> {
|
||||
self.inner.get()
|
||||
}
|
||||
pub async fn get_or_init<F, T>(&self, f: F) -> &ServerConfig where
|
||||
F: FnOnce() -> T,
|
||||
T: Future<Output = ServerConfig>
|
||||
{
|
||||
self.inner.get_or_init(f).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[derive(clap::Args)]
|
||||
|
||||
pub struct PartialServerConfig {
|
||||
#[arg(short, long,)]
|
||||
pub listen_ips: Option<Vec<IpAddr>>,
|
||||
#[arg(short, long,)]
|
||||
pub port: Option<u16>,
|
||||
}
|
||||
|
||||
impl PartialServerConfig {
|
||||
|
||||
pub fn try_from_toml(s: &str) -> Result<Self, Error> {
|
||||
Ok(toml::from_str(s)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PartialServerConfig {
|
||||
fn default() -> Self {
|
||||
PartialServerConfig {
|
||||
listen_ips: Some(vec!["127.0.0.1".parse().unwrap(), "::1".parse().unwrap()]),
|
||||
port: Some(3000),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PartialServerConfig {
|
||||
type Err = Error;
|
||||
/// #Examples
|
||||
/// ```
|
||||
/// use dpts_config::{
|
||||
/// PartialServerConfig,
|
||||
/// PartialDatabaseConfig,
|
||||
/// };
|
||||
/// use std::{
|
||||
/// default::Default,
|
||||
/// net::IpAddr
|
||||
/// };
|
||||
/// let config_from_str: PartialServerConfig = r#"
|
||||
/// listen_ips = ["0.0.0.0"]
|
||||
/// port = 8000
|
||||
/// time_zone = "Asia/Tokyo"
|
||||
///
|
||||
/// [database]
|
||||
/// url = "sqlite::memory:"
|
||||
/// sqlx_logging = true
|
||||
/// "#.parse().unwrap();
|
||||
///
|
||||
/// let config: PartialServerConfig = PartialServerConfig{
|
||||
/// listen_ips : Some(vec!["0.0.0.0".parse().unwrap()]),
|
||||
/// port: Some(8000),
|
||||
/// time_zone: Some(chrono_tz::Asia::Tokyo),
|
||||
/// database: Some(PartialDatabaseConfig {
|
||||
/// url: Some("sqlite::memory:".to_string()),
|
||||
/// sqlx_logging: Some(true),
|
||||
/// ..Default::default()
|
||||
/// }),
|
||||
/// };
|
||||
///
|
||||
/// assert_eq!(config_from_str, config);
|
||||
/// ```
|
||||
///
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(toml::from_str(s)?)
|
||||
}
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature="clap")]
|
||||
use clap::Args;
|
||||
use sea_orm::ConnectOptions;
|
||||
use serde::Deserialize;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
|
@ -64,26 +62,28 @@ impl TryFrom<PartialDatabaseConfig> for DatabaseConfig{
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq)]
|
||||
#[cfg_attr(feature="clap", derive(Args))]
|
||||
#[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>,
|
||||
#[cfg_attr(feature="clap", arg(value_parser = parse_duration ))]
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub connect_timeout: Option<Duration>,
|
||||
#[cfg_attr(feature="clap", arg(value_parser = parse_duration ))]
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub acquire_timeout: Option<Duration>,
|
||||
#[cfg_attr(feature="clap", arg(value_parser = parse_duration ))]
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub idle_timeout: Option<Duration>,
|
||||
#[cfg_attr(feature="clap", arg(value_parser = parse_duration ))]
|
||||
#[arg(long, value_parser = parse_duration )]
|
||||
pub max_lifetime: Option<Duration>,
|
||||
#[arg(long)]
|
||||
pub sqlx_logging: Option<bool>
|
||||
}
|
||||
|
||||
#[cfg(feature="clap")]
|
||||
fn parse_duration(arg: &str) -> Result<std::time::Duration, Error> {
|
||||
let seconds = arg.parse()?;
|
||||
Ok(std::time::Duration::from_secs(seconds))
|
||||
}
|
||||
|
||||
pub static DATABASE_CONFIG: OnceCell<DatabaseConfig> = OnceCell::const_new();
|
10
progress-pile-server/src/config/mod.rs
Normal file
10
progress-pile-server/src/config/mod.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
mod database;
|
||||
|
||||
pub use database::{
|
||||
DatabaseConfig,
|
||||
PartialDatabaseConfig
|
||||
};
|
||||
|
||||
pub struct ServerConfig {}
|
||||
|
||||
pub struct PartialServerConfig {}
|
|
@ -45,7 +45,6 @@ mod tests {
|
|||
use std::time::Duration;
|
||||
use chrono::{offset, FixedOffset, Local, TimeZone};
|
||||
use sea_orm::{entity::*, query::*, ConnectOptions, Database};
|
||||
use progress_pile_migration::{ServerMigrator, MigratorTrait};
|
||||
use crate::{entity::*, global::GLOBAL};
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use progress_pile_migration::{ServerMigrator, MigratorTrait};
|
||||
use progress_pile_migration_server::{Migrator, MigratorTrait};
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
use crate::error::Error;
|
||||
use tokio::sync::OnceCell;
|
||||
|
@ -19,7 +19,7 @@ impl GlobalDatabase for Global {
|
|||
{
|
||||
Ok(self.database.get_or_try_init(|| async {
|
||||
let db = Database::connect(options).await?;
|
||||
ServerMigrator::up(&db, None).await?;
|
||||
Migrator::up(&db, None).await?;
|
||||
Ok::<DatabaseConnection, Error>(db)
|
||||
}).await?)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
mod args;
|
||||
mod auth;
|
||||
mod config;
|
||||
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 args::Args;
|
||||
pub use cli::Cli;
|
||||
use async_graphql::{EmptySubscription, Schema};
|
||||
use async_graphql_axum::{
|
||||
GraphQL,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use async_graphql::{http::{playground_source, GraphQLPlaygroundConfig}, *};
|
||||
use async_graphql_axum::GraphQL;
|
||||
|
||||
use progress_pile_server::{build_app, Args};
|
||||
use progress_pile_server::{build_app, cli::Cli};
|
||||
use axum::{response::{Html, IntoResponse}, routing::get, Router};
|
||||
use clap::Parser;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue