Compare commits

...

6 Commits

32 changed files with 2301 additions and 67 deletions

View File

@ -1,2 +1,3 @@
SESAM_FSV_VERSION=1.40.13 SESAM_FSV_VERSION=1.40.13
SESAM_INI_PATH=/etc/opt/santesocial/fsv/${SESAM_FSV_VERSION}/conf/sesam.ini SESAM_INI_PATH=/etc/opt/santesocial/fsv/${SESAM_FSV_VERSION}/conf/sesam.ini
DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc

View File

@ -1,2 +1,3 @@
SESAM_FSV_VERSION=1.40.13 SESAM_FSV_VERSION=1.40.13
SESAM_INI_PATH=${ALLUSERSPROFILE}\\santesocial\\fsv\\${SESAM_FSV_VERSION}\\conf\\sesam.ini SESAM_INI_PATH=${ALLUSERSPROFILE}\\santesocial\\fsv\\${SESAM_FSV_VERSION}\\conf\\sesam.ini
DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc

3
.gitignore vendored
View File

@ -23,3 +23,6 @@ target/
# Ignore .env files # Ignore .env files
.env .env
# Development Database
*.sqlite

1731
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,4 +5,16 @@ members = [
"crates/sesam-vitale", "crates/sesam-vitale",
"crates/desktop", "crates/desktop",
"crates/utils", "crates/utils",
"migration",
"entity",
".",
] ]
[workspace.dependencies]
anyhow = "1.0"
axum = "0.7.5"
dotenv = "0.15"
sea-orm-cli = "1.0.1"
sea-orm = "1.0.1"
thiserror = "1.0"
tokio = "1.39.1"

View File

@ -42,6 +42,24 @@ La documentation d'installation est disponible sur le site officiel de Tailwindc
La version actuellement utilisée est la [`v3.4.7`](https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.4.7) La version actuellement utilisée est la [`v3.4.7`](https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.4.7)
#### SeaORM CLI
SeaORM est notre ORM. Le CLI SeaORM est nécessaire pour la génération des modèles de la base de données et des migrations associées. Elle peut être installée via Cargo :
```bash
cargo install sea-orm-cli
```
L'applicatif va chercher les informations de connexion à la base de données dans la variable `DATABASE_URL` importée depuis les [fichiers de configuration](#fichiers-de-configuration).
```.env
DATABASE_URL=sqlite://p4pillon.sqlite?mode=rwc
```
Toutefois, l'usage de la CLI de SeaORM nécessite de renseigner les informations de connexion à la base de données dans un fichier `.env` situé à la racine du projet.
> Astuce : utilisé un lien symbolique pour éviter de dupliquer le fichier `.env`.
#### SESAM-Vitale #### SESAM-Vitale
La crate `sesam-vitale` nécessite la présence des librairies dynamiques fournies par le package FSV et la CryptolibCPS. Les instructions d'installation sont disponibles dans le [README](crates/sesam-vitale/README.md) de la crate `sesam-vitale`. La crate `sesam-vitale` nécessite la présence des librairies dynamiques fournies par le package FSV et la CryptolibCPS. Les instructions d'installation sont disponibles dans le [README](crates/sesam-vitale/README.md) de la crate `sesam-vitale`.
@ -81,3 +99,25 @@ Packager le client desktop
```bash ```bash
cargo tauri build cargo tauri build
``` ```
## Gestion de la base de données
### Création d'une migration
```bash
sea-orm-cli migrate generate <nom_de_la_migration>
```
Cette commande génère un fichier de migration à adapter dans le dossier `migration/src`.
### Appliquer les migrations
```bash
sea-orm-cli migrate up
```
### Génération des entitées
```bash
sea-orm-cli generate entity -o entity/src/entities
```

View File

@ -6,16 +6,28 @@ edition = "2021"
[dependencies] [dependencies]
askama = "0.12.1" askama = "0.12.1"
askama_axum = "0.4.0" askama_axum = "0.4.0"
axum = "0.7.5" axum.workspace = true
axum-htmx = { version = "0.6", features = ["auto-vary"] } axum-htmx = { version = "0.6", features = ["auto-vary"] }
futures = "0.3.30"
listenfd = "1.0.1" listenfd = "1.0.1"
notify = "6.1.1" notify = "6.1.1"
sea-orm = { workspace = true, features = [
# Same `ASYNC_RUNTIME` and `DATABASE_DRIVER` as in the migration crate
"sqlx-sqlite",
"runtime-tokio-rustls",
"macros",
] }
serde = { version = "1.0.204", features = ["derive"] } serde = { version = "1.0.204", features = ["derive"] }
thiserror = "1.0.63" thiserror.workspace = true
tokio = { version = "1.39.1", features = ["macros", "rt-multi-thread"] } tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.5.2", features = ["fs"] } tower-http = { version = "0.5.2", features = ["fs"] }
tower-livereload = "0.9.3" tower-livereload = "0.9.3"
entity = { path = "../../entity" }
migration = { path = "../../migration" }
utils = { path = "../utils" }
[dev-dependencies] [dev-dependencies]
cargo-watch = "8.5.1" cargo-watch = "8.5.1"
systemfd = "0.4.0" systemfd = "0.4.0"
sea-orm-cli.workspace = true

View File

@ -2,6 +2,15 @@
- Récupérer le binaire TailwindCSS : https://tailwindcss.com/blog/standalone-cli - Récupérer le binaire TailwindCSS : https://tailwindcss.com/blog/standalone-cli
## Configuration
> Astuce : lorsqu'on exécute directement la crate `App` à des fins de développement, le système de configuration n'utilisera pas l'éventuel fichier `.env` situé à la racine du workspace Rust. Pour éviter de dupliquer le fichier `.env`, il est possible de créer un lien symbolique vers le fichier `.env` de la crate `App` :
```bash
cd crates/app
ln -s ../../.env .env
```
## Exécution ## Exécution
- Lancer tailwindcss en mode watch dans un terminal : - Lancer tailwindcss en mode watch dans un terminal :

View File

@ -580,6 +580,10 @@ video {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.mb-1 {
margin-bottom: 0.25rem;
}
.mb-2 { .mb-2 {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@ -604,6 +608,10 @@ video {
margin-top: 1rem; margin-top: 1rem;
} }
.mb-5 {
margin-bottom: 1.25rem;
}
.block { .block {
display: block; display: block;
} }
@ -616,6 +624,10 @@ video {
display: inline-flex; display: inline-flex;
} }
.table {
display: table;
}
.grid { .grid {
display: grid; display: grid;
} }
@ -696,6 +708,10 @@ video {
width: 100%; width: 100%;
} }
.max-w-3xl {
max-width: 48rem;
}
.max-w-7xl { .max-w-7xl {
max-width: 80rem; max-width: 80rem;
} }
@ -756,6 +772,12 @@ video {
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.divide-y > :not([hidden]) ~ :not([hidden]) { .divide-y > :not([hidden]) ~ :not([hidden]) {
--tw-divide-y-reverse: 0; --tw-divide-y-reverse: 0;
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
@ -801,6 +823,10 @@ video {
border-width: 2px; border-width: 2px;
} }
.border-b {
border-bottom-width: 1px;
}
.border-dashed { .border-dashed {
border-style: dashed; border-style: dashed;
} }
@ -850,6 +876,11 @@ video {
background-color: rgb(255 255 255 / var(--tw-bg-opacity)); background-color: rgb(255 255 255 / var(--tw-bg-opacity));
} }
.bg-blue-600 {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.p-2 { .p-2 {
padding: 0.5rem; padding: 0.5rem;
} }
@ -858,6 +889,10 @@ video {
padding: 1rem; padding: 1rem;
} }
.p-6 {
padding: 1.5rem;
}
.px-3 { .px-3 {
padding-left: 0.75rem; padding-left: 0.75rem;
padding-right: 0.75rem; padding-right: 0.75rem;
@ -868,26 +903,59 @@ video {
padding-right: 1rem; padding-right: 1rem;
} }
.px-5 {
padding-left: 1.25rem;
padding-right: 1.25rem;
}
.px-6 {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.py-10 { .py-10 {
padding-top: 2.5rem; padding-top: 2.5rem;
padding-bottom: 2.5rem; padding-bottom: 2.5rem;
} }
.py-12 {
padding-top: 3rem;
padding-bottom: 3rem;
}
.py-2 { .py-2 {
padding-top: 0.5rem; padding-top: 0.5rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.py-2\.5 {
padding-top: 0.625rem;
padding-bottom: 0.625rem;
}
.py-3 { .py-3 {
padding-top: 0.75rem; padding-top: 0.75rem;
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
} }
.py-4 {
padding-top: 1rem;
padding-bottom: 1rem;
}
.py-8 { .py-8 {
padding-top: 2rem; padding-top: 2rem;
padding-bottom: 2rem; padding-bottom: 2rem;
} }
.text-left {
text-align: left;
}
.text-center {
text-align: center;
}
.text-2xl { .text-2xl {
font-size: 1.5rem; font-size: 1.5rem;
line-height: 2rem; line-height: 2rem;
@ -908,10 +976,24 @@ video {
line-height: 1.25rem; line-height: 1.25rem;
} }
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.text-xs {
font-size: 0.75rem;
line-height: 1rem;
}
.font-bold { .font-bold {
font-weight: 700; font-weight: 700;
} }
.font-light {
font-weight: 300;
}
.font-medium { .font-medium {
font-weight: 500; font-weight: 500;
} }
@ -920,6 +1002,10 @@ video {
font-weight: 600; font-weight: 600;
} }
.uppercase {
text-transform: uppercase;
}
.leading-tight { .leading-tight {
line-height: 1.25; line-height: 1.25;
} }
@ -964,6 +1050,11 @@ video {
background-color: rgb(243 244 246 / var(--tw-bg-opacity)); background-color: rgb(243 244 246 / var(--tw-bg-opacity));
} }
.hover\:bg-blue-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
}
.focus\:outline-none:focus { .focus\:outline-none:focus {
outline: 2px solid transparent; outline: 2px solid transparent;
outline-offset: 2px; outline-offset: 2px;
@ -991,11 +1082,24 @@ video {
--tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity)); --tw-ring-color: rgb(209 213 219 / var(--tw-ring-opacity));
} }
.focus\:ring-blue-300:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity));
}
@media (min-width: 640px) { @media (min-width: 640px) {
.sm\:max-w-md {
max-width: 28rem;
}
.sm\:grid-cols-2 { .sm\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.sm\:p-8 {
padding: 2rem;
}
.sm\:px-6 { .sm\:px-6 {
padding-left: 1.5rem; padding-left: 1.5rem;
padding-right: 1.5rem; padding-right: 1.5rem;
@ -1051,6 +1155,12 @@ video {
margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(2rem * calc(1 - var(--tw-space-x-reverse)));
} }
.md\:space-y-5 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.25rem * var(--tw-space-y-reverse));
}
.md\:border-0 { .md\:border-0 {
border-width: 0px; border-width: 0px;
} }
@ -1072,6 +1182,11 @@ video {
padding: 1.5rem; padding: 1.5rem;
} }
.md\:text-2xl {
font-size: 1.5rem;
line-height: 2rem;
}
.md\:text-blue-700 { .md\:text-blue-700 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(29 78 216 / var(--tw-text-opacity)); color: rgb(29 78 216 / var(--tw-text-opacity));
@ -1088,6 +1203,10 @@ video {
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {
.lg\:mt-5 {
margin-top: 1.25rem;
}
.lg\:grid-cols-4 { .lg\:grid-cols-4 {
grid-template-columns: repeat(4, minmax(0, 1fr)); grid-template-columns: repeat(4, minmax(0, 1fr));
} }
@ -1102,12 +1221,20 @@ video {
--tw-space-x-reverse: 1; --tw-space-x-reverse: 1;
} }
.rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) {
text-align: right;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
.dark\:divide-gray-600 > :not([hidden]) ~ :not([hidden]) { .dark\:divide-gray-600 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-opacity: 1; --tw-divide-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-divide-opacity)); border-color: rgb(75 85 99 / var(--tw-divide-opacity));
} }
.dark\:border {
border-width: 1px;
}
.dark\:border-gray-600 { .dark\:border-gray-600 {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(75 85 99 / var(--tw-border-opacity)); border-color: rgb(75 85 99 / var(--tw-border-opacity));
@ -1133,6 +1260,11 @@ video {
background-color: rgb(17 24 39 / var(--tw-bg-opacity)); background-color: rgb(17 24 39 / var(--tw-bg-opacity));
} }
.dark\:bg-blue-600 {
--tw-bg-opacity: 1;
background-color: rgb(37 99 235 / var(--tw-bg-opacity));
}
.dark\:text-gray-200 { .dark\:text-gray-200 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(229 231 235 / var(--tw-text-opacity)); color: rgb(229 231 235 / var(--tw-text-opacity));
@ -1168,6 +1300,11 @@ video {
background-color: rgb(55 65 81 / var(--tw-bg-opacity)); background-color: rgb(55 65 81 / var(--tw-bg-opacity));
} }
.dark\:hover\:bg-blue-700:hover {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
}
.dark\:hover\:text-white:hover { .dark\:hover\:text-white:hover {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
@ -1177,6 +1314,11 @@ video {
--tw-ring-opacity: 1; --tw-ring-opacity: 1;
--tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity)); --tw-ring-color: rgb(75 85 99 / var(--tw-ring-opacity));
} }
.dark\:focus\:ring-blue-800:focus {
--tw-ring-opacity: 1;
--tw-ring-color: rgb(30 64 175 / var(--tw-ring-opacity));
}
} }
@media (min-width: 768px) { @media (min-width: 768px) {

11
crates/app/src/db.rs Normal file
View File

@ -0,0 +1,11 @@
use migration::{Migrator, MigratorTrait};
use sea_orm::{Database, DatabaseConnection, DbErr};
use std::env;
pub async fn get_connection() -> Result<DatabaseConnection, DbErr> {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let db_connection = Database::connect(database_url).await?;
Migrator::up(&db_connection, None).await?;
Ok(db_connection)
}

View File

@ -1,9 +1,15 @@
use std::path::Path; use std::path::PathBuf;
use axum::http::{StatusCode, Uri}; use axum::http::{StatusCode, Uri};
use axum_htmx::AutoVaryLayer; use axum_htmx::AutoVaryLayer;
use sea_orm::DatabaseConnection;
use thiserror::Error;
use tower_http::services::ServeDir; use tower_http::services::ServeDir;
use ::utils::config::{load_config, ConfigError};
pub mod db;
mod menu; mod menu;
mod pages; mod pages;
@ -11,11 +17,31 @@ async fn fallback(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("No route for {uri}")) (StatusCode::NOT_FOUND, format!("No route for {uri}"))
} }
pub fn get_router(assets_path: &Path) -> axum::Router { #[derive(Error, Debug)]
pub enum InitError {
#[error(transparent)]
ConfigError(#[from] ConfigError),
}
pub fn init() -> Result<(), InitError> {
load_config(None)?;
Ok(())
}
#[derive(Clone)]
pub struct AppState {
db_connection: DatabaseConnection,
}
pub async fn get_router(assets_path: PathBuf) -> axum::Router<()> {
let db_connection = db::get_connection().await.unwrap();
let state: AppState = AppState { db_connection };
axum::Router::new() axum::Router::new()
.nest_service("/assets", ServeDir::new(assets_path)) .nest_service("/assets", ServeDir::new(assets_path))
.merge(pages::get_routes()) .merge(pages::get_routes())
.fallback(fallback) .fallback(fallback)
.with_state(state)
// The AutoVaryLayer is used to avoid cache issues with htmx (cf: https://github.com/robertwayne/axum-htmx?tab=readme-ov-file#auto-caching-management) // The AutoVaryLayer is used to avoid cache issues with htmx (cf: https://github.com/robertwayne/axum-htmx?tab=readme-ov-file#auto-caching-management)
.layer(AutoVaryLayer) .layer(AutoVaryLayer)
} }

View File

@ -10,7 +10,7 @@ use tokio::net::TcpListener;
use tower_livereload::predicate::Predicate; use tower_livereload::predicate::Predicate;
use tower_livereload::LiveReloadLayer; use tower_livereload::LiveReloadLayer;
use ::app::get_router; use ::app::{get_router, init, InitError};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum AppError { pub enum AppError {
@ -20,6 +20,10 @@ pub enum AppError {
NotifyWatcher(#[from] notify::Error), NotifyWatcher(#[from] notify::Error),
#[error("Missing environment variable {var}")] #[error("Missing environment variable {var}")]
MissingEnvVar { var: &'static str }, MissingEnvVar { var: &'static str },
#[error("Error with the database connection")]
DatabaseConnection(#[from] sea_orm::DbErr),
#[error("Error while initialising the app")]
Initialisation(#[from] InitError),
} }
/// Nous filtrons les requêtes de `htmx` pour ne pas inclure le script _JS_ qui gère le rechargement /// Nous filtrons les requêtes de `htmx` pour ne pas inclure le script _JS_ qui gère le rechargement
@ -61,6 +65,8 @@ fn get_livereload_layer(
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), AppError> { async fn main() -> Result<(), AppError> {
init()?;
let manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| AppError::MissingEnvVar { let manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| AppError::MissingEnvVar {
var: "CARGO_MANIFEST_DIR", var: "CARGO_MANIFEST_DIR",
})?; })?;
@ -72,7 +78,7 @@ async fn main() -> Result<(), AppError> {
let livereload_layer = let livereload_layer =
get_livereload_layer(templates_paths).map_err(AppError::NotifyWatcher)?; get_livereload_layer(templates_paths).map_err(AppError::NotifyWatcher)?;
let router = get_router(assets_path.as_path()).layer(livereload_layer); let router = get_router(assets_path).await.layer(livereload_layer);
let listener: TcpListener = get_tcp_listener().await.map_err(AppError::TCPListener)?; let listener: TcpListener = get_tcp_listener().await.map_err(AppError::TCPListener)?;
let local_addr = listener.local_addr().map_err(AppError::TCPListener)?; let local_addr = listener.local_addr().map_err(AppError::TCPListener)?;

View File

@ -19,5 +19,10 @@ pub fn get_menu_items() -> Vec<MenuItem> {
label: "CPS".to_string(), label: "CPS".to_string(),
href: "/cps".to_string(), href: "/cps".to_string(),
}, },
MenuItem {
id: "debug".to_string(),
label: "DEBUG".to_string(),
href: "/debug".to_string(),
},
] ]
} }

View File

@ -0,0 +1,72 @@
{% extends "base.html" %}
{% import "navbar/navbar.html" as navbar -%}
{% block title %}Pharma Libre - Debug{% endblock %}
{% block body %}
{% call navbar::navbar(current="debug") %}
<div class="py-10">
<header id="page-header">
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<h1 id="page-title" class="text-3xl font-bold leading-tight tracking-tight text-gray-900">
DEBUG
</h1>
</div>
</header>
<main id="page-main">
<div class="mx-auto max-w-7xl py-12 sm:px-6 lg:px-8">
<div class="mx-auto max-w-3xl">
<div
class="w-full p-6 bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md dark:bg-gray-800 dark:border-gray-700 sm:p-8">
<h1
class="mb-1 text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Base de données
</h1>
<p class="font-light text-gray-500 dark:text-gray-400 mb-5">
Données extraites de la base de donnée à des fins de debug
</p>
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">
ID
</th>
<th scope="col" class="px-6 py-3">
Value
</th>
</tr>
</thead>
<tbody>
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
db_ping_status
</th>
<td class="px-6 py-4">
{{ db_ping_status }}
</td>
</tr>
<tr class="bg-white border-b dark:bg-gray-800 dark:border-gray-700">
<th scope="row"
class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">
debug_entries_count
</th>
<td class="px-6 py-4">
{{ debug_entries_count }}
</td>
</tr>
</tbody>
</table>
<div class="mt-4 space-y-4 lg:mt-5 md:space-y-5">
<button type="button"
class="w-full text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
hx-trigger="click" hx-post="/debug/add_random" hx-swap="none">
Add random debug entry
</button>
</div>
</div>
</div>
</div>
</main>
</div>
{% endblock %}

View File

@ -0,0 +1,48 @@
use askama_axum::Template;
use axum::{extract::State, routing};
use ::entity::{debug, debug::Entity as DebugEntity};
use axum_htmx::HxRequest;
use sea_orm::*;
use crate::AppState;
async fn get_debug_entries(db: &DatabaseConnection) -> Result<Vec<debug::Model>, DbErr> {
DebugEntity::find().all(db).await
}
async fn add_random_debug_entry(State(AppState { db_connection }): State<AppState>) {
let random_entry = debug::ActiveModel {
title: Set("Random title".to_string()),
text: Set("Random text".to_string()),
..Default::default()
};
random_entry.insert(&db_connection).await.unwrap();
}
#[derive(Template)]
#[template(path = "debug.html")]
struct GetDebugTemplate {
hx_request: bool,
db_ping_status: bool,
debug_entries_count: usize,
}
async fn debug(
HxRequest(hx_request): HxRequest,
State(AppState { db_connection }): State<AppState>,
) -> GetDebugTemplate {
let db_ping_status = db_connection.ping().await.is_ok();
let debug_entries = get_debug_entries(&db_connection).await.unwrap();
GetDebugTemplate {
hx_request,
db_ping_status,
debug_entries_count: debug_entries.len(),
}
}
pub fn get_routes() -> axum::Router<crate::AppState> {
axum::Router::new()
.route("/", routing::get(debug))
.route("/add_random", routing::post(add_random_debug_entry))
}

View File

@ -1,10 +1,14 @@
use axum::{routing, Router}; use axum::{routing, Router};
use crate::AppState;
mod cps; mod cps;
mod debug;
mod home; mod home;
pub fn get_routes() -> Router { pub fn get_routes() -> Router<AppState> {
Router::new() Router::new()
.route("/", routing::get(home::home)) .route("/", routing::get(home::home))
.route("/cps", routing::get(cps::cps)) .route("/cps", routing::get(cps::cps))
.nest("/debug", debug::get_routes())
} }

View File

@ -13,13 +13,13 @@ crate-type = ["lib", "cdylib", "staticlib"]
tauri-build = { version = "2.0.0-beta", features = [] } tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies] [dependencies]
axum = "0.7.5" axum.workspace = true
bytes = "1.6.1"
http = "1.1.0"
tauri = { version = "2.0.0-beta", features = [] } tauri = { version = "2.0.0-beta", features = [] }
thiserror.workspace = true
tower = "0.4.13" tower = "0.4.13"
tokio = "1.39.1" tokio.workspace = true
app = { path = "../app" } app = { path = "../app" }
http = "1.1.0"
bytes = "1.6.1"
thiserror = "1.0.63"

View File

@ -10,6 +10,8 @@ use thiserror::Error;
use tokio::sync::{Mutex, MutexGuard}; use tokio::sync::{Mutex, MutexGuard};
use tower::{Service, ServiceExt}; use tower::{Service, ServiceExt};
use ::app::init;
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum DesktopError { pub enum DesktopError {
#[error("Axum error:\n{0}")] #[error("Axum error:\n{0}")]
@ -46,6 +48,8 @@ async fn process_tauri_request(
#[cfg_attr(mobile, tauri::mobile_entry_point)] #[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() { pub fn run() {
init().expect("Failed to initialize the application");
tauri::Builder::default() tauri::Builder::default()
.setup(|app| { .setup(|app| {
let assets_path: PathBuf = app let assets_path: PathBuf = app
@ -55,7 +59,7 @@ pub fn run() {
// Adds Axum router to application state // Adds Axum router to application state
// This makes it so we can retrieve it from any app instance (see bellow) // This makes it so we can retrieve it from any app instance (see bellow)
let router = Arc::new(Mutex::new(app::get_router(&assets_path))); let router = Arc::new(Mutex::new(app::get_router(assets_path)));
app.manage(router); app.manage(router);

View File

@ -4,10 +4,11 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0" anyhow.workspace = true
libc = "0.2" libc = "0.2"
thiserror = "1.0" thiserror.workspace = true
utils = { path = "../utils" } utils = { path = "../utils" }
[build-dependencies] [build-dependencies]
dotenv = "0.15" dotenv.workspace = true

View File

@ -9,7 +9,7 @@ use thiserror::Error;
use crate::cps::lire_carte; use crate::cps::lire_carte;
use crate::libssv::{SSV_InitLIB2, SSV_LireConfig}; use crate::libssv::{SSV_InitLIB2, SSV_LireConfig};
use ::utils::config::load_config; use ::utils::config::{load_config, ConfigError};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum SSVDemoError { pub enum SSVDemoError {
@ -17,6 +17,8 @@ pub enum SSVDemoError {
CartePSReading(#[from] crate::cps::CartePSError), CartePSReading(#[from] crate::cps::CartePSError),
#[error(transparent)] #[error(transparent)]
SSVLibErrorCode(#[from] crate::libssv::LibSSVError), SSVLibErrorCode(#[from] crate::libssv::LibSSVError),
#[error(transparent)]
Configuration(#[from] ConfigError),
} }
fn ssv_init_lib_2() -> Result<(), SSVDemoError> { fn ssv_init_lib_2() -> Result<(), SSVDemoError> {
@ -69,7 +71,7 @@ pub fn demo() -> Result<(), SSVDemoError> {
println!("------- Demo for the SSV library --------"); println!("------- Demo for the SSV library --------");
load_config()?; load_config(None)?;
ssv_init_lib_2()?; ssv_init_lib_2()?;

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0" anyhow.workspace = true
directories = "5.0" directories = "5.0"
dotenv = "0.15" dotenv.workspace = true
thiserror.workspace = true

View File

@ -1,11 +1,23 @@
use std::{env, path::PathBuf}; use std::{env, path::PathBuf, sync::atomic::AtomicBool};
use anyhow::{bail, Context, Result};
use directories::ProjectDirs; use directories::ProjectDirs;
use dotenv::from_path; use dotenv::from_path;
use thiserror::Error;
const CONFIG_FILE_NAME: &str = ".env"; const CONFIG_FILE_NAME: &str = ".env";
static CONFIG_INITIALIZED: AtomicBool = AtomicBool::new(false);
#[derive(Debug, Error)]
pub enum ConfigError {
#[error("No config file {0} found in the following directories: {1:#?}")]
ConfigFileNotFound(String, Vec<PathBuf>),
#[error("Failed to load config file: {0}")]
LoadConfigError(#[from] dotenv::Error),
#[error("Environment variable error: {0}")]
EnvVarError(#[from] std::env::VarError),
}
pub fn get_config_dirs() -> Vec<PathBuf> { pub fn get_config_dirs() -> Vec<PathBuf> {
let mut config_dirs = vec![ let mut config_dirs = vec![
PathBuf::from(""), // Current directory PathBuf::from(""), // Current directory
@ -19,7 +31,7 @@ pub fn get_config_dirs() -> Vec<PathBuf> {
config_dirs config_dirs
} }
pub fn get_config_files() -> Result<Vec<PathBuf>> { pub fn get_config_files() -> Result<Vec<PathBuf>, ConfigError> {
let config_dirs = get_config_dirs(); let config_dirs = get_config_dirs();
let mut config_files = Vec::new(); let mut config_files = Vec::new();
for config_dir in config_dirs.iter() { for config_dir in config_dirs.iter() {
@ -29,14 +41,20 @@ pub fn get_config_files() -> Result<Vec<PathBuf>> {
} }
} }
if config_files.is_empty() { if config_files.is_empty() {
bail!( return Err(ConfigError::ConfigFileNotFound(
"No config file {CONFIG_FILE_NAME} found in the following directories: {config_dirs:#?}" CONFIG_FILE_NAME.to_string(),
); config_dirs,
));
} }
Ok(config_files) Ok(config_files)
} }
pub fn load_config() -> Result<()> { pub fn load_config(force: Option<bool>) -> Result<(), ConfigError> {
let force = force.unwrap_or(false);
if CONFIG_INITIALIZED.load(std::sync::atomic::Ordering::Relaxed) && force {
println!("DEBUG: Config already initialized, skipping");
return Ok(());
}
let config_files = get_config_files()?; let config_files = get_config_files()?;
// Load the first config file found // Load the first config file found
// TODO: add a verbose log to list all config files found // TODO: add a verbose log to list all config files found
@ -44,5 +62,7 @@ pub fn load_config() -> Result<()> {
"DEBUG: Config files found (1st loaded): {:#?}", "DEBUG: Config files found (1st loaded): {:#?}",
config_files config_files
); );
from_path(config_files[0].as_path()).context("Failed to load config file") from_path(config_files[0].as_path()).map_err(ConfigError::LoadConfigError)?;
CONFIG_INITIALIZED.store(true, std::sync::atomic::Ordering::Relaxed);
Ok(())
} }

14
entity/Cargo.toml Normal file
View File

@ -0,0 +1,14 @@
[package]
name = "entity"
version = "0.1.0"
edition = "2021"
[lib]
name = "entity"
path = "src/lib.rs"
[dependencies]
sea-orm.workspace = true
[dev-dependencies]
sea-orm-cli.workspace = true

View File

@ -0,0 +1,17 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
#[sea_orm(table_name = "debug")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub title: String,
pub text: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,5 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
pub mod prelude;
pub mod debug;

View File

@ -0,0 +1,3 @@
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.0.1
pub use super::debug::Entity as Debug;

2
entity/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
mod entities;
pub use entities::*;

24
migration/Cargo.toml Normal file
View File

@ -0,0 +1,24 @@
[package]
name = "migration"
version = "0.1.0"
edition = "2021"
publish = false
[lib]
name = "migration"
path = "src/lib.rs"
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
[dev-dependencies]
sea-orm-cli.workspace = true
[dependencies.sea-orm-migration]
version = "1.0.0"
features = [
# `ASYNC_RUNTIME` and `DATABASE_DRIVER` are required to run migration using the cli
# They must be the same as the features in the `sea-orm` dependency in the `app` crate
"sqlx-sqlite", # `DATABASE_DRIVER` feature
"runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
]

41
migration/README.md Normal file
View 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
migration/src/lib.rs Normal file
View File

@ -0,0 +1,12 @@
pub use sea_orm_migration::prelude::*;
mod m20220101_000001_create_debug_table;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![Box::new(m20220101_000001_create_debug_table::Migration)]
}
}

View File

@ -0,0 +1,35 @@
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> {
manager
.create_table(
Table::create()
.table(Debug::Table)
.if_not_exists()
.col(pk_auto(Debug::Id))
.col(string(Debug::Title))
.col(string(Debug::Text))
.to_owned(),
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop().table(Debug::Table).to_owned())
.await
}
}
#[derive(DeriveIden)]
enum Debug {
Table,
Id,
Title,
Text,
}

6
migration/src/main.rs Normal file
View File

@ -0,0 +1,6 @@
use sea_orm_migration::prelude::*;
#[async_std::main]
async fn main() {
cli::run_cli(migration::Migrator).await;
}