Compare commits

..

No commits in common. "b94dd5541881622337406431f816c95eb4ef9e53" and "ca2a0ace71046afe614e6407a5559d335a84c05d" have entirely different histories.

26 changed files with 646 additions and 863 deletions

974
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,8 @@
[workspace]
resolver = "2"
members = [
"crates/backend",
"crates/desktop",
"crates/app",
"crates/sesam-vitale",
"crates/desktop",
"crates/utils",
]

View File

@ -2,14 +2,12 @@
Logiciel de Pharmacie libre et open-source.
## Modules applicatifs
## Crates
- `crates`: Dossier racine des modules Rust
- `crates/backend`: Serveur backend propulsé par Axum, exposant une API REST
- `crates/desktop`: Client desktop propulsé par Tauri, exposant le `frontend`
- `crates/sesam-vitale`: Bibliothèque de gestion des services SESAM-Vitale (Lecture des cartes CPS et Vitale, téléservices ...)
- `crates/utils`: Bibliothèque de fonctions utilitaires
- `frontend`: Interface web du logiciel, propulsée par Nuxt.js
- `app`: Interface du logiciel, servie par un serveur web propulsé par Axum. Utilisable en mode endpoint ou encapsulé dans le client `desktop`
- `desktop`: Client desktop propulsé par Tauri, encapsulant le serveur web `app`
- `sesam-vitale`: Bibliothèque de gestion des services SESAM-Vitale (Lecture des cartes CPS et Vitale, téléservices ...)
- `utils`: Bibliothèque de fonctions utilitaires
## Installation
@ -28,49 +26,57 @@ Des exemples de fichiers de configuration sont disponibles à la racine du proje
### Pré-requis
#### Frontend (Nuxt + Typescript)
Le frontend est propulsé par Nuxt.js, un framework TypeScript pour Vue.js. Pour le développement, il est nécessaire d'installer les dépendances suivantes :
- [Bun](https://bun.sh/docs/installation), un gestionnaire de paquets, équivalent à `npm` en plus performant
#### Tauri CLI
TODO: Tauri CLI, réellement nécessaire ?
La CLI Tauri est nécessaire au lancement du client `desktop`. Elle peut être installée via Cargo :
```bash
cargo install tauri-cli --version "^2.0.0-rc"
cargo install tauri-cli --version "^2.0.0-beta"
```
#### Tailwindcss CLI
Le CLI Tailwindcss est nécessaire pour la génération du fichier `crates/app/assets/css/style.css`.
La documentation d'installation est disponible sur le site officiel de Tailwindcss : https://tailwindcss.com/blog/standalone-cli
La version actuellement utilisée est la [`v3.4.7`](https://github.com/tailwindlabs/tailwindcss/releases/tag/v3.4.7)
#### 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`.
#### Backend Hot-reload
Voir le [README](crates/backend/README.md) de la crate `backend` pour les prérequis de développement du serveur backend.
### Lancement
Pour lancer l'application en mode développement, il est nécessaire d'exécuter plusieurs composants simultanément :
Le logiciel dans sa globalité peut être lancé via la commande suivante :
```bash
# Lancement du serveur backend
systemfd --no-pid -s http::3030 -- cargo watch -x 'run --bin backend'
```
```bash
# Lancement de l'interface utilisateur (frontend ou desktop)
# - frontend (serveur web, accessible via navigateur)
bun run --cwd frontend/ dev
# - desktop (client desktop, basé sur Tauri)
cargo tauri dev
```
/!\ Attention, le lancement du client `desktop` ne génère pas le fichier `crates/app/assets/css/style.css` automatiquement pour le moment. En cas de modification des interfaces web, il est donc nécessaire de procéder à sa génération comme indiqué dans le [README](crates/app/README.md) de la crate `app`.
Si vous souhaitez lancer les composants séparément, les indications de lancement sont disponibles dans les README des différents crates.
- [app](crates/app/README.md)
- [sesam-vitale](crates/sesam-vitale/README.md)
## Rechargement automatique
Pour permettre de développer plus rapidement, il existe une librairie qui recompile automatiquement nos modifications en cours : [`cargo-watch`](https://github.com/watchexec/cargo-watch) permet de relancer une commande `cargo` lorsqu'un fichier est modifié (example: `cargo run` --> `cargo watch -x run`).
Voici la commande pour l'installer dans un _package_ :
```bash
cargo add cargo-watch --dev --package app
```
Le fichier [`.ignore`](./ignore) permet d'ignorer certains fichiers pour éviter de relancer la recompilation inutilement.
⚠️ La librairie n'est pas compatible avec _Windows 7_ et les versions antérieurs de _Windows_.
## Build
Pour packager le client `desktop`, il est nécessaire de faire appel à la CLI Tauri, qui se charge de gérer le build du `frontend` et son intégration au bundle :
Packager le client desktop
```bash
cargo tauri build

View File

@ -1,4 +1,4 @@
use std::path::PathBuf;
use std::path::Path;
use axum::http::{StatusCode, Uri};
use axum_htmx::AutoVaryLayer;
@ -11,7 +11,7 @@ async fn fallback(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
}
pub async fn get_router(assets_path: PathBuf) -> axum::Router<()> {
pub fn get_router(assets_path: &Path) -> axum::Router {
axum::Router::new()
.nest_service("/assets", ServeDir::new(assets_path))
.merge(pages::get_routes())

View File

@ -72,7 +72,7 @@ async fn main() -> Result<(), AppError> {
let livereload_layer =
get_livereload_layer(templates_paths).map_err(AppError::NotifyWatcher)?;
let router = get_router(assets_path).await.layer(livereload_layer);
let router = get_router(assets_path.as_path()).layer(livereload_layer);
let listener: TcpListener = get_tcp_listener().await.map_err(AppError::TCPListener)?;
let local_addr = listener.local_addr().map_err(AppError::TCPListener)?;

View File

@ -1,14 +0,0 @@
[package]
name = "backend"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.89"
axum = "0.7.6"
listenfd = "1.0.1"
tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
[dev-dependencies]
cargo-watch = "8.5.2"
systemfd = "0.4.3"

View File

@ -1,19 +0,0 @@
# Backend
Ceci est un serveur backend, basé sur axum, et permettant d'offrir une gestion centralisée des accès aux données.
## Prérequis
En développement, le mécanisme de hot-reload nécessite de disposer de `cargo-watch` et `systemfd`. Pour les installer, exécutez la commande suivante :
```bash
cargo install cargo-watch systemfd
```
## Développement
Pour lancer le serveur en mode développement, exécutez la commande suivante :
```bash
systemfd --no-pid -s http::3030 -- cargo watch -x 'run --bin backend'
```

View File

@ -1,37 +0,0 @@
use anyhow::Error as AnyError;
use axum::http::{StatusCode, Uri};
use axum::response::{IntoResponse, Response};
use axum::{routing::get, Router};
pub fn get_router() -> Router {
Router::new()
.route("/", get(|| async { "Hello, world!" }))
.fallback(fallback)
}
async fn fallback(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
}
struct AppError(AnyError);
// To automatically convert `AppError` into a response
impl IntoResponse for AppError {
fn into_response(self) -> Response {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Internal Server Error: {}", self.0),
)
.into_response()
}
}
// To automatically convert `AnyError` into `AppError`
impl<E> From<E> for AppError
where
E: Into<AnyError>,
{
fn from(err: E) -> Self {
Self(err.into())
}
}

View File

@ -1,24 +0,0 @@
use listenfd::ListenFd;
use tokio::net::TcpListener;
use backend::get_router;
#[tokio::main]
async fn main() {
let app = get_router();
let mut listenfd = ListenFd::from_env();
let listener = match listenfd.take_tcp_listener(0).unwrap() {
// if we are given a tcp listener on listen fd 0, we use that one
Some(listener) => {
listener.set_nonblocking(true).unwrap();
TcpListener::from_std(listener).unwrap()
}
// otherwise fall back to local listening
None => TcpListener::bind("0.0.0.0:8080").await.unwrap(),
};
println!("Listening on {}", listener.local_addr().unwrap());
axum::serve(listener, app).await.unwrap();
}

View File

@ -10,11 +10,16 @@ name = "desktop_lib"
crate-type = ["lib", "cdylib", "staticlib"]
[build-dependencies]
tauri-build = { version = "2.0.0-rc", features = [] }
tauri-build = { version = "2.0.0-beta", features = [] }
[dependencies]
tauri = { version = "2.0.0-rc", features = [] }
tauri-plugin-shell = "2.0.0-rc"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
axum = "0.7.5"
tauri = { version = "2.0.0-beta", features = [] }
tower = "0.4.13"
tokio = "1.39.1"
app = { path = "../app" }
http = "1.1.0"
bytes = "1.6.1"
thiserror = "1.0.63"

View File

@ -1,10 +0,0 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
"shell:allow-open"
]
}

View File

@ -1,14 +1,88 @@
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
use axum::body::{to_bytes, Body};
use axum::Router;
use bytes::Bytes;
use http::{request, response, Request, Response};
use std::path::PathBuf;
use std::sync::Arc;
use tauri::path::BaseDirectory;
use tauri::Manager;
use thiserror::Error;
use tokio::sync::{Mutex, MutexGuard};
use tower::{Service, ServiceExt};
#[derive(Error, Debug)]
pub enum DesktopError {
#[error("Axum error:\n{0}")]
Axum(#[from] axum::Error),
#[error("Infallible error")]
Infallible(#[from] std::convert::Infallible),
}
/// Process requests sent to Tauri (with the `axum://` protocol) and handle them with Axum
/// When an error occurs, this function is expected to panic, which should result in a 500 error
/// being sent to the client, so we let the client handle the error recovering
async fn process_tauri_request(
tauri_request: Request<Vec<u8>>,
mut router: MutexGuard<'_, Router>,
) -> Result<Response<Vec<u8>>, DesktopError> {
let (parts, body): (request::Parts, Vec<u8>) = tauri_request.into_parts();
let axum_request: Request<Body> = Request::from_parts(parts, body.into());
let axum_response: Response<Body> = router
.as_service()
.ready()
.await
.map_err(DesktopError::Infallible)?
.call(axum_request)
.await
.map_err(DesktopError::Infallible)?;
let (parts, body): (response::Parts, Body) = axum_response.into_parts();
let body: Bytes = to_bytes(body, usize::MAX).await?;
let tauri_response: Response<Vec<u8>> = Response::from_parts(parts, body.into());
Ok(tauri_response)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
.setup(|app| {
let assets_path: PathBuf = app
.path()
.resolve("assets", BaseDirectory::Resource)
.expect("Assets path should be resolvable");
// Adds Axum router to application state
// 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)));
app.manage(router);
Ok(())
})
.register_asynchronous_uri_scheme_protocol("axum", move |app, request, responder| {
// Retrieve the router from the application state and clone it for the async block
let router = Arc::clone(&app.state::<Arc<Mutex<axum::Router>>>());
// Spawn a new async task to process the request
tauri::async_runtime::spawn(async move {
let router = router.lock().await;
match process_tauri_request(request, router).await {
Ok(response) => responder.respond(response),
Err(err) => {
let body = format!("Failed to process an axum:// request:\n{}", err);
responder.respond(
http::Response::builder()
.status(http::StatusCode::BAD_REQUEST)
.header(http::header::CONTENT_TYPE, "text/plain")
.body::<Vec<u8>>(body.into())
.expect("BAD_REQUEST response should be valid"),
)
}
}
});
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@ -1,24 +1,20 @@
{
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
"productName": "Chrys4lide LGO",
"productName": "Logiciel Pharma",
"version": "0.0.1",
"identifier": "org.p4pillon.chrys4lide.lgo",
"identifier": "org.p4pillon.pharma.desktop",
"build": {
"beforeDevCommand": {
"cwd": "../../frontend",
"script": "bun run dev"
"cwd": "../app",
"script": "cargo run"
},
"devUrl": "http://localhost:1420",
"beforeBuildCommand": {
"cwd": "../../frontend",
"script": "bun run generate"
},
"frontendDist": "../../frontend/dist"
"devUrl": "http://localhost:3000",
"frontendDist": "axum://place.holder/"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "Chrys4lide | LG0",
"title": "Logiciel Pharma",
"width": 800,
"height": 600
}
@ -29,6 +25,9 @@
},
"bundle": {
"active": true,
"resources": {
"../app/assets/": "./assets/"
},
"targets": "all",
"icon": [
"icons/32x32.png",
@ -38,4 +37,5 @@
"icons/icon.ico"
]
}
}
}

View File

@ -17,8 +17,6 @@ pub enum SSVDemoError {
CartePSReading(#[from] crate::cps::CartePSError),
#[error(transparent)]
SSVLibErrorCode(#[from] crate::libssv::LibSSVError),
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
}
fn ssv_init_lib_2() -> Result<(), SSVDemoError> {

24
frontend/.gitignore vendored
View File

@ -1,24 +0,0 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

View File

@ -1,77 +0,0 @@
# Nuxt 3 Minimal Starter
TODO : Faire un vrai README pour `frontend` (Nuxt 3)
Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
## Setup
Make sure to install the dependencies:
```bash
# npm
npm install
# pnpm
pnpm install
# yarn
yarn install
# bun
bun install
```
## Development Server
Start the development server on `http://localhost:3000`:
```bash
# npm
npm run dev
# pnpm
pnpm run dev
# yarn
yarn dev
# bun
bun run dev
```
## Production
Build the application for production:
```bash
# npm
npm run build
# pnpm
pnpm run build
# yarn
yarn build
# bun
bun run build
```
Locally preview production build:
```bash
# npm
npm run preview
# pnpm
pnpm run preview
# yarn
yarn preview
# bun
bun run preview
```
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

View File

@ -1,6 +0,0 @@
<template>
<div>
<NuxtRouteAnnouncer />
<NuxtWelcome />
</div>
</template>

View File

@ -1,43 +0,0 @@
<!--
This component is used to show a loading spinner when the SPA is loading.
Source: https://github.com/barelyhuman/snips/blob/dev/pages/css-loader.md
-->
<div class="loader"></div>
<style>
.loader {
display: block;
position: fixed;
z-index: 1031;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 18px;
height: 18px;
box-sizing: border-box;
border: solid 2px transparent;
border-top-color: #000;
border-left-color: #000;
border-bottom-color: #efefef;
border-right-color: #efefef;
border-radius: 50%;
-webkit-animation: loader 400ms linear infinite;
animation: loader 400ms linear infinite;
}
\@-webkit-keyframes loader {
0% {
-webkit-transform: translate(-50%, -50%) rotate(0deg);
}
100% {
-webkit-transform: translate(-50%, -50%) rotate(360deg);
}
}
\@keyframes loader {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
transform: translate(-50%, -50%) rotate(360deg);
}
}
</style>

Binary file not shown.

View File

@ -1,39 +0,0 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2024-04-03',
// Enables the development server to be discoverable by other devices for mobile development
devServer: { host: '0.0.0.0', port: 1420 },
devtools: { enabled: true },
modules: [
'@nuxtjs/tailwindcss',
'@nuxtjs/color-mode',
],
// Disable SSR for Tauri
ssr: false,
vite: {
// Better support for Tauri CLI output
clearScreen: false,
// Enable environment variables
// Additional environment variables can be found at
// https://v2.tauri.app/reference/environment-variables/
envPrefix: ['VITE_', 'TAURI_'],
server: {
// Tauri requires a consistent port
strictPort: true,
hmr: {
// Use websocket for mobile hot reloading
protocol: 'ws',
// Make sure it's available on the network
host: '0.0.0.0',
// Use a specific port for hmr
port: 5183,
},
},
},
colorMode: {
// Add `data-theme` attribute to the `html` tag, allowing DaisyUI to handle dark mode automatically
dataValue: 'theme',
// Remove the default `-mode` suffix from the class name, letting have `dark` and `light` as class names, for DaisyUI compatibility
classSuffix: '',
},
})

View File

@ -1,22 +0,0 @@
{
"name": "nuxt-app",
"private": true,
"type": "module",
"scripts": {
"build": "nuxi build",
"dev": "nuxi dev",
"generate": "nuxi generate",
"preview": "nuxi preview",
"postinstall": "nuxi prepare"
},
"dependencies": {
"@nuxtjs/color-mode": "^3.5.1",
"daisyui": "^4.12.10",
"nuxt": "^3.13.0",
"vue": "latest",
"vue-router": "latest"
},
"devDependencies": {
"@nuxtjs/tailwindcss": "^6.12.1"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -1 +0,0 @@

View File

@ -1,3 +0,0 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

View File

@ -1,7 +0,0 @@
import type { Config } from 'tailwindcss'
export default <Partial<Config>>{
plugins: [
require('daisyui'),
],
}

View File

@ -1,4 +0,0 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}