Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion loco-new/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions loco-new/base_template/src/app.rs.t
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ impl Hooks for App {
#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
{%- if settings.auth %}
tasks.register(tasks::user_create::UserCreate);
{%- endif %}
}

{%- if settings.db %}
Expand Down
3 changes: 3 additions & 0 deletions loco-new/base_template/src/tasks/mod.rs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{%- if settings.auth %}
pub mod user_create;
{%- endif %}
99 changes: 99 additions & 0 deletions loco-new/base_template/src/tasks/user_create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use loco_rs::prelude::*;

use crate::{
mailers::auth::AuthMailer,
models::{_entities::users, users::RegisterParams},
};

pub struct UserCreate;
#[async_trait]
impl Task for UserCreate {
fn task(&self) -> TaskInfo {
TaskInfo {
name: "user:create".to_string(),
detail: "Create a new user with email, name, and password. Sends welcome email and sets up email verification.\nUsage:\ncargo run task user:create email:[email protected] name:\"John Doe\" password:\"securepassword\"".to_string(),
}
}
async fn run(&self, app_context: &AppContext, vars: &task::Vars) -> Result<()> {
let email = vars
.cli_arg("email")
.map_err(|_| Error::string("email is mandatory"))?;
let name = vars
.cli_arg("name")
.map_err(|_| Error::string("name is mandatory"))?;
let password = vars
.cli_arg("password")
.map_err(|_| Error::string("password is mandatory"))?;

let register_params = RegisterParams {
email: email.to_string(),
password: password.to_string(),
name: name.to_string(),
};

// Create user with password using the same logic as register controller
let res = users::Model::create_with_password(&app_context.db, &register_params).await;

let user = match res {
Ok(user) => {
tracing::info!(
message = "User created successfully",
user_email = &register_params.email,
user_pid = user.pid.to_string(),
"user created via task"
);
user
}
Err(err) => {
tracing::error!(
message = err.to_string(),
user_email = &register_params.email,
"could not create user via task"
);
return Err(Error::string(
&format!("Failed to create user. err: {err}",),
));
}
};

// Set email verification sent (same as register controller)
let user = user
.into_active_model()
.set_email_verification_sent(&app_context.db)
.await
.map_err(|err| {
tracing::error!(
message = err.to_string(),
user_email = &register_params.email,
"could not set email verification"
);
Error::string("Failed to set email verification")
})?;

// Send welcome email (same as register controller)
AuthMailer::send_welcome(app_context, &user)
.await
.map_err(|err| {
tracing::error!(
message = err.to_string(),
user_email = &register_params.email,
"could not send welcome email"
);
Error::string("Failed to send welcome email")
})?;

tracing::info!(
message = "User creation task completed successfully",
user_email = &register_params.email,
user_pid = user.pid.to_string(),
"user creation task finished"
);

println!("✅ User created successfully!");
println!(" Email: {}", user.email);
println!(" Name: {}", user.name);
println!(" PID: {}", user.pid);

Ok(())
}
}
3 changes: 3 additions & 0 deletions loco-new/base_template/tests/tasks/mod.rs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{%- if settings.auth %}
pub mod user_create;
{%- endif %}
58 changes: 58 additions & 0 deletions loco-new/base_template/tests/tasks/user_create.rs.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use loco_rs::{task, testing::prelude::*};
use {{settings.module_name}}::{app::App, models::users};

use loco_rs::boot::run_task;
use serial_test::serial;

#[tokio::test]
#[serial]
async fn test_can_run_user_create() {
let boot = boot_test::<App>().await.unwrap();

let email = "[email protected]";
let user = users::Model::find_by_email(&boot.app_context.db, email).await;
assert!(user.is_err());

let vars = task::Vars::from_cli_args(vec![
("email".to_string(), email.to_string()),
("name".to_string(), "Test User".to_string()),
("password".to_string(), "securepassword".to_string()),
]);
assert!(
run_task::<App>(&boot.app_context, Some(&"user:create".to_string()), &vars)
.await
.is_ok()
);

let deliveries = boot.app_context.mailer.unwrap().deliveries();
assert_eq!(deliveries.count, 1, "Exactly one email should be sent");

let user = users::Model::find_by_email(&boot.app_context.db, email).await;
assert!(user.is_ok());
}

#[tokio::test]
#[serial]
async fn test_user_email_already_exists() {
let boot = boot_test::<App>().await.unwrap();
seed::<App>(&boot.app_context).await.unwrap();

let email = "[email protected]";

let vars = task::Vars::from_cli_args(vec![
("email".to_string(), email.to_string()),
("name".to_string(), "Test User".to_string()),
("password".to_string(), "securepassword".to_string()),
]);
let err = run_task::<App>(&boot.app_context, Some(&"user:create".to_string()), &vars)
.await
.expect_err("err");

assert_eq!(
err.to_string(),
"Failed to create user. err: Entity already exists"
);

let deliveries = boot.app_context.mailer.unwrap().deliveries();
assert_eq!(deliveries.count, 0, "No email should be sent");
}
3 changes: 3 additions & 0 deletions loco-new/setup.rhai
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ if db {
gen.copy_file("migration/src/m20220101_000001_users.rs"); // Users migration file
gen.copy_file("src/models/_entities/users.rs"); // Users entity definition
gen.copy_file("src/models/users.rs"); // Users model logic
gen.copy_file("src/tasks/user_create.rs");

// Fixtures and test setup for authentication
gen.copy_dir("src/fixtures"); // Database fixtures directory
Expand All @@ -77,6 +78,8 @@ if db {
gen.copy_dir("tests/models/snapshots"); // Test snapshots for models
gen.copy_template("tests/models/users.rs.t"); // User model test template
gen.copy_template("tests/requests/prepare_data.rs.t"); // Data preparation template for tests
gen.copy_template("tests/tasks/user_create.rs.t");

}

gen.copy_template("examples/playground.rs.t"); // Example playground template with DB setup
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/auth.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -55,6 +56,6 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/auth.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use std::path::Path;
use async_trait::async_trait;
Expand Down Expand Up @@ -58,7 +59,7 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
async fn truncate(_ctx: &AppContext) -> Result<()> {
Ok(())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/auth.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -58,5 +59,6 @@ impl Hooks for App {
#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
tasks.register(tasks::user_create::UserCreate);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/auth.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use std::path::Path;
use async_trait::async_trait;
Expand Down Expand Up @@ -61,6 +62,7 @@ impl Hooks for App {
#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
tasks.register(tasks::user_create::UserCreate);
}
async fn truncate(ctx: &AppContext) -> Result<()> {
truncate_table(&ctx.db, users::Entity).await?;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/background.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -58,6 +59,6 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/background.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -58,6 +59,6 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/background.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -55,6 +56,6 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/background.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -58,6 +59,6 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/db.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -55,6 +56,6 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/db.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use std::path::Path;
use async_trait::async_trait;
Expand Down Expand Up @@ -58,7 +59,7 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
async fn truncate(_ctx: &AppContext) -> Result<()> {
Ok(())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/db.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use std::path::Path;
use async_trait::async_trait;
Expand Down Expand Up @@ -58,7 +59,7 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
async fn truncate(_ctx: &AppContext) -> Result<()> {
Ok(())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
source: tests/templates/initializers.rs
expression: "std::fs::read_to_string(generator.path(\"src/app.rs\")).expect(\"could not open file\")"
snapshot_kind: text
---
use async_trait::async_trait;
use loco_rs::{
Expand Down Expand Up @@ -55,6 +56,6 @@ impl Hooks for App {

#[allow(unused_variables)]
fn register_tasks(tasks: &mut Tasks) {
// tasks-inject (do not remove)
// tasks-inject (do not remove)
}
}
Loading
Loading