Setup de SeaORM + SQLite comme base de données #64

Merged
florian_briand merged 12 commits from feat/9_setup_db into main 2024-10-02 12:04:04 +02:00
26 changed files with 0 additions and 1982 deletions
Showing only changes of commit 0aa0aebbad - Show all commits

View File

@ -1,4 +0,0 @@
/target
# Tailwind CSS CLI
tailwindcss

View File

@ -1,33 +0,0 @@
[package]
name = "app"
version = "0.1.0"
edition = "2021"
[dependencies]
askama = "0.12.1"
askama_axum = "0.4.0"
axum.workspace = true
axum-htmx = { version = "0.6", features = ["auto-vary"] }
futures = "0.3.30"
listenfd = "1.0.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"] }
thiserror.workspace = true
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
tower-http = { version = "0.5.2", features = ["fs"] }
tower-livereload = "0.9.3"
entity = { path = "../../entity" }
migration = { path = "../../migration" }
utils = { path = "../utils" }
[dev-dependencies]
cargo-watch = "8.5.1"
systemfd = "0.4.0"
sea-orm-cli.workspace = true

View File

@ -1,44 +0,0 @@
## Pré-requis
- 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
- Lancer tailwindcss en mode watch dans un terminal :
```bash
./tailwindcss -i css/input.css -o assets/css/style.css --watch
```
- Lancer le serveur web dans un autre terminal :
```bash
cargo run --bin app
```
## Rechargement automatique (_auto-reload_)
Pour le projet `app`, nous utilisons en plus de `cargo-watch` ses librairies :
- [`systemfd`](https://github.com/mitsuhiko/systemfd) permet de redémarrer un serveur sans interrompre les connexions en cours, il transmet le descripteur de fichier du socket à une nouvelle instance du serveur (exemple: `cargo watch -x run` --> `systemfd --no-pid -s http::3000 -- cargo watch -x run`). Si le port est déjà pris il en prendra un autre.
- [`listenfd`](https://github.com/mitsuhiko/listenfd) permet, côté _Rust_, de démarrer un serveur en utilisant des connexions déjà ouvertes.
Pour notre application voici la commande à lancer :
```bash
systemfd --no-pid -s http::3000 -- cargo watch -x 'run --bin app'
```
## Chargement à chaud (_livereload_)
Pour que notre navigateur rafraîchisse automatique notre page lorsque le serveur a été recompilé, nous utilisons la librairie [`tower-livereload`](https://github.com/leotaku/tower-livereload).
A chaque changement, que ça soit sur du code en _Rust_, _HTML_, _CSS_ ou _JS_ alors le navigateur va recharger entièrement la page.
En Rust, il n'existe pas encore d'outil de _Hot Reload_ complet et intégré comme on en trouve dans d'autres environnements de développement web, comme pour _Node.js_.

View File

@ -1,6 +0,0 @@
[general]
# Directories to search for templates, relative to the crate root.
dirs = [
"src/pages",
"src/components",
]

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -1,23 +0,0 @@
{% if hx_request %}
<title>{% block title %}{{ title }}{% endblock %}</title>
{% block body %}{% endblock %}
{% else %}
<!doctype html>
<html lang="fr" class="h-full">
<head>
<title>{% block title %}{{ title }}{% endblock %}</title>
<script src="/assets/js/htmx@2.0.1.min.js"></script>
<script src="/assets/js/alpinejs@3.14.1.min.js" defer></script>
<script src="/assets/js/flowbite@2.5.1.min.js"></script>
<link href="/assets/css/style.css" rel="stylesheet">
{% block head %}{% endblock %}
</head>
<body class="h-full">
<div class="min-h-full">
{% block body %}{% endblock %}
</div>
</body>
</html>
{% endif %}

View File

@ -1,18 +0,0 @@
{% set selected = item.id == current %}
<li>
<a
href="{{ item.href }}"
{% if selected -%}
class="block py-2 px-3 text-white bg-blue-700 rounded md:bg-transparent md:text-blue-700 md:p-0 md:dark:text-blue-500"
aria-current="page"
{% else -%}
class="block py-2 px-3 text-gray-900 rounded hover:bg-gray-100 md:hover:bg-transparent md:hover:text-blue-700 md:p-0 dark:text-white md:dark:hover:text-blue-500 dark:hover:bg-gray-700 dark:hover:text-white md:dark:hover:bg-transparent dark:border-gray-700"
{% endif -%}
hx-get="{{ item.href }}"
hx-push-url="true"
hx-swap="outerHTML"
hx-select-oob="#menu-items,#page-header,#page-main"
>
{{ item.label }}
</a>
</li>

View File

@ -1,50 +0,0 @@
{% macro navbar(current) %}
{% let items=crate::menu::get_menu_items() %}
<nav class="bg-white border-gray-200 dark:bg-gray-900">
<div class="max-w-screen-xl flex flex-wrap items-center justify-between mx-auto p-4">
<a href="/" class="flex items-center space-x-3 rtl:space-x-reverse">
<img src="https://flowbite.com/docs/images/logo.svg" class="h-8" alt="Flowbite Logo" />
<span class="self-center text-2xl font-semibold whitespace-nowrap dark:text-white">Krys4lide</span>
</a>
<div class="flex items-center md:order-2 space-x-3 md:space-x-0 rtl:space-x-reverse">
<button type="button" class="flex text-sm bg-gray-800 rounded-full md:me-0 focus:ring-4 focus:ring-gray-300 dark:focus:ring-gray-600" id="user-menu-button" aria-expanded="false" data-dropdown-toggle="user-dropdown" data-dropdown-placement="bottom">
<span class="sr-only">Ouvrir le menu de profil</span>
<img class="w-8 h-8 rounded-full" src="https://flowbite.com/docs/images/people/profile-picture-3.jpg" alt="user photo">
</button>
<!-- Dropdown menu -->
<div class="z-50 hidden my-4 text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow dark:bg-gray-700 dark:divide-gray-600" id="user-dropdown">
<div class="px-4 py-3">
<span class="block text-sm text-gray-900 dark:text-white">Bonnie Green</span>
<span class="block text-sm text-gray-500 truncate dark:text-gray-400">name@flowbite.com</span>
</div>
<ul class="py-2" aria-labelledby="user-menu-button">
<li>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Profile</a>
</li>
<li>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Settings</a>
</li>
<li>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white">Sign out</a>
</li>
</ul>
</div>
<button data-collapse-toggle="navbar-user" type="button" class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm text-gray-500 rounded-lg md:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600" aria-controls="navbar-user" aria-expanded="false">
<span class="sr-only">Ouvrir le menu de navigation</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M1 1h15M1 7h15M1 13h15"/>
</svg>
</button>
</div>
<div class="items-center justify-between hidden w-full md:flex md:w-auto md:order-1" id="navbar-user">
<ul id="menu-items" class="flex flex-col font-medium p-4 md:p-0 mt-4 border border-gray-100 rounded-lg bg-gray-50 md:space-x-8 rtl:space-x-reverse md:flex-row md:mt-0 md:border-0 md:bg-white dark:bg-gray-800 md:dark:bg-gray-900 dark:border-gray-700">
{% for item in items %}
{% include "navbar/menu-item.html" %}
{% endfor %}
</ul>
</div>
</div>
</nav>
{% endmacro %}

View File

@ -1,22 +0,0 @@
<div role="status" class="animate-pulse max-w-sm p-4 border border-gray-200 rounded shadow md:p-6 dark:border-gray-700">
<div class="flex items-center justify-center h-48 mb-4 bg-gray-300 rounded dark:bg-gray-700">
<svg class="w-10 h-10 text-gray-200 dark:text-gray-600" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 16 20">
<path d="M14.066 0H7v5a2 2 0 0 1-2 2H0v11a1.97 1.97 0 0 0 1.934 2h12.132A1.97 1.97 0 0 0 16 18V2a1.97 1.97 0 0 0-1.934-2ZM10.5 6a1.5 1.5 0 1 1 0 2.999A1.5 1.5 0 0 1 10.5 6Zm2.221 10.515a1 1 0 0 1-.858.485h-8a1 1 0 0 1-.9-1.43L5.6 10.039a.978.978 0 0 1 .936-.57 1 1 0 0 1 .9.632l1.181 2.981.541-1a.945.945 0 0 1 .883-.522 1 1 0 0 1 .879.529l1.832 3.438a1 1 0 0 1-.031.988Z"/>
<path d="M5 5V.13a2.96 2.96 0 0 0-1.293.749L.879 3.707A2.98 2.98 0 0 0 .13 5H5Z"/>
</svg>
</div>
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
<div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
<div class="flex items-center mt-4">
<svg class="w-10 h-10 me-3 text-gray-200 dark:text-gray-700" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 20">
<path d="M10 0a10 10 0 1 0 10 10A10.011 10.011 0 0 0 10 0Zm0 5a3 3 0 1 1 0 6 3 3 0 0 1 0-6Zm0 13a8.949 8.949 0 0 1-4.951-1.488A3.987 3.987 0 0 1 9 13h2a3.987 3.987 0 0 1 3.951 3.512A8.949 8.949 0 0 1 10 18Z"/>
</svg>
<div>
<div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-32 mb-2"></div>
<div class="w-48 h-2 bg-gray-200 rounded-full dark:bg-gray-700"></div>
</div>
</div>
<span class="sr-only">Loading...</span>
</div>

View File

@ -1,4 +0,0 @@
<div role="status" class="animate-pulse flex items-center justify-center h-full">
<div class="w-32 h-4 bg-gray-200 rounded-full dark:bg-gray-700 me-3"></div>
<div class="w-32 h-4 bg-gray-200 rounded-full dark:bg-gray-700"></div>
</div>

View File

@ -1 +0,0 @@
<div role="status" class="animate-pulse h-7 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mt-3"></div>

View File

@ -1,47 +0,0 @@
use std::path::PathBuf;
use axum::http::{StatusCode, Uri};
use axum_htmx::AutoVaryLayer;
use sea_orm::DatabaseConnection;
use thiserror::Error;
use tower_http::services::ServeDir;
use ::utils::config::{load_config, ConfigError};
pub mod db;
mod menu;
mod pages;
async fn fallback(uri: Uri) -> (StatusCode, String) {
(StatusCode::NOT_FOUND, format!("No route for {uri}"))
}
#[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()
.nest_service("/assets", ServeDir::new(assets_path))
.merge(pages::get_routes())
.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)
.layer(AutoVaryLayer)
}

View File

@ -1,90 +0,0 @@
use std::path::{Path, PathBuf};
use std::{env, io};
use axum::body::Body;
use axum::http::Request;
use listenfd::ListenFd;
use notify::Watcher;
use thiserror::Error;
use tokio::net::TcpListener;
use tower_livereload::predicate::Predicate;
use tower_livereload::LiveReloadLayer;
use ::app::{get_router, init, InitError};
#[derive(Error, Debug)]
pub enum AppError {
#[error("Unable to bind to TCP listener")]
TCPListener(#[from] std::io::Error),
#[error("Error with the notify watcher")]
NotifyWatcher(#[from] notify::Error),
#[error("Missing environment variable {var}")]
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
/// Voir https://github.com/leotaku/tower-livereload/pull/3
#[derive(Copy, Clone)]
struct NotHtmxPredicate;
impl<T> Predicate<Request<T>> for NotHtmxPredicate {
fn check(&mut self, req: &Request<T>) -> bool {
!(req.headers().contains_key("hx-request"))
}
}
const DEFAULT_LISTENER: &str = "localhost:3000";
async fn get_tcp_listener() -> Result<TcpListener, io::Error> {
let mut listenfd = ListenFd::from_env();
match listenfd.take_tcp_listener(0)? {
// if we are given a tcp listener on listen fd 0, we use that one
Some(listener) => {
listener.set_nonblocking(true)?;
Ok(TcpListener::from_std(listener)?)
}
// otherwise fall back to local listening
None => Ok(TcpListener::bind(DEFAULT_LISTENER).await?),
}
}
fn get_livereload_layer(
templates_paths: Vec<PathBuf>,
) -> Result<LiveReloadLayer<NotHtmxPredicate>, notify::Error> {
let livereload = LiveReloadLayer::new();
let reloader = livereload.reloader();
let mut watcher = notify::recommended_watcher(move |_| reloader.reload())?;
for templates_path in templates_paths {
watcher.watch(templates_path.as_path(), notify::RecursiveMode::Recursive)?;
}
Ok(livereload.request_predicate::<Body, NotHtmxPredicate>(NotHtmxPredicate))
}
#[tokio::main]
async fn main() -> Result<(), AppError> {
init()?;
let manifest_dir = env::var("CARGO_MANIFEST_DIR").map_err(|_| AppError::MissingEnvVar {
var: "CARGO_MANIFEST_DIR",
})?;
let assets_path = Path::new(&manifest_dir).join("assets");
let templates_paths = vec![
Path::new(&manifest_dir).join("src/pages"),
Path::new(&manifest_dir).join("src/components"),
];
let livereload_layer =
get_livereload_layer(templates_paths).map_err(AppError::NotifyWatcher)?;
let router = get_router(assets_path).await.layer(livereload_layer);
let listener: TcpListener = get_tcp_listener().await.map_err(AppError::TCPListener)?;
let local_addr = listener.local_addr().map_err(AppError::TCPListener)?;
println!("Listening on: http://{}", local_addr);
// Run the server with the router
axum::serve(listener, router.into_make_service()).await?;
Ok(())
}

View File

@ -1,28 +0,0 @@
pub struct MenuItem {
pub id: String,
pub label: String,
pub href: String,
}
/// Get the menu items
/// This function is the central place to define the menu items
/// It can be used directly in templates, for example in the `navbar` component to render the menu
pub fn get_menu_items() -> Vec<MenuItem> {
vec![
MenuItem {
id: "home".to_string(),
label: "Accueil".to_string(),
href: "/".to_string(),
},
MenuItem {
id: "cps".to_string(),
label: "CPS".to_string(),
href: "/cps".to_string(),
},
MenuItem {
id: "debug".to_string(),
label: "DEBUG".to_string(),
href: "/debug".to_string(),
},
]
}

View File

@ -1,43 +0,0 @@
{% extends "base.html" %}
{% import "navbar/navbar.html" as navbar -%}
{% block title %}Pharma Libre - CPS{% endblock %}
{% block body %}
{% call navbar::navbar(current="cps") %}
<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"
>
CPS
</h1>
</div>
</header>
<main id="page-main">
<div
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
>A</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
<div
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
>B</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>C</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>D</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>E</div>
</div>
</div>
</main>
</div>
{% endblock %}

View File

@ -1,12 +0,0 @@
use askama_axum::Template;
use axum_htmx::HxRequest;
#[derive(Template)]
#[template(path = "cps.html")]
pub struct CpsTemplate {
hx_request: bool,
}
pub async fn cps(HxRequest(hx_request): HxRequest) -> CpsTemplate {
CpsTemplate { hx_request }
}

View File

@ -1,72 +0,0 @@
{% 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

@ -1,48 +0,0 @@
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,43 +0,0 @@
{% extends "base.html" %}
{% import "navbar/navbar.html" as navbar -%}
{% block title %}Pharma Libre - Accueil{% endblock %}
{% block body %}
{% call navbar::navbar(current="home") %}
<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"
>
Accueil
</h1>
</div>
</header>
<main id="page-main">
<div
class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8"
>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
<div
class="border-2 border-dashed border-gray-300 rounded-lg dark:border-gray-600 h-32 md:h-64"
>A</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>B</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>C</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-32 md:h-64"
>D</div>
</div>
<div
class="border-2 border-dashed rounded-lg border-gray-300 dark:border-gray-600 h-96 mb-4"
>E</div>
</div>
</main>
</div>
{% endblock %}

View File

@ -1,12 +0,0 @@
use askama_axum::Template;
use axum_htmx::HxRequest;
#[derive(Template)]
#[template(path = "home.html")]
pub struct GetHomeTemplate {
hx_request: bool,
}
pub async fn home(HxRequest(hx_request): HxRequest) -> GetHomeTemplate {
GetHomeTemplate { hx_request }
}

View File

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

View File

@ -1,12 +0,0 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/**/*.html',
'./css/**/*.css',
],
theme: {
extend: {},
},
plugins: [],
}