Libera la versión de desarrollo 0.0.2

This commit is contained in:
Manuel Cillero 2022-03-19 20:10:51 +01:00
parent 516d9683da
commit fbc6ab2adf
77 changed files with 651 additions and 161 deletions

View file

@ -1,6 +1,6 @@
[package]
name = "pagetop"
version = "0.0.1"
version = "0.0.2"
edition = "2021"
authors = [

View file

@ -24,3 +24,8 @@ actix-web = "3.3.3"
maud = { version = "0.23.0" }
# Opcional. Si se requiere serialización de estructuras de datos.
serde = { version = "1.0", features = ["derive"] }
[dependencies.sea-orm]
version = "0.6.0"
features = ["debug-print", "macros"]
default-features = false

View file

@ -0,0 +1,29 @@
[package]
name = "app"
version = "0.1.0"
edition = "2021"
# Ver más claves y sus definiciones en
# https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies.pagetop]
path = "../pagetop"
# Opcional. Por defecto se puede usar PageTop sin base de datos.
features = ["mysql"]
# features = ["postgres"]
# features = ["sqlite"]
# PageTop puede dar soporte a todas las bases de datos.
# features = ["mysql", "postgres", "sqlite"]
# Sólo cuando no se usen las características predeterminadas.
default-features = false
[dependencies]
# Opcional. Sólo si se usa la macro html!.
maud = { version = "0.23.0" }
# Opcional. Si se requiere serialización de estructuras de datos.
serde = { version = "1.0", features = ["derive"] }
[dependencies.sea-orm]
version = "0.6.0"
features = ["debug-print", "macros"]
default-features = false

View file

@ -1,8 +1,7 @@
use crate::{Lazy, run_now};
use crate::{Lazy, run_now, app};
use crate::db::migration::*;
use crate::core::theme::ThemeTrait;
use crate::core::module::ModuleTrait;
use crate::core::server;
use crate::theme::ThemeTrait;
use crate::module::ModuleTrait;
use std::sync::RwLock;
@ -16,7 +15,7 @@ pub static THEMES: Lazy<RwLock<Vec<&dyn ThemeTrait>>> = Lazy::new(
|| { RwLock::new(Vec::new()) }
);
pub fn themes(cfg: &mut server::web::ServiceConfig) {
pub fn themes(cfg: &mut app::web::ServiceConfig) {
cfg.service(actix_web_static_files::ResourceFiles::new(
"/theme",
assets()
@ -35,7 +34,7 @@ pub static MODULES: Lazy<RwLock<Vec<&dyn ModuleTrait>>> = Lazy::new(
|| { RwLock::new(Vec::new()) }
);
pub fn modules(cfg: &mut server::web::ServiceConfig) {
pub fn modules(cfg: &mut app::web::ServiceConfig) {
for m in MODULES.read().unwrap().iter() {
m.configure_module(cfg);
}
@ -54,6 +53,6 @@ pub fn run_migrations() {
migrations
}
}
Migrator::up(&server::db::DBCONN, None)
Migrator::up(&app::db::DBCONN, None)
}).unwrap();
}

View file

@ -1,14 +1,13 @@
use crate::{Lazy, base, trace};
use crate::{Lazy, all, app, trace};
use crate::config::SETTINGS;
use crate::core::{Server, all, server};
use crate::core::theme::register_theme;
use crate::core::module::register_module;
use crate::theme::*;
use crate::module::*;
use std::io::Error;
use actix_web::middleware::normalize::{NormalizePath, TrailingSlash};
pub struct Application {
server: Server,
server: app::Server,
}
pub fn essence() {
@ -44,25 +43,19 @@ impl Application {
}
// Inicia registro de trazas y eventos.
Lazy::force(&server::tracing::TRACING);
Lazy::force(&app::tracing::TRACING);
// Valida el identificador de idioma.
Lazy::force(&server::locale::LANGID);
Lazy::force(&app::locale::LANGID);
// Conecta con la base de datos (opcional).
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
Lazy::force(&server::db::DBCONN);
Lazy::force(&app::db::DBCONN);
// Registra los temas predefinidos.
register_theme(&base::theme::aliner::AlinerTheme);
register_theme(&base::theme::minimal::MinimalTheme);
register_theme(&base::theme::bootsier::BootsierTheme);
// Registra los módulos predeterminados.
register_module(&base::module::admin::AdminModule);
// Registra los módulos que requieren base de datos.
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
register_module(&base::module::user::UserModule);
register_theme(&aliner::AlinerTheme);
register_theme(&minimal::MinimalTheme);
register_theme(&bootsier::BootsierTheme);
// Ejecuta la función de inicio de la aplicación.
trace::info!("Calling application bootstrap");
@ -70,15 +63,15 @@ impl Application {
// Registra el módulo para la página de inicio de PageTop.
// Al ser el último, puede sobrecargarse con la función de inicio.
register_module(&base::module::homepage::HomepageModule);
register_module(&homepage::HomepageModule);
// Comprueba actualizaciones pendientes de la base de datos (opcional).
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
all::run_migrations();
// Prepara el servidor web.
let server = server::HttpServer::new(move || {
server::App::new()
let server = app::HttpServer::new(move || {
app::App::new()
.wrap(tracing_actix_web::TracingLogger)
.wrap(NormalizePath::new(TrailingSlash::Trim))
.configure(&all::themes)
@ -93,7 +86,7 @@ impl Application {
Ok(Self { server })
}
pub fn run(self) -> Result<Server, Error> {
pub fn run(self) -> Result<app::Server, Error> {
Ok(self.server)
}
}

View file

@ -1,6 +1,7 @@
pub use actix_web::{
App, HttpRequest, HttpResponse, HttpServer, Responder, Result, http, web
};
use actix_web::dev::Server;
mod tracing;
@ -9,4 +10,4 @@ pub mod locale;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub mod db;
pub mod app;
pub mod application;

View file

@ -1,5 +0,0 @@
//! Temas, Módulos y Componentes base.
pub mod theme;
pub mod module;
pub mod component;

View file

@ -1,2 +0,0 @@
module_fullname = Admin module
module_description = Administration module.

View file

@ -1,2 +0,0 @@
module_fullname = Admin module
module_description = Módulo de administración.

View file

@ -1,28 +0,0 @@
use crate::prelude::*;
localize!("src/base/module/admin/locales");
mod summary;
pub struct AdminModule;
impl ModuleTrait for AdminModule {
fn name(&self) -> &'static str {
"admin"
}
fn fullname(&self) -> String {
l("module_fullname")
}
fn description(&self) -> Option<String> {
Some(l("module_description"))
}
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
cfg.service(
server::web::scope("/admin")
.route("", server::web::get().to(summary::summary))
);
}
}

View file

@ -1,58 +0,0 @@
use crate::prelude::*;
use super::l;
pub async fn summary() -> server::Result<Markup> {
let top_menu = Menu::prepare()
.add(MenuItem::label(l("module_fullname").as_str()))
.add(MenuItem::link("Opción 2", "https://www.google.es"))
.add(MenuItem::link_blank("Opción 3", "https://www.google.es"))
.add(MenuItem::submenu("Submenú 1", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::separator())
.add(MenuItem::submenu("Submenú 2", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::label("Opción 4"));
let side_menu = Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::link("Opción 2", "https://www.google.es"))
.add(MenuItem::link_blank("Opción 3", "https://www.google.es"))
.add(MenuItem::submenu("Submenú 1", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::separator())
.add(MenuItem::submenu("Submenú 2", Menu::prepare()
.add(MenuItem::label("Opción 1"))
.add(MenuItem::label("Opción 2"))
))
.add(MenuItem::label("Opción 4"));
Page::prepare()
.using_theme("bootsier")
.with_title("Admin")
.add_to("top-menu", top_menu)
.add_to("content", Container::row()
.add(Container::column()
.add(side_menu)
)
.add(Container::column()
.add(Chunck::markup(html! {
p { "Columna 2"}
}))
)
)
.using_template("admin")
.render()
}

View file

@ -1,5 +0,0 @@
pub mod admin;
pub mod homepage;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub mod user;

View file

@ -1 +0,0 @@
pub mod user;

View file

@ -1,18 +0,0 @@
use crate::db::entity::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize, Serialize)]
#[sea_orm(table_name = "user")]
pub struct Model {
#[sea_orm(primary_key)]
#[serde(skip_deserializing)]
pub id: i32,
pub title: String,
#[sea_orm(column_type = "Text")]
pub text: String,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}
impl ActiveModelBehavior for ActiveModel {}

View file

@ -1,8 +0,0 @@
module_fullname = User
module_description = Manages the user registration and login system.
username = User name
password = Password
username_help = Enter your { $app } username.
password_help = Enter the password that accompanies your username.
login = Log in

View file

@ -1,8 +0,0 @@
module_fullname = Usuario
module_description = Gestion el registro de usuarios y el sistema de accesos.
username = Nombre de usuario
password = Contraseña
username_help = Introduzca su nombre de usuario en { $app }.
password_help = Introduzca la contraseña asociada a su nombre de usuario.
login = Iniciar sesión

View file

@ -1,54 +0,0 @@
use crate::db::migration::*;
#[derive(Iden)]
enum User {
Table,
Id,
Title,
Text,
}
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20220312_000001_create_table_user"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_table(
Table::create()
.table(User::Table)
.if_not_exists()
.col(ColumnDef::new(User::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(User::Title)
.string()
.not_null()
)
.col(ColumnDef::new(User::Text)
.string()
.not_null()
)
.to_owned()
)
.await
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_table(Table::drop()
.table(User::Table)
.to_owned()
)
.await
}
}

View file

@ -1 +0,0 @@
pub mod m20220312_000001_create_table_user;

View file

@ -1,63 +0,0 @@
use crate::prelude::*;
localize!("src/base/module/user/locales");
mod entity;
mod migration;
pub struct UserModule;
impl ModuleTrait for UserModule {
fn name(&self) -> &'static str {
"user"
}
fn fullname(&self) -> String {
l("module_fullname")
}
fn description(&self) -> Option<String> {
Some(l("module_description"))
}
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
cfg.route("/user/login", server::web::get().to(login));
}
fn migrations(&self) -> Vec<Box<dyn db::migration::MigrationTrait>> {
vec![
boxed_migration!(m20220312_000001_create_table_user)
]
}
}
fn form_login() -> impl PageComponent {
Form::prepare()
.with_id("user-login")
.add(form::Input::textfield()
.with_name("name")
.with_label(l("username").as_str())
.with_help_text(t("username_help", &args![
"app" => SETTINGS.app.name.to_owned()
]).as_str())
.autofocus(true)
)
.add(form::Input::password()
.with_name("pass")
.with_label(l("password").as_str())
.with_help_text(l("password_help").as_str())
)
.add(form::Button::submit(l("login").as_str()))
}
async fn login() -> server::Result<Markup> {
Page::prepare()
.with_title(
"Identificación del usuario"
)
.add_to("content", Container::prepare()
.with_id("welcome")
.add(form_login())
)
.render()
}

View file

@ -1,3 +0,0 @@
pub mod aliner;
pub mod minimal;
pub mod bootsier;

View file

@ -1,9 +0,0 @@
pub use actix_web::dev::Server;
mod all;
pub mod html;
pub mod theme;
pub mod module;
pub mod response;
pub mod server;

View file

@ -1,16 +0,0 @@
use crate::core::all;
mod definition;
pub use definition::ModuleTrait;
pub fn register_module(m: &'static (dyn ModuleTrait + 'static)) {
all::MODULES.write().unwrap().push(m);
}
pub fn find_module(name: &str) -> Option<&'static (dyn ModuleTrait + 'static)> {
let modules = all::MODULES.write().unwrap();
match modules.iter().find(|t| t.name() == name) {
Some(module) => Some(*module),
_ => None,
}
}

View file

@ -8,6 +8,8 @@ pub use futures::executor::block_on as run_now;
// APIs públicas.
// -----------------------------------------------------------------------------
mod all; // Variables globales privadas.
pub mod config; // Gestión de la configuración.
pub mod trace; // Registro de trazas y eventos de la aplicación.
pub mod locale; // Localización.
@ -15,8 +17,14 @@ pub mod locale; // Localización.
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub mod db; // Acceso a la base de datos.
pub mod core; // Servidor web y APIs para Temas, Módulos y Respuestas web.
pub mod base; // Temas, Módulos y Componentes base.
pub mod html; // Publicación de código HTML desde el código.
pub mod theme; // API para crear temas y temas predeterminados.
pub mod module; // API para crear módulos con nuevas funcionalidades.
pub mod response; // Tipos de respuestas web.
pub mod app; // Aplicación y servidor web.
pub mod component; // Componentes base.
pub mod util; // Macros y funciones útiles.
pub mod prelude; // Re-exporta recursos comunes.

View file

@ -7,7 +7,7 @@ pub use fluent_templates::fluent_bundle::FluentValue;
macro_rules! localize {
( $dir_locales:literal $(, $core_locales:literal)? ) => {
use $crate::locale::*;
use $crate::core::server::locale::LANGID;
use $crate::app::locale::LANGID;
static_locale! {
static LOCALES = {
@ -37,8 +37,8 @@ macro_rules! localize {
fn e(
key: &str,
args: &std::collections::HashMap<String, FluentValue>
) -> $crate::core::html::PreEscaped<String> {
$crate::core::html::PreEscaped(
) -> $crate::html::PreEscaped<String> {
$crate::html::PreEscaped(
LOCALES.lookup_with_args(&LANGID, key, args)
)
}

View file

@ -1,20 +1,31 @@
use crate::core::server;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
use crate::db;
use crate::app;
use std::any::type_name;
/// Los módulos deben implementar este *trait*.
pub trait ModuleTrait: Send + Sync {
fn name(&self) -> &'static str;
fn name(&self) -> &'static str {
let name = type_name::<Self>();
match name.rfind("::") {
Some(position) => &name[(position + 2)..],
None => name
}
}
fn fullname(&self) -> String;
fn dependencies(&self) -> Vec<&'static dyn ModuleTrait> {
vec![]
}
fn description(&self) -> Option<String> {
None
}
#[allow(unused_variables)]
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
fn configure_module(&self, cfg: &mut app::web::ServiceConfig) {
}
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]

View file

@ -1,14 +1,10 @@
use crate::prelude::*;
localize!("src/base/module/homepage/locales");
localize!("src/module/homepage/locales");
pub struct HomepageModule;
impl ModuleTrait for HomepageModule {
fn name(&self) -> &'static str {
"homepage"
}
fn fullname(&self) -> String {
l("module_fullname")
}
@ -17,12 +13,12 @@ impl ModuleTrait for HomepageModule {
Some(l("module_description"))
}
fn configure_module(&self, cfg: &mut server::web::ServiceConfig) {
cfg.route("/", server::web::get().to(home));
fn configure_module(&self, cfg: &mut app::web::ServiceConfig) {
cfg.route("/", app::web::get().to(home));
}
}
async fn home() -> server::Result<Markup> {
async fn home() -> app::Result<Markup> {
Page::prepare()
.with_title(
l("page_title").as_str()

17
pagetop/src/module/mod.rs Normal file
View file

@ -0,0 +1,17 @@
use crate::{all, trace};
mod definition;
pub use definition::ModuleTrait;
pub mod homepage;
pub fn register_module(m: &'static dyn ModuleTrait) {
let mut modules = all::MODULES.write().unwrap();
match modules.iter().find(|t| t.name() == m.name()) {
None => {
trace::info!("{}", m.name());
modules.push(m);
},
Some(_) => {},
}
}

View file

@ -8,13 +8,14 @@ pub use crate::localize;
#[cfg(any(feature = "mysql", feature = "postgres", feature = "sqlite"))]
pub use crate::{db, boxed_migration};
pub use crate::core::html::*;
pub use crate::core::theme::*;
pub use crate::core::module::*;
pub use crate::core::response::page::*;
pub use crate::core::server;
pub use crate::core::server::app::{Application, essence};
pub use crate::html::*;
pub use crate::theme::*;
pub use crate::module::*;
pub use crate::response::page::*;
pub use crate::base::component::*;
pub use crate::app;
pub use crate::app::application::{Application, essence};
pub use crate::component::*;
pub use crate::util;

View file

@ -1,8 +1,7 @@
use crate::{Lazy, base};
use crate::{Lazy, all};
use crate::config::SETTINGS;
use crate::core::all;
use crate::core::html::{Markup, PreEscaped, html};
use crate::core::theme::*;
use crate::html::{Markup, PreEscaped, html};
use crate::theme::*;
static DEFAULT_THEME: Lazy<&dyn ThemeTrait> = Lazy::new(|| {
for t in all::THEMES.read().unwrap().iter() {
@ -10,7 +9,7 @@ static DEFAULT_THEME: Lazy<&dyn ThemeTrait> = Lazy::new(|| {
return *t;
}
}
&base::theme::bootsier::BootsierTheme
&bootsier::BootsierTheme
});
// -----------------------------------------------------------------------------

View file

@ -1,5 +1,5 @@
use crate::core::html::{Markup, html};
use crate::core::response::page::PageAssets;
use crate::html::{Markup, html};
use crate::response::page::PageAssets;
use downcast_rs::{Downcast, impl_downcast};

View file

@ -1,5 +1,5 @@
use crate::core::html::{Markup, html};
use crate::core::response::page::{PageAssets, PageComponent, render_component};
use crate::html::{Markup, html};
use crate::response::page::{PageAssets, PageComponent, render_component};
use std::sync::Arc;

View file

@ -1,8 +1,7 @@
use crate::{Lazy, trace, util};
use crate::{Lazy, app, trace, util};
use crate::config::SETTINGS;
use crate::core::html::{DOCTYPE, Markup, html};
use crate::core::response::page::{PageAssets, PageComponent, PageContainer};
use crate::core::server;
use crate::html::{DOCTYPE, Markup, html};
use crate::response::page::{PageAssets, PageComponent, PageContainer};
use std::borrow::Cow;
use std::sync::RwLock;
@ -164,7 +163,7 @@ impl<'a> Page<'a> {
// Page RENDER.
pub fn render(&mut self) -> server::Result<Markup> {
pub fn render(&mut self) -> app::Result<Markup> {
// Acciones del tema antes de renderizar la página.
self.assets.theme().before_render_page(self);

View file

@ -13,7 +13,7 @@ impl ThemeTrait for AlinerTheme {
"Aliner".to_owned()
}
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
fn configure_theme(&self, cfg: &mut app::web::ServiceConfig) {
cfg.service(actix_web_static_files::ResourceFiles::new(
"/aliner",
assets()

View file

@ -2,7 +2,7 @@ use crate::prelude::*;
include!(concat!(env!("OUT_DIR"), "/bootsier.rs"));
localize!("src/base/theme/bootsier/locales");
localize!("src/theme/bootsier/locales");
pub struct BootsierTheme;
@ -15,7 +15,7 @@ impl ThemeTrait for BootsierTheme {
"Bootsier".to_owned()
}
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
fn configure_theme(&self, cfg: &mut app::web::ServiceConfig) {
cfg.service(actix_web_static_files::ResourceFiles::new(
"/bootsier",
assets()
@ -43,16 +43,16 @@ impl ThemeTrait for BootsierTheme {
.add_jquery();
}
fn render_error_page(&self, mut s: server::http::StatusCode) -> server::Result<Markup> {
fn render_error_page(&self, mut s: app::http::StatusCode) -> app::Result<Markup> {
let mut description = "e500-description";
let mut message = "e500-description";
match s {
server::http::StatusCode::NOT_FOUND => {
app::http::StatusCode::NOT_FOUND => {
description = "e404-description";
message = "e404-message";
},
_ => {
s = server::http::StatusCode::INTERNAL_SERVER_ERROR;
s = app::http::StatusCode::INTERNAL_SERVER_ERROR;
}
}
Page::prepare()

View file

@ -1,8 +1,8 @@
use crate::config::SETTINGS;
use crate::core::html::{Markup, html};
use crate::core::response::page::{Page, PageAssets, PageComponent};
use crate::core::server;
use crate::base::component::Chunck;
use crate::html::{Markup, html};
use crate::response::page::{Page, PageAssets, PageComponent};
use crate::app;
use crate::component::Chunck;
/// Los temas deben implementar este "trait".
pub trait ThemeTrait: Send + Sync {
@ -15,7 +15,7 @@ pub trait ThemeTrait: Send + Sync {
}
#[allow(unused_variables)]
fn configure_theme(&self, cfg: &mut server::web::ServiceConfig) {
fn configure_theme(&self, cfg: &mut app::web::ServiceConfig) {
}
#[allow(unused_variables)]
@ -93,7 +93,7 @@ pub trait ThemeTrait: Send + Sync {
*/
}
fn render_error_page(&self, s: server::http::StatusCode) -> server::Result<Markup> {
fn render_error_page(&self, s: app::http::StatusCode) -> app::Result<Markup> {
Page::prepare()
.with_title(format!("Error {}", s.as_str()).as_str())
.add_to("content", Chunck::markup(html! {

View file

@ -1,13 +1,17 @@
use crate::core::all;
use crate::all;
mod definition;
pub use definition::ThemeTrait;
pub fn register_theme(t: &'static (dyn ThemeTrait + 'static)) {
pub mod aliner;
pub mod minimal;
pub mod bootsier;
pub fn register_theme(t: &'static dyn ThemeTrait) {
all::THEMES.write().unwrap().push(t);
}
pub fn find_theme(name: &str) -> Option<&'static (dyn ThemeTrait + 'static)> {
pub fn find_theme(name: &str) -> Option<&'static dyn ThemeTrait> {
let themes = all::THEMES.write().unwrap();
match themes.iter().find(|t| t.name() == name) {
Some(theme) => Some(*theme),