Repository: loco-rs/loco Branch: master Commit: e9598a6822c5 Files: 794 Total size: 2.4 MB Directory structure: gitextract_8x970ict/ ├── .cargo/ │ └── config.toml ├── .clippy.toml ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ ├── config.yml │ │ ├── feature-request.md │ │ └── suggestion.md │ ├── dependabot.yml │ └── workflows/ │ ├── docs.yml │ ├── loco-gen-ci.yml │ ├── loco-gen-deploy.yml │ ├── loco-new.yml │ ├── loco-rs-ci-sanity.yml │ └── loco-rs-ci.yml ├── .gitignore ├── .rustfmt.toml ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.toml ├── DEVELOPMENT.md ├── LICENSE ├── README-pt_BR.md ├── README-zh_CN.md ├── README.es.md ├── README.fr.md ├── README.ja.md ├── README.ko.md ├── README.md ├── README.ru.md ├── SECURITY.md ├── build/ │ └── embedded_assets.rs ├── build.rs ├── docs-site/ │ ├── .gitignore │ ├── config.toml │ ├── content/ │ │ ├── _index.md │ │ ├── authors/ │ │ │ ├── _index.md │ │ │ ├── limpidcrypto.md │ │ │ └── team-loco.md │ │ ├── blog/ │ │ │ ├── _index.md │ │ │ ├── angular-frontend.md │ │ │ ├── axum-session.md │ │ │ ├── deploy-aws.md │ │ │ ├── frontend-website.md │ │ │ └── hello-world.md │ │ ├── casts/ │ │ │ ├── 001-dynamic-responses-and-content-types.md │ │ │ ├── 002-routes-and-prefixes.md │ │ │ ├── 003-scaffolding-crud-with-html.md │ │ │ ├── 004-creating-tasks.md │ │ │ ├── 005-testing-tasks.md │ │ │ ├── 006-mailers.md │ │ │ ├── 007-htmx.md │ │ │ └── _index.md │ │ ├── docs/ │ │ │ ├── _index.md │ │ │ ├── extras/ │ │ │ │ ├── _index.md │ │ │ │ ├── authentication.md │ │ │ │ ├── pluggability.md │ │ │ │ ├── upgrades.md │ │ │ │ └── websocket.md │ │ │ ├── getting-started/ │ │ │ │ ├── _index.md │ │ │ │ ├── axum-users.md │ │ │ │ ├── guide.md │ │ │ │ ├── starters.md │ │ │ │ └── tour/ │ │ │ │ └── index.md │ │ │ ├── infrastructure/ │ │ │ │ ├── _index.md │ │ │ │ ├── cache.md │ │ │ │ ├── data.md │ │ │ │ ├── deployment.md │ │ │ │ └── storage.md │ │ │ ├── processing/ │ │ │ │ ├── _index.md │ │ │ │ ├── mailers.md │ │ │ │ ├── scheduler.md │ │ │ │ ├── task.md │ │ │ │ └── workers.md │ │ │ ├── resources/ │ │ │ │ ├── _index.md │ │ │ │ ├── around-the-web.md │ │ │ │ └── faq.md │ │ │ └── the-app/ │ │ │ ├── _index.md │ │ │ ├── controller.md │ │ │ ├── models.md │ │ │ ├── views.md │ │ │ └── your-project.md │ │ └── privacy-policy/ │ │ └── _index.md │ ├── package.json │ ├── static/ │ │ ├── ahrefs_f06cfb9c2671c1b7a5b6eec0d47a07719bd6d5a2240b829cad6a3f40684fa370 │ │ ├── js/ │ │ │ ├── main.js │ │ │ └── search.js │ │ ├── styles/ │ │ │ └── styles.css │ │ ├── syntax-theme-dark.css │ │ └── syntax-theme-light.css │ ├── styles/ │ │ └── styles.css │ ├── tailwind.config.js │ ├── templates/ │ │ ├── 404.html │ │ ├── base.html │ │ ├── blog/ │ │ │ ├── atom.xml │ │ │ ├── page.html │ │ │ ├── rss.xml │ │ │ └── section.html │ │ ├── casts/ │ │ │ ├── page.html │ │ │ └── section.html │ │ ├── docs/ │ │ │ ├── page.html │ │ │ └── section.html │ │ ├── index.html │ │ ├── macros/ │ │ │ ├── docs-edit-page.html │ │ │ ├── docs-navigation.html │ │ │ ├── docs-sidebar.html │ │ │ ├── docs-toc.html │ │ │ ├── footer.html │ │ │ ├── header.html │ │ │ ├── javascript.html │ │ │ ├── page-publish-metadata.html │ │ │ └── youtube.html │ │ ├── page.html │ │ ├── section.html │ │ ├── shortcodes/ │ │ │ └── get_env.html │ │ ├── taxonomy_list.html │ │ └── taxonomy_single.html │ └── translations/ │ └── tour-fr.md ├── loco-cli/ │ ├── .gitignore │ ├── .rustfmt.toml │ ├── Cargo.toml │ ├── README.md │ └── src/ │ ├── bin/ │ │ └── main.rs │ └── lib.rs ├── loco-gen/ │ ├── .gitattributes │ ├── Cargo.toml │ ├── src/ │ │ ├── controller.rs │ │ ├── infer.rs │ │ ├── lib.rs │ │ ├── mappings.json │ │ ├── migration.rs │ │ ├── model.rs │ │ ├── scaffold.rs │ │ ├── snapshots/ │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array!_big_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array!_bool].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array!_double].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array!_float].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array!_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array!_string].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array^_big_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array^_bool].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array^_double].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array^_float].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array^_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array^_string].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array_big_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array_bool].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array_double].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array_float].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_array_string].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_big_int!_big_int!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_big_int^_big_int^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_big_int_big_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_big_unsigned!_big_unsigned!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_big_unsigned^_big_unsigned^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_big_unsigned_big_unsigned].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_binary_len!_binary_len!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_binary_len^_binary_len^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_binary_len_binary_len].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_blob!_blob!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_blob^_blob^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_blob_blob].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_bool!_bool!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_bool_bool].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_date!_date!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_date^_date^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_date_date].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_date_time!_date_time!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_date_time^_date_time^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_date_time_date_time].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_decimal!_decimal!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_decimal^_decimal^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_decimal_decimal].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_decimal_len!_decimal_len!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_decimal_len^_decimal_len^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_decimal_len_decimal_len].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_double!_double!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_double^_double^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_double_double].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_float!_float!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_float^_float^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_float_float].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_int!_int!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_int^_int^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_int_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_json!_json!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_json_json].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_jsonb!_jsonb!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_jsonb^_jsonb^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_jsonb_jsonb].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_money!_money!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_money^_money^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_money_money].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_small_int!_small_int!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_small_int^_small_int^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_small_int_small_int].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_small_unsigned!_small_unsigned!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_small_unsigned^_small_unsigned^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_small_unsigned_small_unsigned].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_string!_string!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_string^_string^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_string_string].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_text!_text!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_text^_text^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_text_text].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_tstz!_tstz!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_tstz_tstz].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_unsigned!_unsigned!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_unsigned^_unsigned^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_unsigned_unsigned].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_uuid!_uuid!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_uuid^_uuid^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_uuid_uuid].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_var_binary!_var_binary!].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_var_binary^_var_binary^].snap │ │ │ ├── loco_gen__tera_ext__tests__can_render_form_field_[form_var_binary_var_binary].snap │ │ │ └── loco_gen__tera_ext__tests__can_render_view_field.snap │ │ ├── template.rs │ │ ├── templates/ │ │ │ ├── controller/ │ │ │ │ ├── api/ │ │ │ │ │ ├── controller.t │ │ │ │ │ └── test.t │ │ │ │ ├── html/ │ │ │ │ │ ├── controller.t │ │ │ │ │ └── view.t │ │ │ │ └── htmx/ │ │ │ │ ├── controller.t │ │ │ │ └── view.t │ │ │ ├── data/ │ │ │ │ ├── 0_mod.t │ │ │ │ ├── data.t │ │ │ │ ├── mod.t │ │ │ │ └── struct.t │ │ │ ├── deployment/ │ │ │ │ ├── docker/ │ │ │ │ │ ├── docker.t │ │ │ │ │ └── ignore.t │ │ │ │ └── nginx/ │ │ │ │ └── nginx.t │ │ │ ├── mailer/ │ │ │ │ ├── html.t │ │ │ │ ├── mailer.t │ │ │ │ ├── subject.t │ │ │ │ └── text.t │ │ │ ├── migration/ │ │ │ │ ├── add_columns.t │ │ │ │ ├── add_references.t │ │ │ │ ├── empty.t │ │ │ │ ├── join_table.t │ │ │ │ └── remove_columns.t │ │ │ ├── model/ │ │ │ │ ├── model.t │ │ │ │ └── test.t │ │ │ ├── scaffold/ │ │ │ │ ├── api/ │ │ │ │ │ ├── controller.t │ │ │ │ │ └── test.t │ │ │ │ ├── html/ │ │ │ │ │ ├── base.t │ │ │ │ │ ├── controller.t │ │ │ │ │ ├── view.t │ │ │ │ │ ├── view_create.t │ │ │ │ │ ├── view_edit.t │ │ │ │ │ ├── view_list.t │ │ │ │ │ └── view_show.t │ │ │ │ └── htmx/ │ │ │ │ ├── base.t │ │ │ │ ├── controller.t │ │ │ │ ├── view.t │ │ │ │ ├── view_create.t │ │ │ │ ├── view_edit.t │ │ │ │ ├── view_list.t │ │ │ │ └── view_show.t │ │ │ ├── scheduler/ │ │ │ │ └── scheduler.t │ │ │ ├── task/ │ │ │ │ ├── task.t │ │ │ │ └── test.t │ │ │ └── worker/ │ │ │ ├── test.t │ │ │ └── worker.t │ │ ├── tera_ext.rs │ │ └── testutil.rs │ └── tests/ │ ├── db.rs │ ├── mod.rs │ ├── snapshots/ │ │ ├── db__migrations_flow_postgres.snap │ │ ├── db__migrations_flow_sqlite.snap │ │ ├── r#mod__db__migrations_flow_postgres.snap │ │ └── r#mod__db__migrations_flow_sqlite.snap │ └── templates/ │ ├── controller.rs │ ├── deployment.rs │ ├── mailer.rs │ ├── migration.rs │ ├── mod.rs │ ├── model.rs │ ├── scaffold.rs │ ├── scheduler.rs │ ├── snapshots/ │ │ ├── generate[controller_file]@Api_controller.snap │ │ ├── generate[controller_file]@Api_scaffold.snap │ │ ├── generate[controller_file]@Html_controller.snap │ │ ├── generate[controller_file]@Html_scaffold.snap │ │ ├── generate[controller_file]@Htmx_controller.snap │ │ ├── generate[controller_file]@Htmx_scaffold.snap │ │ ├── generate[controller_file]@scheduler.snap │ │ ├── generate[controller_file]@task.snap │ │ ├── generate[controller_file]@worker.snap │ │ ├── generate[docker_file_[0]_[false]]@deployment.snap │ │ ├── generate[docker_file_[0]_[true]]@deployment.snap │ │ ├── generate[docker_file_[2]_[false]]@deployment.snap │ │ ├── generate[docker_file_[2]_[true]]@deployment.snap │ │ ├── generate[html_t_file]@mailer.snap │ │ ├── generate[mailer_mod_rs]@mailer.snap │ │ ├── generate[migration_file]@Api_scaffold.snap │ │ ├── generate[migration_file]@Html_scaffold.snap │ │ ├── generate[migration_file]@Htmx_scaffold.snap │ │ ├── generate[migration_file]@add_column_migration.snap │ │ ├── generate[migration_file]@add_reference_migration.snap │ │ ├── generate[migration_file]@create_join_table_migration.snap │ │ ├── generate[migration_file]@create_join_table_without_tz_migration.snap │ │ ├── generate[migration_file]@create_table_migration.snap │ │ ├── generate[migration_file]@create_table_without_tz_migration.snap │ │ ├── generate[migration_file]@empty_migration.snap │ │ ├── generate[migration_file]@model.snap │ │ ├── generate[migration_file]@remove_columns_migration.snap │ │ ├── generate[nginx]@deployment.snap │ │ ├── generate[subject_t_file]@mailer.snap │ │ ├── generate[test_model]@Api_scaffold.snap │ │ ├── generate[test_model]@Html_scaffold.snap │ │ ├── generate[test_model]@Htmx_scaffold.snap │ │ ├── generate[test_model]@model.snap │ │ ├── generate[tests_controller_mod_rs]@Api_controller.snap │ │ ├── generate[tests_task_file]@task.snap │ │ ├── generate[tests_worker_file]@worker.snap │ │ ├── generate[text_t_file]@mailer.snap │ │ ├── generate[views_[create]]@Html_scaffold.snap │ │ ├── generate[views_[create]]@Htmx_scaffold.snap │ │ ├── generate[views_[edit]]@Html_scaffold.snap │ │ ├── generate[views_[edit]]@Htmx_scaffold.snap │ │ ├── generate[views_[list]]@Html_scaffold.snap │ │ ├── generate[views_[list]]@Htmx_scaffold.snap │ │ ├── generate[views_[show]]@Html_scaffold.snap │ │ ├── generate[views_[show]]@Htmx_scaffold.snap │ │ ├── generate[views_rs]@Html_scaffold.snap │ │ ├── generate[views_rs]@Htmx_scaffold.snap │ │ ├── generate_result@add_column_migration.snap │ │ ├── generate_result@add_reference_migration.snap │ │ ├── generate_result@create_join_table_migration.snap │ │ ├── generate_result@create_join_table_without_tz_migration.snap │ │ ├── generate_result@create_table_migration.snap │ │ ├── generate_result@create_table_without_tz_migration.snap │ │ ├── generate_result@empty_migration.snap │ │ ├── generate_result@remove_columns_migration.snap │ │ ├── generate_results@Api_controller.snap │ │ ├── generate_results@Api_scaffold.snap │ │ ├── generate_results@Html_controller.snap │ │ ├── generate_results@Html_scaffold.snap │ │ ├── generate_results@Htmx_controller.snap │ │ ├── generate_results@Htmx_scaffold.snap │ │ ├── inject[.config_toml]@deployment.snap │ │ ├── inject[app_rs]@Api_controller.snap │ │ ├── inject[app_rs]@Api_scaffold.snap │ │ ├── inject[app_rs]@Html_controller.snap │ │ ├── inject[app_rs]@Html_scaffold.snap │ │ ├── inject[app_rs]@Htmx_controller.snap │ │ ├── inject[app_rs]@Htmx_scaffold.snap │ │ ├── inject[app_rs]@task.snap │ │ ├── inject[app_rs]@worker.snap │ │ ├── inject[controller_mod_rs]@Api_controller.snap │ │ ├── inject[controller_mod_rs]@Api_scaffold.snap │ │ ├── inject[controller_mod_rs]@Html_controller.snap │ │ ├── inject[controller_mod_rs]@Html_scaffold.snap │ │ ├── inject[controller_mod_rs]@Htmx_controller.snap │ │ ├── inject[controller_mod_rs]@Htmx_scaffold.snap │ │ ├── inject[mailer_mod_rs]@mailer.snap │ │ ├── inject[migration_lib]@Api_scaffold.snap │ │ ├── inject[migration_lib]@Html_scaffold.snap │ │ ├── inject[migration_lib]@Htmx_scaffold.snap │ │ ├── inject[migration_lib]@add_column_migration.snap │ │ ├── inject[migration_lib]@add_reference_migration.snap │ │ ├── inject[migration_lib]@create_join_table_migration.snap │ │ ├── inject[migration_lib]@create_join_table_without_tz_migration.snap │ │ ├── inject[migration_lib]@create_table_migration.snap │ │ ├── inject[migration_lib]@create_table_without_tz_migration.snap │ │ ├── inject[migration_lib]@empty_migration.snap │ │ ├── inject[migration_lib]@model.snap │ │ ├── inject[migration_lib]@remove_columns_migration.snap │ │ ├── inject[task_mod_rs]@task.snap │ │ ├── inject[test_mod]@Api_scaffold.snap │ │ ├── inject[test_mod]@Html_scaffold.snap │ │ ├── inject[test_mod]@Htmx_scaffold.snap │ │ ├── inject[test_mod]@model.snap │ │ ├── inject[tests_controller_mod_rs]@Api_controller.snap │ │ ├── inject[tests_task_mod]@task.snap │ │ ├── inject[tests_worker_mod]@worker.snap │ │ ├── inject[views_[GET]]@Html_controller.snap │ │ ├── inject[views_[GET]]@Htmx_controller.snap │ │ ├── inject[views_[POST]]@Html_controller.snap │ │ ├── inject[views_[POST]]@Htmx_controller.snap │ │ ├── inject[views_mod_rs]@Html_scaffold.snap │ │ ├── inject[views_mod_rs]@Htmx_scaffold.snap │ │ └── inject[worker_mod_rs]@worker.snap │ ├── task.rs │ ├── utils.rs │ └── worker.rs ├── loco-new/ │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── base_template/ │ │ ├── .cargo/ │ │ │ └── config.toml.t │ │ ├── .github/ │ │ │ └── workflows/ │ │ │ └── ci.yaml.t │ │ ├── .gitignore │ │ ├── .rustfmt.toml │ │ ├── Cargo.toml.t │ │ ├── README.md │ │ ├── assets/ │ │ │ ├── i18n/ │ │ │ │ ├── de-DE/ │ │ │ │ │ └── main.ftl │ │ │ │ ├── en-US/ │ │ │ │ │ └── main.ftl │ │ │ │ └── shared.ftl │ │ │ ├── static/ │ │ │ │ └── 404.html │ │ │ └── views/ │ │ │ └── home/ │ │ │ └── hello.html │ │ ├── config/ │ │ │ ├── development.yaml.t │ │ │ └── test.yaml.t │ │ ├── examples/ │ │ │ └── playground.rs.t │ │ ├── frontend/ │ │ │ ├── .gitignore │ │ │ ├── README.md │ │ │ ├── biome.json │ │ │ ├── package.json │ │ │ ├── rsbuild.config.ts │ │ │ ├── src/ │ │ │ │ ├── LocoSplash.tsx │ │ │ │ ├── env.d.ts │ │ │ │ ├── index.css │ │ │ │ └── index.tsx │ │ │ └── tsconfig.json │ │ ├── migration/ │ │ │ ├── Cargo.toml.t │ │ │ └── src/ │ │ │ ├── lib.rs.t │ │ │ └── m20220101_000001_users.rs │ │ ├── src/ │ │ │ ├── app.rs.t │ │ │ ├── bin/ │ │ │ │ ├── main.rs.t │ │ │ │ └── tool.rs.t │ │ │ ├── controllers/ │ │ │ │ ├── auth.rs │ │ │ │ ├── home.rs │ │ │ │ └── mod.rs.t │ │ │ ├── data/ │ │ │ │ └── mod.rs.t │ │ │ ├── fixtures/ │ │ │ │ └── users.yaml │ │ │ ├── initializers/ │ │ │ │ ├── mod.rs.t │ │ │ │ └── view_engine.rs │ │ │ ├── lib.rs.t │ │ │ ├── mailers/ │ │ │ │ ├── auth/ │ │ │ │ │ ├── forgot/ │ │ │ │ │ │ ├── html.t │ │ │ │ │ │ ├── subject.t │ │ │ │ │ │ └── text.t │ │ │ │ │ ├── magic_link/ │ │ │ │ │ │ ├── html.t │ │ │ │ │ │ ├── subject.t │ │ │ │ │ │ └── text.t │ │ │ │ │ └── welcome/ │ │ │ │ │ ├── html.t │ │ │ │ │ ├── subject.t │ │ │ │ │ └── text.t │ │ │ │ ├── auth.rs │ │ │ │ └── mod.rs │ │ │ ├── models/ │ │ │ │ ├── _entities/ │ │ │ │ │ ├── mod.rs.t │ │ │ │ │ ├── prelude.rs.t │ │ │ │ │ └── users.rs │ │ │ │ ├── mod.rs.t │ │ │ │ └── users.rs │ │ │ ├── tasks/ │ │ │ │ ├── mod.rs.t │ │ │ │ └── user_create.rs │ │ │ ├── views/ │ │ │ │ ├── auth.rs │ │ │ │ ├── home.rs │ │ │ │ └── mod.rs.t │ │ │ └── workers/ │ │ │ ├── downloader.rs │ │ │ └── mod.rs.t │ │ └── tests/ │ │ ├── mod.rs.t │ │ ├── models/ │ │ │ ├── mod.rs.t │ │ │ ├── snapshots/ │ │ │ │ ├── can_create_with_password@users.snap │ │ │ │ ├── can_find_by_email@users-2.snap │ │ │ │ ├── can_find_by_email@users.snap │ │ │ │ ├── can_find_by_pid@users-2.snap │ │ │ │ ├── can_find_by_pid@users.snap │ │ │ │ ├── can_validate_model@users.snap │ │ │ │ └── handle_create_with_password_with_duplicate@users.snap │ │ │ └── users.rs.t │ │ ├── requests/ │ │ │ ├── auth.rs.t │ │ │ ├── home.rs.t │ │ │ ├── mod.rs.t │ │ │ ├── prepare_data.rs.t │ │ │ └── snapshots/ │ │ │ ├── can_auth_with_magic_link@auth_request.snap │ │ │ ├── can_get_current_user@auth_request.snap │ │ │ ├── can_login_without_verify@auth_request.snap │ │ │ ├── can_register@auth_request.snap │ │ │ ├── can_reset_password@auth_request.snap │ │ │ ├── login_with_invalid_password@auth_request.snap │ │ │ ├── login_with_valid_password@auth_request.snap │ │ │ └── resend_verification_user@auth_request.snap │ │ ├── tasks/ │ │ │ ├── mod.rs.t │ │ │ └── user_create.rs.t │ │ └── workers/ │ │ └── mod.rs │ ├── devnew.sh │ ├── setup.rhai │ ├── src/ │ │ ├── bin/ │ │ │ └── main.rs │ │ ├── generator/ │ │ │ ├── executer/ │ │ │ │ ├── filesystem.rs │ │ │ │ ├── inmem.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── template.rs │ │ ├── lib.rs │ │ ├── settings.rs │ │ └── wizard.rs │ └── tests/ │ ├── assertion/ │ │ ├── mod.rs │ │ ├── string.rs │ │ ├── toml.rs │ │ └── yaml.rs │ ├── mod.rs │ ├── templates/ │ │ ├── asset.rs │ │ ├── auth.rs │ │ ├── background.rs │ │ ├── db.rs │ │ ├── features.rs │ │ ├── initializers.rs │ │ ├── mailer.rs │ │ ├── mod.rs │ │ ├── module_name.rs │ │ └── snapshots/ │ │ ├── r#mod__templates__asset__cargo_dependencies_Clientside.snap │ │ ├── r#mod__templates__asset__cargo_dependencies_None.snap │ │ ├── r#mod__templates__asset__cargo_dependencies_Serverside.snap │ │ ├── r#mod__templates__auth__src_app_rs_auth_false_None.snap │ │ ├── r#mod__templates__auth__src_app_rs_auth_false_Sqlite.snap │ │ ├── r#mod__templates__auth__src_app_rs_auth_true_None.snap │ │ ├── r#mod__templates__auth__src_app_rs_auth_true_Sqlite.snap │ │ ├── r#mod__templates__background__src_app_rs_Async.snap │ │ ├── r#mod__templates__background__src_app_rs_Blocking.snap │ │ ├── r#mod__templates__background__src_app_rs_None.snap │ │ ├── r#mod__templates__background__src_app_rs_Queue.snap │ │ ├── r#mod__templates__db__cargo_dependencies_None.snap │ │ ├── r#mod__templates__db__cargo_dependencies_Postgres.snap │ │ ├── r#mod__templates__db__cargo_dependencies_Sqlite.snap │ │ ├── r#mod__templates__db__config_development_yaml_config_database_Postgres.snap │ │ ├── r#mod__templates__db__config_development_yaml_config_database_Sqlite.snap │ │ ├── r#mod__templates__db__config_test_yaml_config_database_Postgres.snap │ │ ├── r#mod__templates__db__config_test_yaml_config_database_Sqlite.snap │ │ ├── r#mod__templates__db__src_app_rs_None.snap │ │ ├── r#mod__templates__db__src_app_rs_Postgres.snap │ │ ├── r#mod__templates__db__src_app_rs_Sqlite.snap │ │ ├── r#mod__templates__initializers__src_app_rs_with_initializers.snap │ │ ├── r#mod__templates__initializers__src_app_rs_without_initializers.snap │ │ ├── r#mod__templates__mailer__cargo_dependencies_mailer_false.snap │ │ └── r#mod__templates__mailer__cargo_dependencies_mailer_true.snap │ └── wizard/ │ ├── mod.rs │ └── new.rs ├── snipdoc.yml ├── src/ │ ├── app.rs │ ├── auth/ │ │ ├── jwt.rs │ │ ├── mod.rs │ │ └── snapshots/ │ │ ├── loco_rs__auth__jwt__tests__token expired.snap │ │ ├── loco_rs__auth__jwt__tests__valid token and custom array claims.snap │ │ ├── loco_rs__auth__jwt__tests__valid token and custom boolean claims.snap │ │ ├── loco_rs__auth__jwt__tests__valid token and custom nested array claims.snap │ │ ├── loco_rs__auth__jwt__tests__valid token and custom nested claims.snap │ │ ├── loco_rs__auth__jwt__tests__valid token and custom number claims.snap │ │ ├── loco_rs__auth__jwt__tests__valid token and custom string claims.snap │ │ └── loco_rs__auth__jwt__tests__valid token.snap │ ├── banner.rs │ ├── bgworker/ │ │ ├── mod.rs │ │ ├── pg.rs │ │ ├── redis.rs │ │ ├── snapshots/ │ │ │ ├── loco_rs__bgworker__pg__tests__can_complete_job_with_interval.snap │ │ │ ├── loco_rs__bgworker__pg__tests__can_dequeue.snap │ │ │ ├── loco_rs__bgworker__pg__tests__can_enqueue.snap │ │ │ ├── loco_rs__bgworker__pg__tests__can_fail_job.snap │ │ │ ├── loco_rs__bgworker__pg__tests__can_initialize_database.snap │ │ │ ├── loco_rs__bgworker__sqlt__tests__can_complete_job_with_interval.snap │ │ │ ├── loco_rs__bgworker__sqlt__tests__can_dequeue.snap │ │ │ ├── loco_rs__bgworker__sqlt__tests__can_enqueue.snap │ │ │ ├── loco_rs__bgworker__sqlt__tests__can_fail_job.snap │ │ │ ├── loco_rs__bgworker__sqlt__tests__sqlt_loco_queue.snap │ │ │ ├── loco_rs__bgworker__sqlt__tests__sqlt_loco_queue_lock.snap │ │ │ └── loco_rs__bgworker__tests__can_dump_jobs.snap │ │ └── sqlt.rs │ ├── boot.rs │ ├── cache/ │ │ ├── drivers/ │ │ │ ├── inmem.rs │ │ │ ├── mod.rs │ │ │ ├── null.rs │ │ │ └── redis.rs │ │ └── mod.rs │ ├── cargo_config.rs │ ├── cli.rs │ ├── config.rs │ ├── controller/ │ │ ├── app_routes.rs │ │ ├── backtrace.rs │ │ ├── describe.rs │ │ ├── extractor/ │ │ │ ├── auth.rs │ │ │ ├── mod.rs │ │ │ ├── shared_store.rs │ │ │ ├── snapshots/ │ │ │ │ ├── loco_rs__controller__extractor__auth__tests__extract_from_bearer.snap │ │ │ │ ├── loco_rs__controller__extractor__auth__tests__extract_from_cookie.snap │ │ │ │ ├── loco_rs__controller__extractor__auth__tests__extract_from_default.snap │ │ │ │ ├── loco_rs__controller__extractor__auth__tests__extract_from_multiple_locations.snap │ │ │ │ └── loco_rs__controller__extractor__auth__tests__extract_from_query.snap │ │ │ └── validate.rs │ │ ├── format.rs │ │ ├── middleware/ │ │ │ ├── _archive/ │ │ │ │ └── content_etag.rs │ │ │ ├── catch_panic.rs │ │ │ ├── compression.rs │ │ │ ├── cors.rs │ │ │ ├── etag.rs │ │ │ ├── fallback.html │ │ │ ├── fallback.rs │ │ │ ├── format.rs │ │ │ ├── limit_payload.rs │ │ │ ├── logger.rs │ │ │ ├── mod.rs │ │ │ ├── powered_by.rs │ │ │ ├── remote_ip.rs │ │ │ ├── request_id.rs │ │ │ ├── secure_headers.json │ │ │ ├── secure_headers.rs │ │ │ ├── snapshots/ │ │ │ │ ├── loco_rs__controller__middleware__cors__tests__cors_OPTIONS_[allow_origins].snap │ │ │ │ ├── loco_rs__controller__middleware__cors__tests__cors_[default].snap │ │ │ │ ├── loco_rs__controller__middleware__cors__tests__cors_[with_allow_headers].snap │ │ │ │ ├── loco_rs__controller__middleware__cors__tests__cors_[with_allow_methods].snap │ │ │ │ ├── loco_rs__controller__middleware__cors__tests__cors_[with_expose_headers].snap │ │ │ │ ├── loco_rs__controller__middleware__cors__tests__cors_[with_max_age].snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing-2.snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing-3.snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing-4.snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing-5.snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing-6.snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing-7.snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing-8.snap │ │ │ │ ├── loco_rs__controller__middleware__remote_ip__tests__parsing.snap │ │ │ │ ├── loco_rs__controller__middleware__request_id__tests__create_or_fetch_request_id-2.snap │ │ │ │ ├── loco_rs__controller__middleware__request_id__tests__create_or_fetch_request_id-3.snap │ │ │ │ ├── loco_rs__controller__middleware__request_id__tests__create_or_fetch_request_id-4.snap │ │ │ │ ├── loco_rs__controller__middleware__request_id__tests__create_or_fetch_request_id-5.snap │ │ │ │ ├── loco_rs__controller__middleware__request_id__tests__create_or_fetch_request_id.snap │ │ │ │ ├── loco_rs__controller__middleware__secure_headers__tests__can_override_headers.snap │ │ │ │ ├── loco_rs__controller__middleware__secure_headers__tests__can_set_headers.snap │ │ │ │ └── loco_rs__controller__middleware__secure_headers__tests__default_is_github_preset.snap │ │ │ ├── static_assets.rs │ │ │ ├── static_assets_embedded.rs │ │ │ └── timeout.rs │ │ ├── mod.rs │ │ ├── monitoring.rs │ │ ├── routes.rs │ │ ├── snapshots/ │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]_health].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]_ping].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]_readiness].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]api[slash]loco-rs].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]api[slash]loco].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]api[slash]v1[slash]notes].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]api[slash]v1[slash]users].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]multiple1].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]multiple2].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]multiple3].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]loco[slash]rs].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-end].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]multiple-start].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]normalizer[slash]no-slash].snap │ │ │ ├── loco_rs__controller__app_routes__tests__[[slash]normalizer].snap │ │ │ ├── loco_rs__controller__format__tests__builder_cookies_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_empty_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_html_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_json_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_redirect_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_redirect_with_custom_header_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_template_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_text_response.snap │ │ │ ├── loco_rs__controller__format__tests__builder_view_response-2.snap │ │ │ ├── loco_rs__controller__format__tests__builder_view_response.snap │ │ │ ├── loco_rs__controller__format__tests__empty_json_response_format.snap │ │ │ ├── loco_rs__controller__format__tests__empty_response_format.snap │ │ │ ├── loco_rs__controller__format__tests__html_response_format.snap │ │ │ ├── loco_rs__controller__format__tests__json_response_format.snap │ │ │ ├── loco_rs__controller__format__tests__redirect_response.snap │ │ │ ├── loco_rs__controller__format__tests__template_response.snap │ │ │ ├── loco_rs__controller__format__tests__text_response_format.snap │ │ │ ├── loco_rs__controller__format__tests__view_response-2.snap │ │ │ ├── loco_rs__controller__format__tests__view_response.snap │ │ │ └── loco_rs__controller__format__tests__yaml_response_format.snap │ │ └── views/ │ │ ├── engine.rs │ │ ├── engine_embedded.rs │ │ ├── mod.rs │ │ ├── pagination.rs │ │ └── tera_builtins/ │ │ ├── filters/ │ │ │ ├── mod.rs │ │ │ └── number.rs │ │ └── mod.rs │ ├── data.rs │ ├── db.rs │ ├── depcheck.rs │ ├── doctor.rs │ ├── env_vars.rs │ ├── environment.rs │ ├── errors.rs │ ├── hash.rs │ ├── initializers/ │ │ ├── extra_db.rs │ │ ├── mod.rs │ │ └── multi_db.rs │ ├── lib.rs │ ├── logger.rs │ ├── mailer/ │ │ ├── email_sender.rs │ │ ├── mod.rs │ │ ├── snapshots/ │ │ │ ├── loco_rs__mailer__email_sender__tests__can_send_email.snap │ │ │ ├── loco_rs__mailer__email_sender__tests__can_send_email_with_custom_headers.snap │ │ │ └── loco_rs__mailer__template__tests__can_render_template.snap │ │ └── template.rs │ ├── model/ │ │ ├── mod.rs │ │ └── query/ │ │ ├── dsl/ │ │ │ ├── date_range.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── paginate/ │ │ └── mod.rs │ ├── prelude.rs │ ├── scheduler.rs │ ├── schema.rs │ ├── snapshots/ │ │ ├── loco_rs__auth__tests__token expired.snap │ │ ├── loco_rs__auth__tests__valid token.snap │ │ ├── loco_rs__db__tests__dump_tables_sqlite_all_types.snap │ │ ├── loco_rs__db__tests__dump_tables_sqlite_all_types_roundtrip.snap │ │ ├── loco_rs__scheduler__tests__can_display_scheduler.snap │ │ ├── loco_rs__scheduler__tests__can_prepare_command_[shell].snap │ │ ├── loco_rs__scheduler__tests__can_prepare_command_[task].snap │ │ ├── loco_rs__validation__tests__struct-[foo-bar].snap │ │ ├── loco_rs__validation__tests__struct-[foo].snap │ │ ├── loco_rs__worker__tests__default_custom_queues-2.snap │ │ ├── loco_rs__worker__tests__default_custom_queues-3.snap │ │ └── loco_rs__worker__tests__default_custom_queues.snap │ ├── storage/ │ │ ├── contents.rs │ │ ├── drivers/ │ │ │ ├── aws.rs │ │ │ ├── azure.rs │ │ │ ├── gcp.rs │ │ │ ├── local.rs │ │ │ ├── mem.rs │ │ │ ├── mod.rs │ │ │ ├── null.rs │ │ │ └── opendal_adapter.rs │ │ ├── mod.rs │ │ ├── strategies/ │ │ │ ├── backup.rs │ │ │ ├── mirror.rs │ │ │ ├── mod.rs │ │ │ └── single.rs │ │ └── stream.rs │ ├── task.rs │ ├── tera.rs │ ├── testing/ │ │ ├── db.rs │ │ ├── mod.rs │ │ ├── prelude.rs │ │ ├── redaction.rs │ │ ├── request.rs │ │ └── selector.rs │ ├── tests_cfg/ │ │ ├── app.rs │ │ ├── config.rs │ │ ├── controllers/ │ │ │ ├── auth.rs │ │ │ ├── home.rs │ │ │ └── mod.rs │ │ ├── db.rs │ │ ├── mod.rs │ │ ├── postgres.rs │ │ ├── queue.rs │ │ ├── redis.rs │ │ └── task.rs │ └── validation.rs ├── tests/ │ ├── build_scripts/ │ │ ├── embedded_assets.rs │ │ ├── mod.rs │ │ └── snapshots/ │ │ ├── r#mod__build_scripts__embedded_assets__build_static_assets_static.snap │ │ ├── r#mod__build_scripts__embedded_assets__build_static_assets_templates.snap │ │ ├── r#mod__build_scripts__embedded_assets__collected_all_files.snap │ │ ├── r#mod__build_scripts__embedded_assets__collected_css_files.snap │ │ ├── r#mod__build_scripts__embedded_assets__complex_template_inheritance.snap │ │ ├── r#mod__build_scripts__embedded_assets__discovered_directories.snap │ │ ├── r#mod__build_scripts__embedded_assets__empty_static_assets_rs.snap │ │ ├── r#mod__build_scripts__embedded_assets__empty_templates_rs.snap │ │ ├── r#mod__build_scripts__embedded_assets__static_assets_rs.snap │ │ ├── r#mod__build_scripts__embedded_assets__template_inheritance.snap │ │ └── r#mod__build_scripts__embedded_assets__view_templates_rs.snap │ ├── controller/ │ │ ├── extractor/ │ │ │ ├── auth/ │ │ │ │ ├── api_token.rs │ │ │ │ ├── jwt.rs │ │ │ │ ├── jwt_with_user.rs │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ ├── shared_store.rs │ │ │ └── validate.rs │ │ ├── from_ref.rs │ │ ├── into_response.rs │ │ ├── middlewares.rs │ │ ├── mod.rs │ │ └── snapshots/ │ │ ├── cors_[default]@middlewares.snap │ │ ├── cors_[disabled]@middlewares.snap │ │ ├── cors_[with_allow_headers]@middlewares.snap │ │ ├── cors_[with_allow_methods]@middlewares.snap │ │ ├── cors_[with_max_age]@middlewares.snap │ │ ├── panic@middlewares.snap │ │ ├── secure_headers_[empty]_overrides[none]@middlewares.snap │ │ ├── secure_headers_[github]_overrides[Content-Security-Policy]@middlewares.snap │ │ └── secure_headers_[none]_overrides[none]@middlewares.snap │ ├── fixtures/ │ │ ├── email_template/ │ │ │ └── test/ │ │ │ ├── html.t │ │ │ ├── subject.t │ │ │ └── text.t │ │ └── queue/ │ │ └── jobs.yaml │ ├── infra_cfg/ │ │ ├── mod.rs │ │ └── server.rs │ └── mod.rs └── xtask/ ├── .rustfmt.toml ├── Cargo.toml ├── README.md └── src/ ├── bin/ │ └── main.rs ├── bump_version.rs ├── ci.rs ├── errors.rs ├── lib.rs ├── out.rs ├── prompt.rs ├── utils.rs └── versions.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .cargo/config.toml ================================================ [alias] xtask = "run --package xtask --" # https://github.com/rust-lang/rust/issues/141626 # (can be removed once link.exe is fixed) [target.x86_64-pc-windows-msvc] linker = "rust-lld" ================================================ FILE: .clippy.toml ================================================ cognitive-complexity-threshold = 40 ================================================ FILE: .gitattributes ================================================ * linguist-vendored *.rs linguist-vendored=false ================================================ FILE: .github/FUNDING.yml ================================================ github: loco-rs ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug Report about: Report behavior that deviates from specification or expectation title: "" labels: assessment assignees: "" --- **Description** Provide a clear and concise explanation of the bug, including references to any conflicting documentation or an elucidation of why the current functionality doesn't align with your expectations. **To Reproduce** Please outline the steps to replicate the bug, and if possible, provide a comprehensive code example consisting of both `main.rs` and `Cargo.toml`` files. A complete and functional code snippet would be highly appreciated. **Expected Behavior** A clear and concise description of what you expected to happen. **Environment:** **Additional Context** Include additional context about the issue in this section. For instance, share insights into the bug's discovery process or any hypotInclude additional context about the issue in this section. For instance, share insights into the bug's discovery process or any hypotheses you may have regarding the root cause or specific aspects where Loco may be malfunctioning. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: Question url: https://github.com/loco-rs/loco/discussions about: Please ask questions or raise indefinite concerns on Discussions ================================================ FILE: .github/ISSUE_TEMPLATE/feature-request.md ================================================ --- name: Feature Request about: Suggest a new feature title: "" labels: enhancement assignees: "" --- ## Feature Request **Is your feature request related to a problem? Please describe.** Provide a succinct and clear description of the problem. For example, I encounter an issue when... **Describe the solution you'd like** Articulate a clear and concise depiction of your desired outcome. **Describe alternatives you've considered** Offer a clear and concise description of any alternative solutions or features you have contemplated. ================================================ FILE: .github/ISSUE_TEMPLATE/suggestion.md ================================================ --- name: Suggestion about: Suggest a change or improvement to existing functionality title: "" labels: assessment assignees: "" --- **Description** Please provide a well-defined and succinct explanation of the issue. Include references to any conflicting documentation or clarify why the current functionality deviates from your expectations. **To Reproduce** Please outline the steps to replicate the bug, and if possible, provide a comprehensive code example consisting of both `main.rs` and `Cargo.toml`` files. A complete and functional code snippet would be highly appreciated. **Expected Behavior** Provide a straightforward and brief explanation of your anticipated outcome. **Environment:** **Additional Context** Include additional details about the issue in this section. For instance, share insights into how you discovered the bug or any hypotheses you may have regarding what might be causing the problem with Loco. ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "cargo" directory: "/" schedule: interval: "daily" open-pull-requests-limit: 0 - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/workflows/docs.yml ================================================ name: "[docs]" on: push: branches: - master pull_request: env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: check: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - run: cargo install snipdoc --features exec - run: snipdoc check continue-on-error: true env: SNIPDOC_SKIP_EXEC_COMMANDS: true ================================================ FILE: .github/workflows/loco-gen-ci.yml ================================================ name: "[loco-gen:ci]" on: push: branches: - master paths: - "loco-gen/**" pull_request: paths: - "loco-gen/**" env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal defaults: run: working-directory: ./loco-gen jobs: style: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} components: rustfmt - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo fmt run: cargo fmt --all -- --check - name: Run cargo clippy run: cargo clippy --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms test: needs: [style] runs-on: ubuntu-latest permissions: contents: read services: postgres: image: postgres env: POSTGRES_DB: postgres_test POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres ports: - "5432:5432" # Set health checks to wait until postgres has started options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Install seaorm cli run: cargo install sea-orm-cli - run: | cargo install --path ../loco-new - name: Run cargo test run: cargo test --all-features env: LOCO_DEV_MODE_PATH: ${{ github.workspace }} DATABASE_URL: postgres://postgres:postgres@localhost:5432/postgres_test ================================================ FILE: .github/workflows/loco-gen-deploy.yml ================================================ name: "[loco-gen-deploy]" on: schedule: - cron: "0 0 * * *" env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: g-deploy-docker: # This workflow creates a new Loco application and builds a Docker image # We only want this to run on the main repository (loco-rs/loco) and not on forks because: # 1. It consumes GitHub Actions minutes unnecessarily on forks # 2. The Docker build and deployment is specific to the main repository # 3. Forks typically don't need to run this automated deployment process if: github.repository == 'loco-rs/loco' runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Install seaorm cli run: cargo install sea-orm-cli - name: install 'loco new' run: | cargo install loco - name: create myapp run: | loco new -n myapp --db sqlite --bg async --assets serverside -a - name: run: cargo loco generate deployment docker && docker build -t demo . working-directory: ./myapp ================================================ FILE: .github/workflows/loco-new.yml ================================================ name: "[loco-new:ci]" on: push: branches: - master paths: - "loco-new/**" - "loco-gen/**" pull_request: paths: - "loco-new/**" - "loco-gen/**" env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: style: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} components: rustfmt - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - run: cargo fmt --all -- --check working-directory: ./loco-new - name: Run cargo clippy run: cargo clippy --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms working-directory: ./loco-new test: # needs: [style] runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, windows-latest] permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Configure sccache run: | echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV - name: Run sccache-cache uses: mozilla-actions/sccache-action@v0.0.9 - name: Install seaorm cli run: cargo install sea-orm-cli - name: Free Disk Space if: ${{ matrix.os == 'ubuntu-latest' }} uses: jlumbroso/free-disk-space@main with: tool-cache: false - name: Run cargo test run: cargo test --all-features -- --test-threads 1 working-directory: ./loco-new env: LOCO_DEV_MODE_PATH: ${{ github.workspace }} # NOTE NOTE NOTE: this is for optimizing build and may result in strange behavior CARGO_TARGET_DIR: /tmp/shared-target ================================================ FILE: .github/workflows/loco-rs-ci-sanity.yml ================================================ # To optimize CI runtime: # A simpler "sanity check" workflow is introduced. # This workflow only runs if changes in the PR do NOT include # the `loco-gen` or `loco-new` paths. # (When changes are made to `loco-gen` or `loco-new`, # we run comprehensive tests to validate every generator command # and template option.) # Purpose of the sanity check: # It performs basic validation by comparing the local changes # against the templates. # If any breaking changes are detected in the templates, # the sanity check will fail, signaling an issue. name: "[loco_rs:sanity]" on: push: branches: - master pull_request: env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: sanity: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Install seaorm cli run: cargo install sea-orm-cli - run: cargo install --path loco-new - run: | loco new -n myappdb --db sqlite --bg async --assets serverside -a cd myappdb cargo check cargo build --release env: LOCO_DEV_MODE_PATH: ${{ github.workspace }} - run: | loco new -n myapp --db none --bg async --assets none -a cd myapp cargo check cargo build --release env: LOCO_DEV_MODE_PATH: ${{ github.workspace }} ================================================ FILE: .github/workflows/loco-rs-ci.yml ================================================ name: "[loco_rs:ci]" on: push: branches: - master pull_request: env: RUST_TOOLCHAIN: stable TOOLCHAIN_PROFILE: minimal jobs: style: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} components: rustfmt - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo fmt run: cargo fmt --all -- --check - name: Run cargo clippy run: cargo clippy --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms check: needs: [style] runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - uses: taiki-e/install-action@v2 with: tool: cargo-hack - run: cargo hack check --each-feature build: needs: [check, style] runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo build run: cargo build --release test: needs: [check, style] runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout the code uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@stable with: toolchain: ${{ env.RUST_TOOLCHAIN }} - name: Setup Rust cache uses: Swatinem/rust-cache@v2 - name: Run cargo test run: cargo test --all-features --workspace --exclude loco-gen --exclude loco ================================================ FILE: .gitignore ================================================ # local dev todo.txt todo.md examples/demo2 examples/myapp *.sqlite *.sqlite-wal *.sqlite-shm *.sqlite3 *.sqlite3-wal *.sqlite3-shm # IDE config files .idea .vscode **/config/local.yaml **/config/*.local.yaml # Local Netlify folder .netlify ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Rust ### # Generated by Cargo # will have compiled files and executables target/ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk ## Frontend # Various node lock files; so people don't accidentally commit them while # updating the template starters/saas/frontend/package-lock.json starters/saas/frontend/yarn.lock starters/saas/frontend/pnpm-lock.yaml example ================================================ FILE: .rustfmt.toml ================================================ max_width = 100 use_small_heuristics = "Default" ================================================ FILE: CHANGELOG.md ================================================ # Changelog ## Unreleased - Fix `cargo fmt` error in `loco-new` ([#1669](https://github.com/loco-rs/loco/pull/1669)) - Fix UUID pattern in form field generation ([#1665](https://github.com/loco-rs/loco/pull/1665)) - Add tests for auth extractor ([#1671](https://github.com/loco-rs/loco/pull/1671)) - Fix Clippy warnings for Rust 1.92 ([#1705](https://github.com/loco-rs/loco/pull/1705)) - Add email headers support to mailer ([#1700](https://github.com/loco-rs/loco/pull/1700)) - Wrap `TeraView` in `Arc` to reduce runtime memory usage ([#1703](https://github.com/loco-rs/loco/pull/1703)) - Allow overriding a secure header ([#1659](https://github.com/loco-rs/loco/pull/1659)) - Add “create user” task ([#1670](https://github.com/loco-rs/loco/pull/1670)) - Add `UuidUniqWithDefault` and `UuidWithDefault` types ([#1642](https://github.com/loco-rs/loco/pull/1642)) - Refactor users model to reuse `find_by_api_key` in `Authenticable` ([#1706](https://github.com/loco-rs/loco/pull/1706)) - Split error detail generic parameters ([#1709](https://github.com/loco-rs/loco/pull/1709)) - Update `loco-new` for new Rhai version ([#1704](https://github.com/loco-rs/loco/pull/1704)) ### Breaking Changes In file `src/initializers/view_engine.rs`, modify the code lines in `after_routes`: Before ```rust async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { : engines::TeraView::build()?.post_process(move |tera| { : ``` After (use `build_with_post_process` instead of `post_process`) ```rust async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { : engines::TeraView::build_with_post_process(move |tera| { : } ``` ## v0.16.4 - Feat: decouple JWT authentication from database dependency. [https://github.com/loco-rs/loco/pull/1546](https://github.com/loco-rs/loco/pull/1546) - Fix: add sqlx dependency to with-db feature. [https://github.com/loco-rs/loco/pull/1557](https://github.com/loco-rs/loco/pull/1557) - Remove the deprecated `--link` generate command and fix the table name creation. [https://github.com/loco-rs/loco/pull/1556](https://github.com/loco-rs/loco/pull/1556) - Support underscore for migration join table. [https://github.com/loco-rs/loco/pull/1562](https://github.com/loco-rs/loco/pull/1562) - Fix: resolve deployment CLI argument parsing issue. [https://github.com/loco-rs/loco/pull/1566](https://github.com/loco-rs/loco/pull/1566) - Add database enum support (Postgres only). [https://github.com/loco-rs/loco/pull/1593](https://github.com/loco-rs/loco/pull/1568) - Remove duplicated #[async_trait::async_trait]. [https://github.com/loco-rs/loco/pull/1593](https://github.com/loco-rs/loco/pull/1572) - Clippy fixes for Rust 1.89. [https://github.com/loco-rs/loco/pull/1593](https://github.com/loco-rs/loco/pull/1593) - Improvement: do not hot-reload unless files have changed. [https://github.com/loco-rs/loco/pull/1552](https://github.com/loco-rs/loco/pull/1552) - Feat: add --without-tz flag for controlling timestamp generation. [https://github.com/loco-rs/loco/pull/1592](https://github.com/loco-rs/loco/pull/1592) - Support extra fields when generating the join table migration. [https://github.com/loco-rs/loco/pull/1595](https://github.com/loco-rs/loco/pull/1595) - Convert validator to trait-based API (add ValidatorTrait, keep derive adapter, update docs). [https://github.com/loco-rs/loco/pull/1597](https://github.com/loco-rs/loco/pull/1597) - Rename dockerfile to Dockerfile. [https://github.com/loco-rs/loco/pull/1574](https://github.com/loco-rs/loco/pull/1574) - Enable edit CORS expose headers. [https://github.com/loco-rs/loco/pull/1599](https://github.com/loco-rs/loco/pull/1599) - Adding new imports about multipart. [https://github.com/loco-rs/loco/pull/1600](https://github.com/loco-rs/loco/pull/1600) - Adding readiness default endpoint. [https://github.com/loco-rs/loco/pull/1563](https://github.com/loco-rs/loco/pull/1563) - Add Route methods to make collecting and nesting easier. [https://github.com/loco-rs/loco/pull/1608](https://github.com/loco-rs/loco/pull/1608) - Add streaming support for both download and upload. [https://github.com/loco-rs/loco/pull/1610](https://github.com/loco-rs/loco/pull/1610) - Fix Clippy for Rust 1.90. [https://github.com/loco-rs/loco/pull/1630](https://github.com/loco-rs/loco/pull/1630) - Loco CLI: Update rhai version. [https://github.com/loco-rs/loco/pull/1631](https://github.com/loco-rs/loco/pull/1631) ## v0.16.3 - Support nullable foreign keys with `references?` syntax. [https://github.com/loco-rs/loco/pull/1544](https://github.com/loco-rs/loco/pull/1544) - **HOTFIX**: **Breaking changes** Fixed a critical issue introduced in version `v0.16.2` that caused `cargo build --release` to fail after merging #1540. [https://github.com/loco-rs/loco/pull/1551](https://github.com/loco-rs/loco/pull/1551) - Add an API to re-send verification mail. [https://github.com/loco-rs/loco/pull/1456](https://github.com/loco-rs/loco/pull/1456) - Adding to ci cargo build --release. [https://github.com/loco-rs/loco/pull/1553](https://github.com/loco-rs/loco/pull/1553) ### Breaking Changes In file `src/initializers/view_engine.rs`, modify the method `after_routes`: Before ```rust async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { #[allow(unused_mut)] let mut tera_engine = engines::TeraView::build()?; if std::path::Path::new(I18N_DIR).exists() { let arc = ArcLoader::builder(&I18N_DIR, unic_langid::langid!("en-US")) .shared_resources(Some(&[I18N_SHARED.into()])) .customize(|bundle| bundle.set_use_isolating(false)) .build() .map_err(|e| Error::string(&e.to_string()))?; #[cfg(debug_assertions)] tera_engine .tera .lock() .expect("lock") .register_function("t", FluentLoader::new(arc)); #[cfg(not(debug_assertions))] tera_engine .tera .register_function("t", FluentLoader::new(arc)); info!("locales loaded"); } Ok(router.layer(Extension(ViewEngine::from(tera_engine)))) } ``` After (use `post_process` to add i18n initialization code) ```rust async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { let tera_engine = if std::path::Path::new(I18N_DIR).exists() { let arc = std::sync::Arc::new( ArcLoader::builder(&I18N_DIR, unic_langid::langid!("en-US")) .shared_resources(Some(&[I18N_SHARED.into()])) .customize(|bundle| bundle.set_use_isolating(false)) .build() .map_err(|e| Error::string(&e.to_string()))?, ); info!("locales loaded"); engines::TeraView::build()?.post_process(move |tera| { tera.register_function("t", FluentLoader::new(arc.clone())); Ok(()) })? } else { engines::TeraView::build()? }; Ok(router.layer(Extension(ViewEngine::from(tera_engine)))) } ``` ## v0.16.2 - Update auth import in the Authentication document. [https://github.com/loco-rs/loco/pull/1531](https://github.com/loco-rs/loco/pull/1531) - Adding cache control header to the static asset middleware. [https://github.com/loco-rs/loco/pull/1535](https://github.com/loco-rs/loco/pull/1535) - Fix borrow checker when sending config to handle_job_command when feature with-db is off. [https://github.com/loco-rs/loco/pull/1536](https://github.com/loco-rs/loco/pull/1536) - feat: add initializer health checks to doctor command. [https://github.com/loco-rs/loco/pull/1537](https://github.com/loco-rs/loco/pull/1537) - Update shuttle template to 0.56. [https://github.com/loco-rs/loco/pull/1518](https://github.com/loco-rs/loco/pull/1518) - Encapsulate post-processing into Tera engine creation. [https://github.com/loco-rs/loco/pull/1540](https://github.com/loco-rs/loco/pull/1540) - Adding QueryValidateWithMessage. [https://github.com/loco-rs/loco/pull/1521](https://github.com/loco-rs/loco/pull/1521) - Add S3 driver with credentials and endpoint support. [https://github.com/loco-rs/loco/pull/1539](https://github.com/loco-rs/loco/pull/1539) ## v0.16.1 - fix clippy result_large_err. [https://github.com/loco-rs/loco/pull/1496](https://github.com/loco-rs/loco/pull/1496) - chore: remove async-std. [https://github.com/loco-rs/loco/pull/1492](https://github.com/loco-rs/loco/pull/1492) - fix: Bump shuttle version to 0.55.0. [https://github.com/loco-rs/loco/pull/1488](https://github.com/loco-rs/loco/pull/1488) - Change the Docker building image to 1.87. [https://github.com/loco-rs/loco/pull/1475](https://github.com/loco-rs/loco/pull/1475) - Fix Clippy warnings for Rust 1.88 stable. [https://github.com/loco-rs/loco/pull/1519](https://github.com/loco-rs/loco/pull/1519) - Remove Migrator from boot_test_* doc comments. [https://github.com/loco-rs/loco/pull/1512](https://github.com/loco-rs/loco/pull/1512) - fix: use rust-lld linker on Windows. [https://github.com/loco-rs/loco/pull/1508](https://github.com/loco-rs/loco/pull/1508) - Fix precompressed in static assets. [https://github.com/loco-rs/loco/pull/1524](https://github.com/loco-rs/loco/pull/1524) - Support multiple JWT locations. [https://github.com/loco-rs/loco/pull/1497](https://github.com/loco-rs/loco/pull/1497) ## v0.16.0 **Note:** For detailed upgrade steps for breaking changes, see the [upgrade guide](https://loco.rs/docs/extras/upgrades/#upgrade-from-0-15-x-to-0-16-x). - chore: improve readability and performance by using map_err in Model. [https://github.com/loco-rs/loco/pull/1311](https://github.com/loco-rs/loco/pull/1311) - Allow testing the controller by passing a cookie. [https://github.com/loco-rs/loco/pull/1326](https://github.com/loco-rs/loco/pull/1326) - Support BigInt in the scaffold Array. [https://github.com/loco-rs/loco/pull/1304](https://github.com/loco-rs/loco/pull/1304) - Add `escape` Tera function to the scaffold list template. [https://github.com/loco-rs/loco/pull/1337](https://github.com/loco-rs/loco/pull/1337) - Return a specific error when logging in with a non-existent email. [https://github.com/loco-rs/loco/pull/1336](https://github.com/loco-rs/loco/pull/1336) - Return a specific error when trying to verify with an invalid token. [https://github.com/loco-rs/loco/pull/1340](https://github.com/loco-rs/loco/pull/1340) - Clippy 1.86. [https://github.com/loco-rs/loco/pull/1353](https://github.com/loco-rs/loco/pull/1353) - Fix the DB creation. [https://github.com/loco-rs/loco/pull/1352](https://github.com/loco-rs/loco/pull/1352) - YAML responses. [https://github.com/loco-rs/loco/pull/1360](https://github.com/loco-rs/loco/pull/1360) - Swap to the validators' built-in email validation. [https://github.com/loco-rs/loco/pull/1359](https://github.com/loco-rs/loco/pull/1359) - Cancellation tokens for the Postgres and SQLite background workers. [https://github.com/loco-rs/loco/pull/1365](https://github.com/loco-rs/loco/pull/1365) - docs: testing auth routes. [https://github.com/loco-rs/loco/pull/1303](https://github.com/loco-rs/loco/pull/1303) - Add comprehensive tests for the task module. [https://github.com/loco-rs/loco/pull/1386](https://github.com/loco-rs/loco/pull/1386) - Add comprehensive test coverage for the data module. [https://github.com/loco-rs/loco/pull/1387](https://github.com/loco-rs/loco/pull/1387) - Add validator extractors test suite. [https://github.com/loco-rs/loco/pull/1388](https://github.com/loco-rs/loco/pull/1388) - **Breaking changes** Replace sidekiq job management: existing Redis jobs incompatible. [https://github.com/loco-rs/loco/pull/1384](https://github.com/loco-rs/loco/pull/1384) - **Breaking changes** Add generic type support to the Cache API: cache method calls need type parameters. [https://github.com/loco-rs/loco/pull/1385](https://github.com/loco-rs/loco/pull/1385) - Adding cache redis driver + configuration instead of enabling from code. [https://github.com/loco-rs/loco/pull/1389](https://github.com/loco-rs/loco/pull/1389) - Ability to configure pragma for SQLite. [https://github.com/loco-rs/loco/pull/1346](https://github.com/loco-rs/loco/pull/1346) - **Breaking changes** swap to validators builtin email validation: custom email validator syntax changed. [https://github.com/loco-rs/loco/pull/1359](https://github.com/loco-rs/loco/pull/1359) - Optimize worker tag filtering string handling. [https://github.com/loco-rs/loco/pull/1396](https://github.com/loco-rs/loco/pull/1396) - Add test coverage for db.rs. [https://github.com/loco-rs/loco/pull/1400](https://github.com/loco-rs/loco/pull/1400) - Allow storage of arbitrary custom objects in AppContext. [https://github.com/loco-rs/loco/pull/1404](https://github.com/loco-rs/loco/pull/1404) - Improve deployment generator CLI. [https://github.com/loco-rs/loco/pull/1413](https://github.com/loco-rs/loco/pull/1413) - Move auth and validate to the extractor folder. [https://github.com/loco-rs/loco/pull/1414](https://github.com/loco-rs/loco/pull/1414) - Hot reload on extended Tera templates. [https://github.com/loco-rs/loco/pull/1416](https://github.com/loco-rs/loco/pull/1416) - **Breaking changes** Update the `init_logger` to use `AppContext` instead of config: function signature changed. [https://github.com/loco-rs/loco/pull/1418](https://github.com/loco-rs/loco/pull/1418) - Support embedded assets. [https://github.com/loco-rs/loco/pull/1427](https://github.com/loco-rs/loco/pull/1427) - **Removed dependencies:** - [`hyper`](https://github.com/loco-rs/loco/pull/1430) - [`thousands`](https://github.com/loco-rs/loco/pull/1431) - [`cfg-if`](https://github.com/loco-rs/loco/pull/1432) - [`reqwest`](https://github.com/loco-rs/loco/pull/1434) - [`serde_variant`](https://github.com/loco-rs/loco/pull/1493) * **Dependency updates:** - Bumped [`tokio`] to `1.45` and [`tokio-util`] to `0.7` ([#1435](https://github.com/loco-rs/loco/pull/1435)) - Bumped [`colored`] to `3.0` ([#1437](https://github.com/loco-rs/loco/pull/1437)) - Bumped [`rand`] to `0.9` ([#1439](https://github.com/loco-rs/loco/pull/1439)) - Bumped [`duct`] to `1.0` ([#1438](https://github.com/loco-rs/loco/pull/1438)) - Bumped [`redis`] to `0.31`, [`bb8`] to `0.9`, and [`bb8-redis`] to `0.23` ([commit `7e7be`](https://github.com/loco-rs/loco/commit/7e7bebe15f74c377c93d979aab41c52eb871d667)) - Updated Loco template crates ([#1440](https://github.com/loco-rs/loco/pull/1440)) - Support custom flags from `sea-orm entity`. [https://github.com/loco-rs/loco/pull/1442](https://github.com/loco-rs/loco/pull/1442) - Better `loco new` cleanup folders. [https://github.com/loco-rs/loco/pull/1429](https://github.com/loco-rs/loco/pull/1429) - Remove legacy mailer derive macro code. [https://github.com/loco-rs/loco/pull/1472](https://github.com/loco-rs/loco/pull/1472) - Make extract_token and get_jwt_from_config fn public. [https://github.com/loco-rs/loco/pull/1495](https://github.com/loco-rs/loco/pull/1495) ## v0.15.0 - Added total_items to pagination view & response. [https://github.com/loco-rs/loco/pull/1197](https://github.com/loco-rs/loco/pull/1197) - Flatten (de)serialization of custom user claims. [https://github.com/loco-rs/loco/pull/1159](https://github.com/loco-rs/loco/pull/1159) - Updated validator to 0.20. [https://github.com/loco-rs/loco/pull/1199](https://github.com/loco-rs/loco/pull/1199) - Scaffold v2. [https://github.com/loco-rs/loco/pull/1209](https://github.com/loco-rs/loco/pull/1209) - Fix generator Docker deployment to support both server-side and client-side rendering. [https://github.com/loco-rs/loco/pull/1227](https://github.com/loco-rs/loco/pull/1227) - Docs: num_workers worker configuration. [https://github.com/loco-rs/loco/pull/1242](https://github.com/loco-rs/loco/pull/1242) - Smoother model validations. [https://github.com/loco-rs/loco/pull/1233](https://github.com/loco-rs/loco/pull/1233) - Docs: num_workers worker configuration. [https://github.com/loco-rs/loco/pull/1242](https://github.com/loco-rs/loco/pull/1242) - Ignore SQLite WAL and SHM files and update Cargo watch crate docs. [https://github.com/loco-rs/loco/pull/1254](https://github.com/loco-rs/loco/pull/1254) - Remove fs-err crate. [https://github.com/loco-rs/loco/pull/1253](https://github.com/loco-rs/loco/pull/1253) - Allows to run scheduler as part of cargo loco start. [https://github.com/loco-rs/loco/pull/1247](https://github.com/loco-rs/loco/pull/1247) - Added prefix and route nesting to AppRoutes. [https://github.com/loco-rs/loco/pull/1241](https://github.com/loco-rs/loco/pull/1241) - Replace hyper crate with axum. [https://github.com/loco-rs/loco/pull/1258](https://github.com/loco-rs/loco/pull/1258) - Remove mime crate. [https://github.com/loco-rs/loco/pull/1256](https://github.com/loco-rs/loco/pull/1256) - Support async tests. [https://github.com/loco-rs/loco/pull/1237](https://github.com/loco-rs/loco/pull/1237) - Change job queue status from cli. [https://github.com/loco-rs/loco/pull/1228](https://github.com/loco-rs/loco/pull/1228) - Handle panics in queue worker. [https://github.com/loco-rs/loco/pull/1274](https://github.com/loco-rs/loco/pull/1274) - Schema with defaults. [https://github.com/loco-rs/loco/pull/1273](https://github.com/loco-rs/loco/pull/1273) - Add data subsystem. [https://github.com/loco-rs/loco/pull/1267](https://github.com/loco-rs/loco/pull/1267) - Add "endpoint" arg to azure storage builder.[https://github.com/loco-rs/loco/pull/1317](https://github.com/loco-rs/loco/pull/1317) - Improve readability and performance by using map_err in Model. [https://github.com/loco-rs/loco/pull/1311](https://github.com/loco-rs/loco/pull/1311) ### Breaking Changes In module `loco_rs::auth::jwt` in struct `JWT`, the impl method `generate_token` signature has changed. Migration: Before ```rust jwt.generate_token(&expiration, pid.clone(), None); ``` After ```rust jwt.generate_token(expiration, pid.clone(), Map::new()); // ^ no "&" ^ serde_json::map (doesn't allocate in constructor) ``` ## v0.14.1 - Fix: bump shuttle to 0.51.0. [https://github.com/loco-rs/loco/pull/1169](https://github.com/loco-rs/loco/pull/1169) - Return 422 status code for JSON rejection errors. [https://github.com/loco-rs/loco/pull/1173](https://github.com/loco-rs/loco/pull/1173) - Address clippy warnings for Rust stable 1.84. [https://github.com/loco-rs/loco/pull/1168](https://github.com/loco-rs/loco/pull/1168) - Bump shuttle to 0.51.0. [https://github.com/loco-rs/loco/pull/1169](https://github.com/loco-rs/loco/pull/1169) - Return 422 status code for JSON rejection errors. [https://github.com/loco-rs/loco/pull/1173](https://github.com/loco-rs/loco/pull/1173) - Return json validation details response. [https://github.com/loco-rs/loco/pull/1174](https://github.com/loco-rs/loco/pull/1174) - Fix example command after generating schedule. [https://github.com/loco-rs/loco/pull/1176](https://github.com/loco-rs/loco/pull/1176) - Fixed independent features. [https://github.com/loco-rs/loco/pull/1177](https://github.com/loco-rs/loco/pull/1177) - Custom response header for redirect. [https://github.com/loco-rs/loco/pull/1186](https://github.com/loco-rs/loco/pull/1186) - Added run_on_start feature to scheduler. [https://github.com/loco-rs/loco/pull/1184](https://github.com/loco-rs/loco/pull/1184) - feat: public jwt extractor from non-mutable reference to parts. [https://github.com/loco-rs/loco/pull/1190](https://github.com/loco-rs/loco/pull/1190) ## v0.14 - feat: smart migration generator. you can now generate migration based on naming them for creating a table, adding columns, references, join tables and more. [https://github.com/loco-rs/loco/pull/1086](https://github.com/loco-rs/loco/pull/1086) - feat: `cargo loco routes` will now pretty-print routes - fix: guard jwt error behind feature flag. [https://github.com/loco-rs/loco/pull/1032](https://github.com/loco-rs/loco/pull/1032) - fix: logger file_appender not using the seperated format setting. [https://github.com/loco-rs/loco/pull/1036](https://github.com/loco-rs/loco/pull/1036) - seed cli command. [https://github.com/loco-rs/loco/pull/1046](https://github.com/loco-rs/loco/pull/1046) - Updated validator to 0.19. [https://github.com/loco-rs/loco/pull/993](https://github.com/loco-rs/loco/pull/993) ### Breaking Changes Bump validator to 0.19 in your local `Cargo.toml` - Testing helpers: simplified function calls + adding html selector. [https://github.com/loco-rs/loco/pull/1047](https://github.com/loco-rs/loco/pull/1047) ### Breaking Changes #### Updated Import Paths The testing module import path has been updated. To adapt your code, update imports from: ```rust use loco_rs::testing; ``` to: ```rust use testing::prelude::*; ``` #### Simplified Function Calls Function calls within the testing module no longer require the testing:: prefix. Update your code accordingly. For example: Before: ```rust let boot = testing::boot_test::().await.unwrap(); ``` After: ```rust let boot = boot_test::().await.unwrap(); ``` - implement commands to manage background jobs. [https://github.com/loco-rs/loco/pull/1071](https://github.com/loco-rs/loco/pull/1071) - magic link. [https://github.com/loco-rs/loco/pull/1085](https://github.com/loco-rs/loco/pull/1085) - infer migration. [https://github.com/loco-rs/loco/pull/1086](https://github.com/loco-rs/loco/pull/1086) - Remove unnecessary calls to 'register_tasks' functions in scheduler. [https://github.com/loco-rs/loco/pull/1100](https://github.com/loco-rs/loco/pull/1100) - implement commands to manage background jobs. [https://github.com/loco-rs/loco/pull/1071](https://github.com/loco-rs/loco/pull/1071) - expose hello_name for SMTP client config. [https://github.com/loco-rs/loco/pull/1057](https://github.com/loco-rs/loco/pull/1057) - use reqwest with rustls rather than openssl. [https://github.com/loco-rs/loco/pull/1058](https://github.com/loco-rs/loco/pull/1058) - more flexible config, take more values from ENV. [https://github.com/loco-rs/loco/pull/1058](https://github.com/loco-rs/loco/pull/1058) - refactor: Use opendal to replace object_store. [https://github.com/loco-rs/loco/pull/897](https://github.com/loco-rs/loco/pull/897) - allow override loco template. [https://github.com/loco-rs/loco/pull/1102](https://github.com/loco-rs/loco/pull/1102) - support custom config folder. [https://github.com/loco-rs/loco/pull/1081](https://github.com/loco-rs/loco/pull/1081) - feat: upgrade to Axum 8. [https://github.com/loco-rs/loco/pull/1130](https://github.com/loco-rs/loco/pull/1130) - create load config hook. [https://github.com/loco-rs/loco/pull/1143](https://github.com/loco-rs/loco/pull/1143) - initial impl new migration dsl. [https://github.com/loco-rs/loco/pull/1125](https://github.com/loco-rs/loco/pull/1125) - allow disable limit_payload middleware. [https://github.com/loco-rs/loco/pull/1113](https://github.com/loco-rs/loco/pull/1113) ## v0.13.2 - static fallback now returns 200 and not 404 [https://github.com/loco-rs/loco/pull/991](https://github.com/loco-rs/loco/pull/991) - cache system now has expiry [https://github.com/loco-rs/loco/pull/1006](https://github.com/loco-rs/loco/pull/1006) - fixed: http interface binding [https://github.com/loco-rs/loco/pull/1007](https://github.com/loco-rs/loco/pull/1007) - JWT claims now editable and public [https://github.com/loco-rs/loco/issues/988](https://github.com/loco-rs/loco/issues/988) - CORS now not enabled in dev mode to avoid friction [https://github.com/loco-rs/loco/pull/1009](https://github.com/loco-rs/loco/pull/1009) - fixed: task code generation now injects in all cases [https://github.com/loco-rs/loco/pull/1012](https://github.com/loco-rs/loco/pull/1012) **BREAKING** In your `app.rs` add the following injection comment at the bottom: ```rust fn register_tasks(tasks: &mut Tasks) { tasks.register(tasks::user_report::UserReport); tasks.register(tasks::seed::SeedData); tasks.register(tasks::foo::Foo); // tasks-inject (do not remove) } ``` - fix: seeding now sets autoincrement fields in the relevant DBs [https://github.com/loco-rs/loco/pull/1014](https://github.com/loco-rs/loco/pull/1014) - fix: avoid generating entities from queue tables when the queue backend is database based [https://github.com/loco-rs/loco/issues/1013](https://github.com/loco-rs/loco/issues/1013) - removed: channels moved to an initializer [https://github.com/loco-rs/loco/issues/892](https://github.com/loco-rs/loco/issues/892) **BREAKING** See how this looks like in [https://github.com/loco-rs/chat-rooms](https://github.com/loco-rs/chat-rooms) ## v0.13.0 - Added SQLite background job support [https://github.com/loco-rs/loco/pull/969](https://github.com/loco-rs/loco/pull/969) - Added automatic updating of `updated_at` on change [https://github.com/loco-rs/loco/pull/962](https://github.com/loco-rs/loco/pull/962) - fixed codegen injection point in migrations [https://github.com/loco-rs/loco/pull/952](https://github.com/loco-rs/loco/pull/952) **NOTE: update your migration listing module like so:** ```rust // migrations/src/lib.rs vec![ Box::new(m20220101_000001_users::Migration), Box::new(m20231103_114510_notes::Migration), Box::new(m20240416_071825_roles::Migration), Box::new(m20240416_082115_users_roles::Migration), // inject-above (do not remove this comment) ] ``` Add the comment just before the closing array (`inject-above`) - Added ability to name references in [https://github.com/loco-rs/loco/pull/955](https://github.com/loco-rs/loco/pull/955): ```sh $ generate scaffold posts title:string! content:string! written_by:references:users approved_by:references:users ``` - Added hot-reload like experience to Tera templates [https://github.com/loco-rs/loco/issues/977](https://github.com/loco-rs/loco/issues/977), in debug builds only. **NOTE: update your initializers `after_routes` like so:** ```rust // src/initializers/view_engine.rs async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { #[allow(unused_mut)] let mut tera_engine = engines::TeraView::build()?; if std::path::Path::new(I18N_DIR).exists() { let arc = ArcLoader::builder(&I18N_DIR, unic_langid::langid!("en-US")) .shared_resources(Some(&[I18N_SHARED.into()])) .customize(|bundle| bundle.set_use_isolating(false)) .build() .map_err(|e| Error::string(&e.to_string()))?; #[cfg(debug_assertions)] tera_engine .tera .lock() .expect("lock") .register_function("t", FluentLoader::new(arc)); #[cfg(not(debug_assertions))] tera_engine .tera .register_function("t", FluentLoader::new(arc)); info!("locales loaded"); } Ok(router.layer(Extension(ViewEngine::from(tera_engine)))) } ``` - `loco doctor` now checks for app-specific minimum dependency versions. This should help in upgrades. `doctor` also supports "production only" checks which you can run in production with `loco doctor --production`. This, for example, will check your connections but will not check dependencies. [https://github.com/loco-rs/loco/pull/931](https://github.com/loco-rs/loco/pull/931) - Use a single loco-rs dep for a whole project. [https://github.com/loco-rs/loco/pull/927](https://github.com/loco-rs/loco/pull/927) - chore: fix generated testcase. [https://github.com/loco-rs/loco/pull/939](https://github.com/loco-rs/loco/pull/939) - chore: Correct cargo test message. [https://github.com/loco-rs/loco/pull/938](https://github.com/loco-rs/loco/pull/938) - Add relevant meta tags for better defaults. [https://github.com/loco-rs/loco/pull/943](https://github.com/loco-rs/loco/pull/943) - Update cli message with correct command. [https://github.com/loco-rs/loco/pull/942](https://github.com/loco-rs/loco/pull/942) - remove lazy_static. [https://github.com/loco-rs/loco/pull/941](https://github.com/loco-rs/loco/pull/941) - change update HTTP verb semantics to put+patch. [https://github.com/loco-rs/loco/pull/919](https://github.com/loco-rs/loco/pull/919) - Fixed HTML scaffold error. [https://github.com/loco-rs/loco/pull/960](https://github.com/loco-rs/loco/pull/960) - Scaffolded HTML update method should be POST. [https://github.com/loco-rs/loco/pull/963](https://github.com/loco-rs/loco/pull/963) ## v0.12.0 This release have been primarily about cleanups and simplification. Please update: - `loco-rs` - `loco-cli` Changes: - **generators (BREAKING)**: all prefixes in starters (e.g. `/api`) are now _local to each controller_, and generators will be prefix-aware (`--api` generator will add an `/api` prefix to controllers) [https://github.com/loco-rs/loco/pull/818](https://github.com/loco-rs/loco/pull/818) To migrate, please move prefixes from `app.rs` to each controller you use in `controllers/`, for example in `notes` controller: ```rust Routes::new() .prefix("api/notes") .add("/", get(list)) ``` - **starters**: removed `.devcontainer` which can now be found in [loco-devcontainer](https://github.com/loco-rs/loco-devcontainer) - **starters**: removed example `notes` scaffold (model, controllers, etc), and unified `user` and `auth` into a single file: `auth.rs` - **generators**: `scaffold` generator will now generate a CRUD with `PUT` and `PATCH` semantics for updating an entity [https://github.com/loco-rs/loco/issues/896](https://github.com/loco-rs/loco/issues/896) - **cleanup**: `loco-extras` was moved out of the repo, but we've incorporated `MultiDB` and `ExtraDB` from `extras` into `loco-rs` [https://github.com/loco-rs/loco/pull/917](https://github.com/loco-rs/loco/pull/917) - `cargo loco doctor` now checks for minimal required SeaORM CLI version - **BREAKING** Improved migration generator. If you have an existing migration project, add the following comment indicator to the top of the `vec` statement and right below the opening bracked like so in `migration/src/lib.rs`: ```rust fn migrations() -> Vec> { vec![ // inject-below (do not remove this comment) ``` ## v0.11.0 - Upgrade **SeaORM to v1.1.0** - Added OpenAPI example - Improve health route [https://github.com/loco-rs/loco/pull/851](https://github.com/loco-rs/loco/pull/851) - Add good pragmas to Sqlite [https://github.com/loco-rs/loco/pull/848](https://github.com/loco-rs/loco/pull/848) - Upgrade to rsbuild 1.0. [https://github.com/loco-rs/loco/pull/792](https://github.com/loco-rs/loco/pull/792) - Implements fmt::Debug to pub structs. [https://github.com/loco-rs/loco/pull/812](https://github.com/loco-rs/loco/pull/812) - Add num_workers config for sidekiq queue. [https://github.com/loco-rs/loco/pull/823](https://github.com/loco-rs/loco/pull/823) - Fix some comments in the starters and example code. [https://github.com/loco-rs/loco/pull/824](https://github.com/loco-rs/loco/pull/824) - Fix Y2038 bug for JWT on 32 bit platforms. [https://github.com/loco-rs/loco/pull/825](https://github.com/loco-rs/loco/pull/825) - Make App URL in Boot Banner Clickable. [https://github.com/loco-rs/loco/pull/826](https://github.com/loco-rs/loco/pull/826) - Add `--no-banner` flag to allow disabling the banner display. [https://github.com/loco-rs/loco/pull/839](https://github.com/loco-rs/loco/pull/839) - add on_shutdown hook. [https://github.com/loco-rs/loco/pull/842](https://github.com/loco-rs/loco/pull/842) ## v0.10.1 - `Format(respond_to): Format` extractor in controller can now be replaced with `respond_to: RespondTo` extractor for less typing. - When supplying data to views, you can now use `data!` instead of `serde_json::json!` for shorthand. - Refactor middlewares. [https://github.com/loco-rs/loco/pull/785](https://github.com/loco-rs/loco/pull/785). Middleware selection, configuration, and tweaking is MUCH more powerful and convenient now. You can keep the `middleware:` section empty or remove it now, see more in [the middleware docs](https://loco.rs/docs/the-app/controller/#middleware) - **NEW (BREAKING)** background worker subsystem is now queue agnostic. Providing for both Redis and Postgres with a change of configuration. This means you can now use a full-Postgres stack to remove Redis as a dependency if you wish. Here are steps to migrate your codebase: ```rust // in your app.rs, change the worker registration code: // BEFORE fn connect_workers<'a>(p: &'a mut Processor, ctx: &'a AppContext) { p.register(DownloadWorker::build(ctx)); } // AFTER async fn connect_workers(ctx: &AppContext, queue: &Queue) -> Result<()>{ queue.register(DownloadWorker::build(ctx)).await?; Ok(()) } // in your app.rs, replace the `worker` module references. // REMOVE worker::{AppWorker, Processor}, // REPLACE WITH bgworker::{BackgroundWorker, Queue}, // in your workers change the signature, and add the `build` function // BEFORE impl worker::Worker for DownloadWorker { async fn perform(&self, args: DownloadWorkerArgs) -> worker::Result<()> { // AFTER #[async_trait] impl BackgroundWorker for DownloadWorker { fn build(ctx: &AppContext) -> Self { Self { ctx: ctx.clone() } } async fn perform(&self, args: DownloadWorkerArgs) -> Result<()> { // Finally, remove the `AppWorker` trait implementation completely. // REMOVE impl worker::AppWorker for DownloadWorker { fn build(ctx: &AppContext) -> Self { Self { ctx: ctx.clone() } } } ``` Finally, update your `development.yaml` and `test.yaml` with a `kind`: ```yaml queue: kind: Redis # add this to the existing `queue` section ``` - **UPGRADED (BREAKING)**: `validator` crate was upgraded which require some small tweaks to work with the new API: ```rust // BEFORE: #[validate(custom = "validation::is_valid_email")] pub email: String, // AFTER: #[validate(custom (function = "validation::is_valid_email"))] pub email: String, ``` Then update your `Cargo.toml` to take version `0.18`: ```toml # update validator = { version = "0.18" } ``` - **UPGRADED (BREAKING)**: `axum-test` crate was upgraded Update your `Cargo.toml` to version `16`: ```toml # update axum-test = { version = "16" } ``` ## v0.9.0 - Add fallback behavior. [https://github.com/loco-rs/loco/pull/732](https://github.com/loco-rs/loco/pull/732) - Add Scheduler Feature for Running Cron Jobs. [https://github.com/loco-rs/loco/pull/735](https://github.com/loco-rs/loco/pull/735) - Add `--html`, `--htmx` and `--api` flags to scaffold CLI command. [https://github.com/loco-rs/loco/pull/749](https://github.com/loco-rs/loco/pull/749) - Add base template for scaffold generation. [https://github.com/loco-rs/loco/pull/752](https://github.com/loco-rs/loco/pull/752) - Connect Redis only when the worker is BackgroundQueue. [https://github.com/loco-rs/loco/pull/755](https://github.com/loco-rs/loco/pull/755) - Add loco doctor --config. [https://github.com/loco-rs/loco/pull/736](https://github.com/loco-rs/loco/pull/736) - Rename demo: blo -> demo_app. [https://github.com/loco-rs/loco/pull/741](https://github.com/loco-rs/loco/pull/741) ## v0.8.1 - fix: introduce secondary binary for compile-and-run on Windows. [https://github.com/loco-rs/loco/pull/727](https://github.com/loco-rs/loco/pull/727) ## v0.8.0 - Added: loco-cli (`loco new`) now receives options from CLI and/or interactively asks for configuration options such as which asset pipeline, background worker type, or database provider to use. - Fix: custom queue names now merge with default queues. - Added `remote_ip` middleware for resolving client remote IP when under a proxy or loadbalancer, similar to the Rails `remote_ip` middleware. - Added `secure_headers` middleware for setting secure headers by default, similar to how [https://github.com/github/secure_headers](https://github.com/github/secure_headers) works. This is now ON by default to promote security-by-default. - Added: `money`, `blob` types to entitie generator. ## 0.7.0 - Moving to _timezone aware timestamps_. From now on migrations will generate **timestamps with time zone** by default. Moving to TZ aware timestamps in combination with newly revamped timestamp code generation in SeaORM v1.0.0 finally allows for _seamlessly_ moving between using `sqlite` and `postgres` with minimal or no entities code changes (resolved [this long standing issue](https://github.com/loco-rs/loco/issues/518#issuecomment-2051708319)). TZ aware timestamps also aligns us with how Rails works today (initially Rails had a no-tz timestamps, and today the default is to use timestamps). If not specified the TZ is the server TZ, which is usually UTC, therefore semantically this is almost like a no-tz timestamp. **A few highlights:** Generated entities will now always use `DateTimeWithTimeZone` for the default timestamp fields: ``` ... Generating users.rs > Column `created_at`: DateTimeWithTimeZone, not_null > Column `updated_at`: DateTimeWithTimeZone, not_null ... ``` For better cross database provider compatibility, from now on prefer the `tstz` type instead of just `ts` when using generators (i.e. `cargo loco generate model movie released:tstz`) - remove eyer lib. [https://github.com/loco-rs/loco/pull/650](https://github.com/loco-rs/loco/pull/650) ### Breaking Changes: 1. Update the Main Function in src/bin/main Replace the return type of the main function: **Before:** ```rust async fn main() -> eyre::Result<()> ``` **After:** ```rust async fn main() -> loco_rs::Result<()> ``` 2. Modify examples/playground.rs You need to apply two changes here: a. Update the Function Signature **Before:** ```rust async fn main() -> eyre::Result<()> ``` **After:** ```rust async fn main() -> loco_rs::Result<()> ``` b. Adjust the Context Handling **Before:** ```rust let _ctx = playground::().await.context("playground")?; ``` **After:** ```rust let _ctx = playground::().await?; ``` Note, If you are using eyre in your project, you can continue to do so. We have only removed this crate from our base code dependencies. - Bump rstest crate to 0.21.0. [https://github.com/loco-rs/loco/pull/650](https://github.com/loco-rs/loco/pull/650) - Bump serial_test crate to 3.1.1. [https://github.com/loco-rs/loco/pull/651](https://github.com/loco-rs/loco/pull/651) - Bumo object store to create to 0.10.2. [https://github.com/loco-rs/loco/pull/654](https://github.com/loco-rs/loco/pull/654) - Bump axum crate to 0.7.5. [https://github.com/loco-rs/loco/pull/652](https://github.com/loco-rs/loco/pull/652) - Add Hooks::before_routes to give user control over initial axum::Router construction. [https://github.com/loco-rs/loco/pull/646](https://github.com/loco-rs/loco/pull/646) - Support logger file appender. [https://github.com/loco-rs/loco/pull/636](https://github.com/loco-rs/loco/pull/636) - Response from the template. [https://github.com/loco-rs/loco/pull/682](https://github.com/loco-rs/loco/pull/682) - Add get_or_insert function to cache layer. [https://github.com/loco-rs/loco/pull/637](https://github.com/loco-rs/loco/pull/637) - Bump ORM create to 1.0.0. [https://github.com/loco-rs/loco/pull/684](https://github.com/loco-rs/loco/pull/684) ## 0.6.2 - Use Rust-based tooling for SaaS starter frontend. [https://github.com/loco-rs/loco/pull/625](https://github.com/loco-rs/loco/pull/625) - Default binding to localhost to avoid firewall dialogues during development on macOS. [https://github.com/loco-rs/loco/pull/627](https://github.com/loco-rs/loco/pull/627) - upgrade sea-orm to 1.0.0 RC 7. [https://github.com/loco-rs/loco/pull/627](https://github.com/loco-rs/loco/pull/639) - Add a down migration command. [https://github.com/loco-rs/loco/pull/414](https://github.com/loco-rs/loco/pull/414) - replace create_postgres_database function table_name to db_name. [https://github.com/loco-rs/loco/pull/647](https://github.com/loco-rs/loco/pull/647) ## 0.6.1 - Upgrade htmx generator to htmx2. [https://github.com/loco-rs/loco/pull/629](https://github.com/loco-rs/loco/pull/629) ## 0.6.0 https://github.com/loco-rs/loco/pull/610 - Bump socketioxide to v0.13.1. [https://github.com/loco-rs/loco/pull/594](https://github.com/loco-rs/loco/pull/594) - Add CC and BCC fields to the mailers. [https://github.com/loco-rs/loco/pull/599](https://github.com/loco-rs/loco/pull/599) - Delete reset tokens after use. [https://github.com/loco-rs/loco/pull/602](https://github.com/loco-rs/loco/pull/602) - Generator html support delete entity. [https://github.com/loco-rs/loco/pull/604](https://github.com/loco-rs/loco/pull/604) - **Breaking changes** move task args from BTreeMap to struct. [https://github.com/loco-rs/loco/pull/609](https://github.com/loco-rs/loco/pull/609) - Change task signature from `async fn run(&self, app_context: &AppContext, vars: &BTreeMap)` to `async fn run(&self, _app_context: &AppContext, _vars: &task::Vars) -> Result<()>` - **Breaking changes** change default port to 5150. [https://github.com/loco-rs/loco/pull/611](https://github.com/loco-rs/loco/pull/611) - Update shuttle version in deployment generation. [https://github.com/loco-rs/loco/pull/616](https://github.com/loco-rs/loco/pull/616) ## v0.5.0 https://github.com/loco-rs/loco/pull/593 - refactor auth middleware for supporting bearer, cookie and query. [https://github.com/loco-rs/loco/pull/560](https://github.com/loco-rs/loco/pull/560) - SeaORM upgraded: `rc1` -> `rc4`. [https://github.com/loco-rs/loco/pull/585](https://github.com/loco-rs/loco/pull/585) - Adding Cache to app content. [https://github.com/loco-rs/loco/pull/570](https://github.com/loco-rs/loco/pull/570) - Apply a layer to a specific handler using `layer` method. [https://github.com/loco-rs/loco/pull/554](https://github.com/loco-rs/loco/pull/554) - Add the debug macro to the templates to improve the errors. [https://github.com/loco-rs/loco/pull/547](https://github.com/loco-rs/loco/pull/547) - Opentelemetry initializer. [https://github.com/loco-rs/loco/pull/531](https://github.com/loco-rs/loco/pull/531) - Refactor auth middleware for supporting bearer, cookie and query [https://github.com/loco-rs/loco/pull/560](https://github.com/loco-rs/loco/pull/560) - Add redirect response [https://github.com/loco-rs/loco/pull/563](https://github.com/loco-rs/loco/pull/563) - **Breaking changes** Adding a custom claims `Option` to the `UserClaims` struct (type changed). [https://github.com/loco-rs/loco/pull/578](https://github.com/loco-rs/loco/pull/578) - **Breaking changes** Refactored DSL and Pagination: namespace changes. [https://github.com/loco-rs/loco/pull/566](https://github.com/loco-rs/loco/pull/566) - Replaced `model::query::dsl::` with `model::query`. - Replaced `model::query::exec::paginate` with `model::query::paginate`. - Updated the `PaginatedResponse` struct. Refer to its usage example [here](https://github.com/loco-rs/loco/blob/master/examples/demo/src/views/notes.rs#L29). - **Breaking changes** When introducing the Cache system which is much more flexible than having just Redis, we now call the 'redis' member simply a 'queue' which indicates it should be used only for the internal queue and not as a general purpose cache. In the application configuration setting `redis`, change to `queue`. [https://github.com/loco-rs/loco/pull/590](https://github.com/loco-rs/loco/pull/590) ```yaml # before: redis: # after: queue: ``` - **Breaking changes** We have made a few parts of the context pluggable, such as the `storage` and new `cache` subsystems, this is why we decided to let you configure the context entirely before starting up your app. As a result, if you have a storage building hook code it should move to `after_context`, see example [here](https://github.com/loco-rs/loco/pull/570/files#diff-5534e8826fb82e5c7f2587d270a51b48009341e79889d1504e6b63b2f0b652bdR83). [https://github.com/loco-rs/loco/pull/570](https://github.com/loco-rs/loco/pull/570) ## v0.4.0 - Refactored model validation for better developer experience. Added a few traits and structs to `loco::prelude` for a smoother import story. Introducing `Validatable`: ```rust impl Validatable for super::_entities::users::ActiveModel { fn validator(&self) -> Box { Box::new(Validator { name: self.name.as_ref().to_owned(), email: self.email.as_ref().to_owned(), }) } } // now you can call `user.validate()` freely ``` - Refactored type field mapping to be centralized. Now model, scaffold share the same field mapping, so no more gaps like [https://github.com/loco-rs/loco/issues/513](https://github.com/loco-rs/loco/issues/513) (e.g. when calling `loco generate model title:string` the ability to map `string` into something useful in the code generation side) **NOTE** the `_integer` class of types are now just `_int`, e.g. `big_int`, so that it correlate with the `int` field name in a better way - Adding to to quiery dsl `is_in` and `is_not_in`. [https://github.com/loco-rs/loco/pull/507](https://github.com/loco-rs/loco/pull/507) - Added: in your configuration you can now use an `initializers:` section for initializer specific settings ```yaml # Initializers Configuration initializers: # oauth2: # authorization_code: # Authorization code grant type # - client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config. # ... other fields ``` - Docs: fix schema data types mapping. [https://github.com/loco-rs/loco/pull/506](https://github.com/loco-rs/loco/pull/506) - Let Result accept other errors. [https://github.com/loco-rs/loco/pull/505](https://github.com/loco-rs/loco/pull/505) - Allow trailing slashes in URIs by adding the NormalizePathLayer. [https://github.com/loco-rs/loco/pull/481](https://github.com/loco-rs/loco/pull/481) - **BREAKING** Move from `Result` to `Result`. This enables much greater flexibility building APIs, where with `Result` you mix and match response types based on custom logic (returning JSON and HTML/String in the same route). - **Added**: mime responders similar to `respond_to` in Rails: 1. Use the `Format` extractor 2. Match on `respond_to` 3. Create different content for different response formats The following route will always return JSON, unless explicitly asked for HTML with a `Content-Type: text/html` (or `Accept: `) header: ```rust pub async fn get_one( Format(respond_to): Format, Path(id): Path, State(ctx): State, ) -> Result { let item = load_item(&ctx, id).await?; match respond_to { RespondTo::Html => format::html(&format!("{:?}", item.title)), _ => format::json(item), } } ``` ## 0.3.2 - Redisgin pagination. [https://github.com/loco-rs/loco/pull/463](https://github.com/loco-rs/loco/pull/463) - Wrap seaorm query and condition for common use cases. [https://github.com/loco-rs/loco/pull/463](https://github.com/loco-rs/loco/pull/463) - Adding to loco-extras initializer for extra or multiple db. [https://github.com/loco-rs/loco/pull/471](https://github.com/loco-rs/loco/pull/471) - Scaffold now supporting different templates such as API,HTML or htmx, this future is in beta.[https://github.com/loco-rs/loco/pull/474](https://github.com/loco-rs/loco/pull/474) - Fix generatore fields types + adding tests. [https://github.com/loco-rs/loco/pull/459](https://github.com/loco-rs/loco/pull/459) - Fix channel cors. [https://github.com/loco-rs/loco/pull/430](https://github.com/loco-rs/loco/pull/430) - Improve auth controller compatibility with frontend [https://github.com/loco-rs/loco/pull/472](https://github.com/loco-rs/loco/pull/472) ## 0.3.1 - **Breaking changes** Upgrade sea-orm to v1.0.0-rc.1. [https://github.com/loco-rs/loco/pull/420](https://github.com/loco-rs/loco/pull/420) Needs to update `sea-orm` crate to use `v1.0.0-rc.1` version. - Implemented file upload support with versatile strategies. [https://github.com/loco-rs/loco/pull/423](https://github.com/loco-rs/loco/pull/423) - Create a `loco_extra` crate to share common basic implementations. [https://github.com/loco-rs/loco/pull/425](https://github.com/loco-rs/loco/pull/425) - Update shuttle deployment template to 0.38. [https://github.com/loco-rs/loco/pull/422](https://github.com/loco-rs/loco/pull/422) - Enhancement: Move the Serve to Hook flow with the ability to override default serve settings. [https://github.com/loco-rs/loco/pull/418](https://github.com/loco-rs/loco/pull/418) - Avoid cloning sea_query::ColumnDef. [https://github.com/loco-rs/loco/pull/415](https://github.com/loco-rs/loco/pull/415) - Allow required UUID type in a scaffold. [https://github.com/loco-rs/loco/pull/408](https://github.com/loco-rs/loco/pull/408) - Cover `SqlxMySqlPoolConnection` in db.rs. [https://github.com/loco-rs/loco/pull/411](https://github.com/loco-rs/loco/pull/411) - Update worker docs and change default worker mode. [https://github.com/loco-rs/loco/pull/412](https://github.com/loco-rs/loco/pull/412) - Added server-side view generation through a new `ViewEngine` infrastructure and `Tera` server-side templates: [https://github.com/loco-rs/loco/pull/389](https://github.com/loco-rs/loco/pull/389) - Added `generate model --migration-only` [https://github.com/loco-rs/loco/issues/400](https://github.com/loco-rs/loco/issues/400) - Add JSON to scaffold gen. [https://github.com/loco-rs/loco/pull/396](https://github.com/loco-rs/loco/pull/396) - Add --binding(-b) and --port(-b) to `cargo loco start`.[https://github.com/loco-rs/loco/pull/402](https://github.com/loco-rs/loco/pull/402) ## 0.2.3 - Add: support for [pre-compressed assets](https://github.com/loco-rs/loco/pull/370/files). - Added: Support socket channels, see working example [here](https://github.com/loco-rs/chat-rooms). [https://github.com/loco-rs/loco/pull/380](https://github.com/loco-rs/loco/pull/380) - refactor: optimize checking permissions on Postgres. [9416c](https://github.com/loco-rs/loco/commit/9416c5db85a27e3d30471374effec3fe88bf80a2) - Added: E2E db. [https://github.com/loco-rs/loco/pull/371](https://github.com/loco-rs/loco/pull/371) ## v0.2.2 - fix: public fields in mailer-op. [e51b7e](https://github.com/loco-rs/loco/commit/e51b7e64e7667c519451ac8a8bea574b2c5d4403) - fix: handle missing db permissions. [e51b7e](https://github.com/loco-rs/loco/commit/e51b7e64e7667c519451ac8a8bea574b2c5d4403) ## v0.2.1 - enable compression for CompressionLayer, not etag. [https://github.com/loco-rs/loco/pull/356](https://github.com/loco-rs/loco/pull/356) - Fix nullable JSONB column schema definition. [https://github.com/loco-rs/loco/pull/357](https://github.com/loco-rs/loco/pull/357) ## v0.2.0 - Add: Loco now has Initializers ([see the docs](https://loco.rs/docs/the-app/initializers/)). Initializers help you integrate infra into your app in a seamless way, as well as share pieces of setup code between your projects - Add: an `init_logger` hook in `src/app.rs` for those who want to take ownership of their logging and tracing stack. - Add: Return a JSON schema when payload json could not serialize to a struct. [https://github.com/loco-rs/loco/pull/343](https://github.com/loco-rs/loco/pull/343) - Init logger in cli.rs. [https://github.com/loco-rs/loco/pull/338](https://github.com/loco-rs/loco/pull/338) - Add: return JSON schema in panic HTTP layer. [https://github.com/loco-rs/loco/pull/336](https://github.com/loco-rs/loco/pull/336) - Add: JSON field support in model generation. [https://github.com/loco-rs/loco/pull/327](https://github.com/loco-rs/loco/pull/327) [https://github.com/loco-rs/loco/pull/332](https://github.com/loco-rs/loco/pull/332) - Add: float support in model generation. [https://github.com/loco-rs/loco/pull/317](https://github.com/loco-rs/loco/pull/317) - Fix: conflicting idx definition on M:M migration. [https://github.com/loco-rs/loco/issues/311](https://github.com/loco-rs/loco/issues/311) - Add: **Breaking changes** Supply `AppContext` to `routes` Hook. Migration steps in `src/app.rs`: ```rust // src/app.rs: add app context to routes function impl Hooks for App { ... fn routes(_ctx: &AppContext) -> AppRoutes; ... } ``` - Add: **Breaking changes** change parameter type from `&str` to `&Environment` in `src/app.rs` ```rust // src/app.rs: change parameter type for `environment` from `&str` to `&Environment` impl Hooks for App { ... async fn boot(mode: StartMode, environment: &Environment) -> Result { create_app::(mode, environment).await } ... ``` - Added: setting cookies: ```rust format::render() .cookies(&[ cookie::Cookie::new("foo", "bar"), cookie::Cookie::new("baz", "qux"), ])? .etag("foobar")? .json(notes) ``` ## v0.1.9 - Adding [pagination](https://loco.rs/docs/the-app/pagination/) on Models. [https://github.com/loco-rs/loco/pull/238](https://github.com/loco-rs/loco/pull/238) - Adding compression middleware. [https://github.com/loco-rs/loco/pull/205](https://github.com/loco-rs/loco/pull/205) Added support for [compression middleware](https://docs.rs/tower-http/0.5.0/tower_http/compression/index.html). usage: ```yaml middlewares: compression: enable: true ``` - Create a new Database from the CLI. [https://github.com/loco-rs/loco/pull/223](https://github.com/loco-rs/loco/pull/223) - Validate if seaorm CLI is installed before running `cargo loco db entities` and show a better error to the user. [https://github.com/loco-rs/loco/pull/212](https://github.com/loco-rs/loco/pull/212) - Adding to `saas and `rest-api` starters a redis and DB in GitHub action workflow to allow users work with github action out of the box. [https://github.com/loco-rs/loco/pull/215](https://github.com/loco-rs/loco/pull/215) - Adding the app name and the environment to the DB name when creating a new starter. [https://github.com/loco-rs/loco/pull/216](https://github.com/loco-rs/loco/pull/216) - Fix generator when users adding a `created_at` or `update_at` fields. [https://github.com/loco-rs/loco/pull/214](https://github.com/loco-rs/loco/pull/214) - Add: `format::render` which allows a builder-like formatting, including setting etag and ad-hoc headers - Add: Etag middleware, enabled by default in starter projects. Once you set an Etag it will check for cache headers and return `304` if needed. To enable etag in your existing project: ```yaml #... middlewares: etag: enable: true ``` usage: ```rust format::render() .etag("foobar")? .json(Entity::find().all(&ctx.db).await?) ``` #### Authentication: Added API Token Authentication! - See [https://github.com/loco-rs/loco/pull/217](https://github.com/loco-rs/loco/pull/217) Now when you generate a `saas starter` or `rest api` starter you will get additional authentication methods for free: - Added: authentication added -- **api authentication** where each user has an API token in the schema, and you can authenticate with `Bearer` against that user. - Added: authentication added -- `JWTWithUser` extractor, which is a convenience for resolving the authenticated JWT claims into a current user from database **migrating an existing codebase** Add the following to your generated `src/models/user.rs`: ```rust #[async_trait] impl Authenticable for super::_entities::users::Model { async fn find_by_api_key(db: &DatabaseConnection, api_key: &str) -> ModelResult { let user = users::Entity::find() .filter(users::Column::ApiKey.eq(api_key)) .one(db) .await?; user.ok_or_else(|| ModelError::EntityNotFound) } async fn find_by_claims_key(db: &DatabaseConnection, claims_key: &str) -> ModelResult { super::_entities::users::Model::find_by_pid(db, claims_key).await } } ``` Update imports in this file to include `model::Authenticable`: ```rust use loco_rs::{ auth, hash, model::{Authenticable, ModelError, ModelResult}, validation, validator::Validate, }; ``` ## v0.1.8 - Added: `loco version` for getting an operable version string containing logical crate version and git SHA if available: `0.3.0 ()` To migrate to this behavior from earlier versions, it requires adding the following to your `app.rs` app hooks: ```rust fn app_version() -> String { format!( "{} ({})", env!("CARGO_PKG_VERSION"), option_env!("BUILD_SHA") .or(option_env!("GITHUB_SHA")) .unwrap_or("dev") ) } ``` Reminder: `loco --version` will give you the current Loco framework which your app was built against and `loco version` gives you your app version. - Added: `loco generate migration` for adding ad-hoc migrations - Added: added support in model generator for many-to-many link table generation via `loco generate model --link` - Docs: added Migration section, added relations documentation 1:M, M:M - Adding .devcontainer to starter projects [https://github.com/loco-rs/loco/issues/170](https://github.com/loco-rs/loco/issues/170) - **Braking changes**: Adding `Hooks::boot` application. Migration steps: ```rust // Load boot::{create_app, BootResult, StartMode} from loco_rs lib // Load migration: use migration::Migrator; Only when using DB // Adding boot hook with the following code impl Hooks for App { ... async fn boot(mode: StartMode, environment: &str) -> Result { // With DB: create_app::(mode, environment).await // Without DB: create_app::(mode, environment).await } ... } ``` ## v0.1.7 - Added pretty backtraces [https://github.com/loco-rs/loco/issues/41](https://github.com/loco-rs/loco/issues/41) - adding tests for note requests [https://github.com/loco-rs/loco/pull/156](https://github.com/loco-rs/loco/pull/156) - Define the min rust version the loco can run [https://github.com/loco-rs/loco/pull/164](https://github.com/loco-rs/loco/pull/164) - Added `cargo loco doctor` cli command for validate and diagnose configurations. [https://github.com/loco-rs/loco/pull/145](https://github.com/loco-rs/loco/pull/145) - Added ability to specify `settings:` in config files, which are available in context - Adding compilation mode in the banner. [https://github.com/loco-rs/loco/pull/127](https://github.com/loco-rs/loco/pull/127) - Support shuttle deployment generator. [https://github.com/loco-rs/loco/pull/124](https://github.com/loco-rs/loco/pull/124) - Adding a static asset middleware which allows to serve static folder/data. Enable this section in config. [https://github.com/loco-rs/loco/pull/134](https://github.com/loco-rs/loco/pull/134) ```yaml static: enable: true # ensure that both the folder.path and fallback file path are existence. must_exist: true folder: uri: "/assets" path: "frontend/dist" fallback: "frontend/dist/index.html" ``` - fix: `loco generate request` test template. [https://github.com/loco-rs/loco/pull/133](https://github.com/loco-rs/loco/pull/133) - Improve docker deployment generator. [https://github.com/loco-rs/loco/pull/131](https://github.com/loco-rs/loco/pull/131) ## v0.1.6 - refactor: local settings are now `.local.yaml` and available for all environments, for example you can add a local `test.local.yaml` and `development.local.yaml` - refactor: removed `config-rs` and now doing config loading by ourselves. - fix: email template rendering will not escape URLs - Config with variables: It is now possible to use [tera](https://keats.github.io/tera) templates in config YAML files Example of pulling a port from environment: ```yaml server: port: { { get_env(name="NODE_PORT", default=5150) } } ``` It is possible to use any `tera` templating constructs such as loops, conditionals, etc. inside YAML configuration files. - Mailer: expose `stub` in non-test - `Hooks::before_run` with a default blank implementation. You can now code some custom loading of resources or other things before the app runs - an LLM inference example, text generation in Rust, using an API (`examples/inference`) - Loco starters version & create release script [https://github.com/loco-rs/loco/pull/110](https://github.com/loco-rs/loco/pull/110) - Configure Cors middleware [https://github.com/loco-rs/loco/pull/114](https://github.com/loco-rs/loco/pull/114) - `Hooks::after_routes` Invoke this function after the Loco routers have been constructed. This function enables you to configure custom Axum logics, such as layers, that are compatible with Axum. [https://github.com/loco-rs/loco/pull/114](https://github.com/loco-rs/loco/pull/114) - Adding docker deployment generator [https://github.com/loco-rs/loco/pull/119](https://github.com/loco-rs/loco/pull/119) DOCS: - Remove duplicated docs in auth section - FAQ docs: [https://github.com/loco-rs/loco/pull/116](https://github.com/loco-rs/loco/pull/116) ENHANCEMENTS: - Remove unused libs: [https://github.com/loco-rs/loco/pull/106](https://github.com/loco-rs/loco/pull/106) - turn off default features in tokio [https://github.com/loco-rs/loco/pull/118](https://github.com/loco-rs/loco/pull/118) ## 0.1.5 NEW FEATURES - `format:html` [https://github.com/loco-rs/loco/issues/74](https://github.com/loco-rs/loco/issues/74) - Create a stateless HTML starter [https://github.com/loco-rs/loco/pull/100](https://github.com/loco-rs/loco/pull/100) - Added worker generator + adding a way to test workers [https://github.com/loco-rs/loco/pull/92](https://github.com/loco-rs/loco/pull/92) ENHANCEMENTS: - CI: allows cargo cli run on fork prs [https://github.com/loco-rs/loco/pull/96](https://github.com/loco-rs/loco/pull/96) ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement (open an issue to reach out). All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Loco Thank you for taking the time to read this. The first way to show support is to star our repos :). Loco is a community driven project. We welcome you to participate, contribute and together build a productivity-first web and api framework in Rust. ## Code of Conduct This project is follows [Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. ## I have a question If you have a question to ask, feel free to open an new [discussion](https://github.com/loco-rs/loco/discussions). There are no dumb questions. ## I need a feature Feature requests from anyone is definitely welcomed! You can open an [issue](https://github.com/loco-rs/loco/issues/new/choose). When you can, illustrate a feature with code, simulated console output, and "make believe" console interactions, so we know what you want and what you expect. ## I want to support Awesome! The best way to support us is to recommend it to your classmates/colleagues/friends, write blog posts and tutorials on our projects and help out other users in the community. ## I want to join We are always looking for long-term contributors. If you want to commit longer-term to Loco's open source effort, definitely talk with us! * From time to time we will make issues clear for newcomers with `mentoring` and `good-first-issue` * If no issue exist, just open an issue and ask how to help ### Using an example app to test Our testing grounds is [examples/demo](examples/demo/) which is pointing to the latest local `loco` framework. You can use it to test out an actual app, using a locally modified `loco`. ## Code style We use `rustfmt`/`cargo fmt`. A few code style options are set in the [.rustfmt.toml](.rustfmt.toml) file, and some of them are not stable yet and require a nightly version of rustfmt. If you're using rustup, the nightly version of rustfmt can be installed by doing the following: ```sh rustup component add rustfmt --toolchain nightly ``` And then format your code by running: ```sh rustup default nightly cargo fmt --all cargo fmt --all --manifest-path loco-new/Cargo.toml cargo clippy --fix --allow-dirty --workspace --all-features -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms cargo clippy --fix --allow-dirty --workspace --all-features --manifest-path loco-new/Cargo.toml -- -D warnings -W clippy::pedantic -W clippy::nursery -W rust-2018-idioms rustup default stable ``` ## Testing Just clone the project and run `cargo test`. You can see how we test in [.github/workflows](.github/workflows/) #### Snapshots We use [insta](https://github.com/mitsuhiko/insta) for snapshot testing, which helps us detect changes in output formats and behavior. To work with snapshots: 1. Install the insta CLI tool: ```sh cargo install cargo-insta ``` 2. Run tests and review/update snapshots: ```sh cargo insta test --review ``` For CLI-related changes, we maintain separate snapshots of binary command outputs. To update these CLI snapshots: ```sh LOCO_CI_MODE=true TRYCMD=overwrite cargo test ``` ## Docs The documentation consists of two main components: + The [loco.rs website](https://loco.rs) with its source code available [here](./docs-site/). + RustDocs. To reduce duplication in documentation and examples, we use [snipdoc](https://github.com/kaplanelad/snipdoc). As part of our CI process, we ensure that the documentation remains consistent. Updating the Documentation + Download [snipdoc](https://github.com/kaplanelad/snipdoc). + Create the snippet in the [yaml file](./snipdoc.yml) or inline the code. + Run `snipdoc run`. To run the documentation site locally, we use [zola](https://www.getzola.org/) so you'll need to [install](https://www.getzola.org/documentation/getting-started/installation/) it. The documentation site works with zola version `0.19.2` and since zola still has breaking changes, we make no guarantees about other versions. Running the local preview + `cd docs-site` + `npm run serve` or `zola serve` ## Open A Pull Request The most recommended and straightforward method to contribute changes to the project involves forking it on GitHub and subsequently initiating a pull request to propose the integration of your modifications into our repository. Changes a starters project are not recommended. read more [here](./starters/README.md) ### In Your Pull Request Description, Include: - References to any bugs fixed by the change - Informative notes for the reviewer, aiding their comprehension of the necessity for the change or providing insights on how to conduct a more effective review. - A clear explanation of how you tested your changes. ### Your PR must also: - be based on the master branch - adhere to the code [style](#code-style) - Successfully passes the [test suite](#testing) ================================================ FILE: Cargo.toml ================================================ [workspace] members = ["xtask", "loco-gen"] exclude = ["starters"] [workspace.package] edition = "2021" rust-version = "1.70" license = "Apache-2.0" [package] name = "loco-rs" version = "0.16.4" description = "The one-person framework for Rust" homepage = "https://loco.rs/" documentation = "https://docs.rs/loco-rs" repository = "https://github.com/loco-rs/loco" license.workspace = true edition.workspace = true rust-version.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] default = [ "auth_jwt", "cli", "with-db", "cache_inmem", "bg_redis", "bg_pg", "bg_sqlt", ] auth_jwt = ["dep:jsonwebtoken"] cli = ["dep:clap"] testing = ["dep:axum-test", "dep:scraper", "dep:tree-fs"] with-db = [ "dep:sea-orm", "dep:sea-orm-migration", "dep:sqlx", "loco-gen/with-db", ] # Storage features all_storage = ["storage_aws_s3", "storage_azure", "storage_gcp"] storage_aws_s3 = ["opendal/services-s3"] storage_azure = ["opendal/services-azblob"] storage_gcp = ["opendal/services-gcs"] # Cache feature cache_inmem = ["dep:moka"] cache_redis = ["dep:bb8-redis", "dep:bb8"] bg_redis = ["dep:redis", "dep:ulid"] bg_pg = ["dep:sqlx", "dep:ulid"] bg_sqlt = ["dep:sqlx", "dep:ulid"] ## Testing feature flags integration_test = [] # Embed assets into binary embedded_assets = [] [dependencies] loco-gen = { version = "0.16.1", path = "./loco-gen" } backtrace_printer = { version = "1.3.0" } # cli clap = { version = "4.4.7", features = ["derive"], optional = true } colored = { workspace = true } sea-orm = { version = "1.1.0", features = [ "sqlx-postgres", # `DATABASE_DRIVER` feature "sqlx-sqlite", "runtime-tokio-rustls", "macros", ], optional = true } tokio = { version = "1.45", default-features = false } tokio-util = "0.7" # the rest serde = { workspace = true } serde_json = { workspace = true } serde_yaml = "0.9" serde_variant = "0.1.2" toml = "0.8" async-trait = { workspace = true } axum = { workspace = true } axum-extra = { version = "0.10", features = ["cookie"] } regex = { workspace = true } # mailer tera = { workspace = true } heck = { workspace = true } cruet = "0.13.0" lettre = { version = "0.11.4", default-features = false, features = [ "builder", "hostname", "smtp-transport", "tokio1-rustls-tls", ] } include_dir = "0.7.3" thiserror = { workspace = true } tracing = { workspace = true } tracing-subscriber = { version = "0.3.16", default-features = false, features = [ "env-filter", "json", "ansi", ] } tracing-appender = { version = "0.2.3", default-features = false } duct = { workspace = true } duct_sh = { version = "1.0.0" } tower-http = { workspace = true } byte-unit = "4.0.19" argon2 = { version = "0.5", features = ["std"] } rand = { version = "0.9", features = ["std"] } jsonwebtoken = { version = "9.3.0", optional = true } validator = { version = "0.20.0", features = ["derive"] } futures-util = "0.3" tower = { workspace = true } bytes = "1.1" ipnetwork = "0.20.0" semver = "1" axum-test = { version = "17.0.1", optional = true } tree-fs = { version = "0.3", optional = true } chrono = { workspace = true } uuid = { version = "1.10.0", features = ["v4", "fast-rng"] } # File Upload opendal = { version = "0.54", default-features = false, features = [ "services-memory", "services-fs", ] } # cache moka = { version = "0.12.7", features = ["sync"], optional = true } bb8-redis = { version = "0.23", optional = true } bb8 = { version = "0.9", optional = true } # Scheduler tokio-cron-scheduler = { version = "0.11.0", features = ["signal"] } english-to-cron = { version = "0.1.2" } # bg_sqlt: sqlite workers # bg_pg: postgres workers sqlx = { version = "0.8.2", default-features = false, features = [ "json", "postgres", "chrono", "sqlite", ], optional = true } ulid = { version = "1", optional = true } # bg_redis: redis workers redis = { version = "0.31", features = ["aio", "tokio-comp"], optional = true } scraper = { version = "0.25.0", features = ["deterministic"], optional = true } dashmap = "6" notify = "8.1.0" [workspace.dependencies] tera = { version = "1.19.1" } colored = { version = "3.0" } chrono = { version = "0.4", features = ["serde"] } tracing = "0.1.40" regex = "1" thiserror = "1" serde = "1" serde_json = "1" async-trait = { version = "0.1.74" } axum = { version = "0.8.1", features = ["macros", "multipart"] } tower = "0.4" tower-http = { version = "0.6.8", features = [ "trace", "catch-panic", "timeout", "add-extension", "cors", "fs", "set-header", "compression-full", ] } heck = "0.4.0" duct = { version = "1.0.0" } [dependencies.sea-orm-migration] optional = true version = "1.0.0" features = [ # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. # e.g. "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature "sqlx-postgres", # `DATABASE_DRIVER` feature "sqlx-sqlite", ] [package.metadata.docs.rs] features = ["testing"] [dev-dependencies] loco-rs = { path = ".", features = ["testing"] } rstest = "0.21.0" insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] } tree-fs = { version = "0.3" } reqwest = { version = "0.12.7", features = ["json"] } tower = { workspace = true, features = ["util"] } sqlx = { version = "0.8.2", default-features = false, features = [ "macros", "json", "postgres", "chrono", "sqlite", "migrate", ] } testcontainers = { version = "0.23.3" } ================================================ FILE: DEVELOPMENT.md ================================================ ## Blessed dependencies maintenance and `loco doctor` Loco contain a few major and "blessed" dependencies, these appear **both** in an app that was generated at the surface level in their `Cargo.toml` and in the core Loco framework. If stale, may require an upgrade as a must. Example for such dependencies: * The `sea-orm-cli` - while Loco uses `SeaORM`, it uses the `SeaORM` CLI to generate entities, and so there may be an incompatibility if `SeaORM` has a too large breaking change between their CLI (which ships separately) and their framework. * `axum` * etc. This is why we are checking these automatically as part of `loco doctor`. We keep minimal version requirements for these. As a maintainer, you can update these **minimal** versions, only if required in [`doctor.rs`](src/doctor.rs). ## Running Tests Before running tests make sure that: [ ] redis is running [ ] starters/saas frontend package is built: ``` $ cd starters/saas/frontend $ npm i -g pnpm $ pnpm i && pnpm build ``` Running all tests should be done with: ``` $ cargo xtask test ``` ## Rebuilding your database and local generated entities This should write out a fresh DB structure (drops and migrates): ``` $ cargo loco db reset ``` And then, the entities generators connect to that newly minted DB, to generate a corresponding entities code: ``` $ cargo loco db entities ``` ## Publishing a new version **Test your changes** * [ ] Ensure you have the necessary local resources, such as `DB`/`Redis`, by executing the command `cargo loco doctor --environment test`. In case you don't have them, refer to the relevant documentation section for guidance. * [ ] run `cargo test` on the root to test Loco itself * [ ] cd `examples/demo` and run `cargo test` to test our "driver app" which exercises the framework in various ways * [ ] push your changes to Github to get the CI running and testing in various additional configurations that you don't have * [ ] CI should pass. Take note that all `starters-*` CI are using a **fixed version** of Loco and are not seeing your changes yet **Actually bump version + test and align starters** * [ ] in project root, run `cargo xtask bump-version` and give it the next version. Versions are without `v` prefix. Example: `0.1.3`. * [ ] Did the xtask testing workflow fail? * [ ] YES: fix errors, and re-run `cargo xtask bump-version` **with the same version as before**. * [ ] NO: great, move to publishing * [ ] Your repo may be dirty with fixes. Now that tests are passing locally commit the changes. Then run `cargo publish` to publish the next Loco version (remember: the starters at this point are pointing to the **next version already**, so we don't want to push until publish finished) * [ ] When publish finished successfully, push your changes to github * [ ] Wait for CI to finish. You want to be focusing more at the starters CI, because they will now pull the new version. * [ ] Did CI fail? * [ ] YES: This means you had a circumstance that's not predictable (e.g. some operating system issue). Fix the issue and **repeat the bumping process, advance a new version**. * [ ] NO: all good! you're done. **Book keeping** * [ ] Update changelog: (1) move vnext to be that new version of yours, (2) create a blank vnext * [ ] Think about if any of the items in the new version needs new documentation or update to the documentation -- and do it ## Errors Errors are done with `thiserror`. We adopt a minimalistic approach to errors. * We try to have _one error kind_ for the entirety of Loco. * Errors that cannot be handled, are _informative_ and so can be opaque (we don't offer deep matching on those) * Errors that can be handled and reasoned upon should be able to be matched and extract good knowledge from * To users, error should _not be cryptic_, and should indicate how to fix issues as much as possible, or point to the issue precisely ### Auto conversions When possible use `from` conversions. ```rust #[error(transparent)] JSON(#[from] serde_json::Error), ``` When complicated, implement a `From` trait yourself. This is done to _centralize_ errors into one place and not litter needless `map_err` code which holds error conversion logic (an exception is Context, see below). ### Context When you know a user might need context, resort to manually shaping the error with extra information. First, define the error: ```rust #[error("cannot parse `{1}`: {0}")] YAMLFile(#[source] serde_yaml::Error, String), ``` Then, shape it: ```rust serde_yaml::from_str(&rendered) .map_err(|err| Error::YAMLFile(err, selected_path.to_string_lossy().to_string())) ``` In this example, the information about where `rendered` came from was long lost at the `serde_yaml::from_str` callsite. Which is why errors were cryptic indicating bad YAML format, but not where it comes from (which file). In this case, we duplicate the YAML error type, leave one of those for auto conversions with `from`, where we don't have a file, and create a new specialized error type with the file information: `YAMLFile`. ## The `CONTRIBUTORS` comment Some files contain a special `CONTRIBUTORS` comment. This comment should contain context, special notes for that module, and a checklist if needed, so please make sure to follow it. ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [2022] Dotan Nahum, Elad Kaplan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README-pt_BR.md ================================================

Bem-vindo ao Loco

🚂 Loco is Rust on Rails.

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
[English](./README.md) · [中文](./README-zh_CN.md) · [Français](./README.fr.md) · Portuguese (Brazil) ・ [日本語](./README.ja.md) · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Español](./README.es.md) ## O que é o Loco? `Loco` é fortemente inspirado no Rails. Se você conhece Rails e Rust, se sentirá em casa. Se você só conhece Rails e é novo em Rust, achará o Loco refrescante. Não presumimos que você conheça o Rails. Para uma imersão mais profunda em como o Loco funciona, incluindo guias detalhados, exemplos e referências da API, confira nosso [site de documentação](https://loco.rs). ## Recursos do Loco: * `Convenção sobre Configuração:` Semelhante ao Ruby on Rails, o Loco enfatiza simplicidade e produtividade ao reduzir a necessidade de código boilerplate. Ele utiliza padrões sensatos, permitindo que os desenvolvedores se concentrem em escrever a lógica de negócios em vez de perder tempo com configuração. * `Desenvolvimento Rápido:` Com o objetivo de alta produtividade para o desenvolvedor, o design do Loco se concentra em reduzir código boilerplate e fornecer APIs intuitivas, permitindo que os desenvolvedores iteren rapidamente e construam protótipos com esforço mínimo. * `Integração ORM:` Modele seu negócio com entidades robustas, eliminando a necessidade de escrever SQL. Defina relacionamentos, validações e lógica personalizada diretamente em suas entidades para melhorar a manutenção e escalabilidade. * `Controladores:` Manipule os parâmetros de solicitações web, corpo, validação e renderize uma resposta que é consciente do conteúdo. Usamos Axum para o melhor desempenho, simplicidade e extensibilidade. Os controladores também permitem que você construa facilmente middlewares, que podem ser usados para adicionar lógica como autenticação, registro ou tratamento de erros antes de passar as solicitações para as ações principais do controlador. * `Views:` O Loco pode se integrar com mecanismos de template para gerar conteúdo HTML dinâmico a partir de templates. * `Trabalhos em segundo plano:` Realize trabalhos intensivos de computação ou I/O em segundo plano com uma fila baseada em Redis ou com threads. Implementar um trabalhador é tão simples quanto implementar uma função de execução para o trait Worker. * `Scheduler:` Simplifica o tradicional e frequentemente complicado sistema crontab, tornando mais fácil e elegante agendar tarefas ou scripts shell. * `Mailers:` Um mailer entregará e-mails em segundo plano usando a infraestrutura de trabalhador existente do loco. Tudo será transparente para você. * `Armazenamento:` No Armazenamento do Loco, facilitamos o trabalho com arquivos por meio de várias operações. O armazenamento pode ser em memória, no disco ou utilizar serviços em nuvem, como AWS S3, GCP e Azure. * `Cache:` O Loco fornece uma camada de cache para melhorar o desempenho da aplicação armazenando dados acessados frequentemente. Para ver mais recursos do Loco, confira nosso [site de documentação](https://loco.rs/docs/getting-started/tour/). ## Começando ```sh cargo install loco cargo install sea-orm-cli # Only when DB is needed ``` Agora você pode criar seu novo aplicativo (escolha "`SaaS` app"). ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · Saas App with client side rendering ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) 🚂 Loco app generated successfully in: myapp/ - assets: You've selected `clientside` for your asset serving configuration. Next step, build your frontend: $ cd frontend/ $ npm install && npm run build ``` Agora execute `cd` no seu `myapp` e inicie seu aplicativo: ```sh $ cargo loco start ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs listening on port 5150 ``` ## Impulsionado pelo Loco + [SpectralOps](https://spectralops.io) - vários serviços impulsionados pelo framework Loco + [Nativish](https://nativi.sh) - backend do aplicativo impulsionado pelo framework Loco ## Contribuidores ✨ Agradecimentos a essas pessoas maravilhosas: ================================================ FILE: README-zh_CN.md ================================================

Loco

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
[English](./README.md) · 中文 · [Français](./README.fr.md) · [Portuguese (Brazil)](./README-pt_BR.md) ・ [日本語](./README.ja.md) · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Español](./README.es.md) Loco 是一个用 Rust 编写的 Web 框架,类似于 Rails。Loco 提供快速构建 Web 应用的功能,并且允许创建自定义任务,可以通过 CLI 运行。 ## 特性 - **简单的 API**: 使用 Rust 的强类型系统确保安全性和可靠性。 - **快速开发**: 提供快速构建 Web 应用的工具和模板。 - **CLI 支持**: 可以创建和运行自定义 CLI 任务。 - **灵活性**: 支持自定义配置和扩展。 ## 安装 通过 Cargo 安装 Loco: ```sh cargo install loco ``` ## 快速开始 创建一个新的 Loco 项目: ```sh loco new my_project cd my_project ``` 启动开发服务器: ```sh cargo loco start ``` ## 贡献 欢迎对 Loco 的贡献!请阅读 [CONTRIBUTING.md](CONTRIBUTING.md) 了解更多信息。 ## 许可证 Loco 在 MIT 许可证下发布。详情请参阅 [LICENSE](LICENSE)。 --- For more details, you can visit the [original README file](https://github.com/loco-rs/loco/blob/master/README.md). ================================================ FILE: README.es.md ================================================

Bienvenido a Loco

🚂 Loco es Rust on Rails.

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
Español · [English](./README.md) · [中文](./README-zh_CN.md) · [Français](./README.fr.md) · [Português (Brasil)](./README-pt_BR.md) · [日本語](./README.ja.md) · [한국어](./README.ko.md) · [Русский](./README.ru.md) · Español ## ¿Qué es Loco? `Loco` está fuertemente inspirado en Rails. Si conoces Rails y Rust, te sentirás como en casa. Si solo conoces Rails y eres nuevo en Rust, encontrarás Loco refrescante. No asumimos que conozcas Rails. Para una explicación más profunda de cómo funciona Loco, incluyendo guías detalladas, ejemplos y referencias de la API, consulta nuestro [sitio de documentación](https://loco.rs). ## Características de Loco * `Convención sobre configuración:` Al igual que Ruby on Rails, Loco enfatiza la simplicidad y la productividad al reducir la necesidad de código repetitivo. Utiliza valores predeterminados sensatos, permitiendo a los desarrolladores centrarse en la lógica de negocio en lugar de perder tiempo en la configuración. * `Desarrollo rápido:` Loco está diseñado para una alta productividad del desarrollador, reduciendo el código repetitivo y proporcionando APIs intuitivas, permitiendo iterar rápidamente y construir prototipos con un esfuerzo mínimo. * `Integración ORM:` Modela tu negocio con entidades robustas, eliminando la necesidad de escribir SQL. Define relaciones, validaciones y lógica personalizada directamente en tus entidades para una mayor mantenibilidad y escalabilidad. * `Controladores:` Maneja parámetros de solicitudes web, cuerpo, validación y renderiza una respuesta consciente del contenido. Usamos Axum para el mejor rendimiento, simplicidad y extensibilidad. Los controladores también permiten construir middlewares fácilmente, que pueden usarse para agregar lógica como autenticación, registro o manejo de errores antes de pasar las solicitudes a las acciones principales del controlador. * `Vistas:` Loco puede integrarse con motores de plantillas para generar contenido HTML dinámico a partir de plantillas. * `Trabajos en segundo plano:` Realiza trabajos intensivos en computación o I/O en segundo plano con una cola respaldada por Redis o con hilos. Implementar un worker es tan simple como implementar una función perform para el trait Worker. * `Planificador:` Simplifica el tradicional y a menudo engorroso sistema crontab, facilitando y haciendo más elegante la programación de tareas o scripts de shell. * `Mailers:` Un mailer enviará correos electrónicos en segundo plano usando la infraestructura de background worker de Loco. Todo será transparente para ti. * `Almacenamiento:` En Loco Storage, facilitamos el trabajo con archivos a través de múltiples operaciones. El almacenamiento puede ser en memoria, en disco o usar servicios en la nube como AWS S3, GCP y Azure. * `Caché:` Loco proporciona una capa de caché para mejorar el rendimiento de la aplicación almacenando datos de acceso frecuente. Para ver más características de Loco, consulta nuestro [sitio de documentación](https://loco.rs/docs/getting-started/tour/). ## Primeros pasos ```sh cargo install loco cargo install sea-orm-cli # Solo si necesitas base de datos ``` Ahora puedes crear tu nueva app (elige "`SaaS` app"). ```sh ❯ loco new ✔ ❯ ¿Nombre de la app? · miapp ✔ ❯ ¿Qué te gustaría construir? · App SaaS con renderizado del lado del cliente ✔ ❯ Selecciona un proveedor de BD · Sqlite ✔ ❯ Selecciona el tipo de worker en segundo plano · Async (tareas async in-process con tokio) 🚂 App Loco generada exitosamente en: miapp/ - assets: Has seleccionado `clientside` para la configuración de tu servidor de assets. Siguiente paso, construye tu frontend: $ cd frontend/ $ npm install && npm run build ``` Ahora entra en tu `miapp` y arranca tu app: ```sh $ cargo loco start ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs listening on port 5150 ``` ## Proyectos impulsados por Loco * [SpectralOps](https://spectralops.io) - varios servicios impulsados por el framework Loco * [Nativish](https://nativi.sh) - backend de la app impulsado por el framework Loco ## Contribuidores ✨ Gracias a estas personas maravillosas: ================================================ FILE: README.fr.md ================================================

Loco vous souhaite la bienvenue

🚂 Loco c'est Rust on Rails.

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
[English](./README.md) · [中文](./README-zh_CN.md) · Français · [Portuguese (Brazil)](./README-pt_BR.md) ・ [日本語](./README.ja.md) · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Español](./README.es.md) ## À propos de Loco `Loco` est fortement inspiré de Rails. Si vous connaissez Rails et Rust, vous vous sentirez chez vous. Si vous ne connaissez que Rails et que vous êtes nouveau sur Rust, vous trouverez Loco rafraîchissant. Nous ne supposons pas que vous connaissez Rails. Pour un aperçu plus approfondie du fonctionnement de Loco, y compris des guides détaillés, des exemples et des références API, consultez notre [site Web de documentation](https://loco.rs). ## Caractéristiques de Loco: * `Convention plutôt que configuration`: Semblable à Ruby on Rails, Loco met l'accent sur la simplicité et la productivité en réduisant le besoin de code passe-partout. Il utilise des valeurs par défaut raisonnables, permettant aux développeurs de se concentrer sur l'écriture de la logique métier plutôt que de consacrer du temps à la configuration. * `Développement rapide`: Visant une productivité élevée des développeurs, la conception de Loco se concentre sur la réduction du code passe-partout et la fourniture d'API intuitives, permettant aux développeurs d'intégrer rapidement et de créer des prototypes avec un minimum d'effort. * `Intégration ORM`: Modélisez avec des entités robustes, éliminant le besoin d'écrire du SQL. Définissez les relations, la validation et la logique sur mesure directement sur vos entités pour une maintenabilité et une évolutivité améliorées. * `Contrôleurs`: Gérez les paramètres et le contenu des requêtes Web, la validation des requêtes et affichez une réponse tenant compte du contenu. Nous utilisons Axum pour une meilleure performance, simplicité et extensibilité. Les contrôleurs vous permettent également de créer facilement des middlewares, qui peuvent être utilisés pour ajouter une logique telle que l'authentification, la journalisation (logging) ou la gestion des erreurs avant de transmettre les requêtes aux actions du contrôleur principal. * `Vues`: Loco peut s'intégrer aux moteurs de _templates_ pour générer du contenu HTML dynamique à partir de modèles template. * `Tâches en arrière-plan`: Effectuer des calculs informatiques ou d'I/O (Entrée/Sortie) intensives en arrière-plan avec une file d'attente sauvegardée Redis ou avec des threads. Implémenter un travailleur (worker) est aussi simple que d'implémenter une fonction d'exécution pour le trait Worker. * `Scheduler`: Simplifie le système crontab traditionnel, souvent encombrant, en rendant plus facile et plus élégante la planification de tâches ou de scripts shell. * `Mailers`: Un logiciel de messagerie enverra des e-mails en arrière-plan en utilisant l'infrastructure de travail d'arrière-plan de Loco existante. Tout se passera sans problème pour vous. * `Stockage`: Loco Storage facilite le travail avec des fichiers via plusieurs opérations. Le stockage peut être en mémoire, sur disque ou utiliser des services cloud tels qu'AWS S3, GCP et Azure. * `Cache :` Loco fournit une strate cache pour améliorer les performances des applications en stockant les données fréquemment consultées. Pour en savoir plus sur les fonctionnalités de Loco, consultez notre [site Web de documentation](https://loco.rs/docs/getting-started/tour/). ## Commencez rapidement ```sh cargo install loco cargo install sea-orm-cli # Only when DB is needed ``` Vous pouvez maintenant créer votre nouvelle application (choisissez "`SaaS` app"). ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · Saas App with client side rendering ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) 🚂 Loco app generated successfully in: myapp/ - assets: You've selected `clientside` for your asset serving configuration. Next step, build your frontend: $ cd frontend/ $ npm install && npm run build ``` Maintenant, faite `cd` dans votre `myapp` et démarrez votre application: ```sh $ cargo loco start ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs listening on port 5150 ``` ## Servi par Loco + [SpectralOps](https://spectralops.io) - divers services servi par le framework Loco + [Nativish](https://nativi.sh) - app backend servi par le framework Loco ## Contributeurs ✨ Merci à ces personnes formidables : ================================================ FILE: README.ja.md ================================================

Locoへようこそ

🚂 LocoはRust on Railsです。

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
English · [中文](./README-zh_CN.md) · [Français](./README.fr.md) · [Portuguese (Brazil)](./README-pt_BR.md) ・ 日本語 · [한국어](./README.ko.md) · [Русский](./README.ru.md) ## Locoとは? `Loco`はRailsに強くインスパイアされています。RailsとRustの両方を知っているなら、すぐに馴染むでしょう。Railsしか知らなく、Rustに新しい方でも、Locoは新鮮に感じるでしょう。Railsを知っているとは仮定していません。 Locoの動作についての詳細なガイド、例、APIリファレンスは、[ドキュメント](https://loco.rs)をチェックしてください。 ## Locoの特徴: * `設定より規約:` Ruby on Railsに似て、Locoはボイラープレートコードを減らすことでシンプルさと生産性を発揮します。合理的なデフォルトを使用し、開発者が設定に時間を費やすのではなく、ビジネスロジックの記述に集中できるようにします。 * `迅速な開発:` 高い開発者生産性を目指し、Locoの設計はボイラープレートコードを減らし、直感的なAPIを提供することに焦点を当てています。これにより、開発者は迅速に反復し、最小限の努力でプロトタイプを構築できます。 * `ORM統合:` ビジネスモデルを堅牢なエンティティで表現し、SQLを書く必要をなくします。エンティティに直接関係、検証、およびカスタムロジックを定義でき、メンテナンス性とスケーラビリティが向上します。 * `コントローラー:` ウェブリクエストのパラメータ、ボディ、検証を処理し、コンテンツに応じたレスポンスをレンダリングします。最高のパフォーマンス、シンプルさ、拡張性のためにAxumを使用しています。コントローラーは、認証、ロギング、エラーハンドリングなどのロジックを追加するためのミドルウェアを簡単に構築できます。 * `ビュー:` Locoはテンプレートエンジンと統合し、テンプレートから動的なHTMLコンテンツを生成できます。 * `バックグラウンドジョブ:` Redisバックエンドキューやスレッドを使用して、計算またはI/O集約型のジョブをバックグラウンドで実行します。ワーカーを実装するのは、Workerトレイトのperform関数を実装するだけです。 * `スケジューラー:` 従来の、しばしば面倒なcrontabシステムを簡素化し、タスクやシェルスクリプトをスケジュールするのをより簡単かつエレガントにします。 * `メール送信:` メール送信者は、既存のLocoバックグラウンドワーカーインフラストラクチャを使用して、バックグラウンドでメールを配信します。すべてがシームレスに行われます。 * `ストレージ:` Locoのストレージでは、ファイル操作を簡素化します。ストレージはメモリ内、ディスク上、またはAWS S3、GCP、Azureなどのクラウドサービスを使用できます。 * `キャッシュ:` Locoは、頻繁にアクセスされるデータを保存することでアプリケーションのパフォーマンスを向上させるためのキャッシュレイヤーを提供します。 Locoの詳細な機能については、[ドキュメントウェブサイト](https://loco.rs/docs/getting-started/tour/)を確認してください。 ## 始め方 ```sh cargo install loco cargo install sea-orm-cli # データベースが必要な場合のみ ``` 以下で新しいアプリを作成できます(「`SaaS`アプリ」を選択)。 ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · SaaS app (with DB and user auth) ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) ✔ ❯ Select an asset serving configuration · Client (configures assets for frontend serving) 🚂 Loco app generated successfully in: myapp/ ``` 次に`myapp`に移動し、アプリを起動します: ```sh $ cargo loco start ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs listening on port 5150 ``` ## Locoによって開発されています + [SpectralOps](https://spectralops.io) - Locoフレームワークによる各種サービス + [Nativish](https://nativi.sh) - Locoフレームワークによるアプリバックエンド ## 貢献者 ✨ これらの素晴らしい人々に感謝します: ================================================ FILE: README.ko.md ================================================

Loco에 오신 것을 환영합니다

🚂 Loco는 Rust on Rails입니다.

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
[English](./README.md) · [中文](./README-zh_CN.md) · [Français](./README.fr.md) · [Portuguese (Brazil)](./README-pt_BR.md) ・ [日本語](./README.ja.md) · 한국어 · [Русский](./README.ru.md) · [Español](./README.es.md) ## Loco란? `Loco`는 Rails에서 강한 영감을 받았습니다. Rails와 Rust를 모두 알고 계신다면 친숙하게 느껴지실 것이며, Rails만 알고 Rust를 처음 접하시는 분들에게도 Loco는 새롭게 다가올 것입니다. 참고로, Rails에 대한 사전 지식은 필수가 아닙니다. Loco의 작동 방식에 대해 더 자세히 알아보려면 가이드, 예제, API 참조를 포함한 [문서 웹사이트](https://loco.rs)를 확인해보세요. ## Loco의 주요 기능: * `설정보다 관습`: Ruby on Rails와 유사하게, Loco는 상용구 코드의 필요성을 줄임으로써 단순성과 생산성을 강조합니다. 합리적인 기본값을 사용하여 개발자가 설정보다는 비즈니스 로직 작성에 집중할 수 있게 합니다. * `빠른 개발`: 높은 개발자 생산성을 목표로 하며, Loco의 설계는 상용구 코드를 줄이고 직관적인 API를 제공하여 개발자가 최소한의 노력으로 빠르게 반복하고 프로토타입을 구축할 수 있도록 합니다. * `ORM 통합`: SQL 작성 없이 비즈니스를 강력한 엔티티로 모델링합니다. 관계, 유효성 검사, 사용자 정의 로직을 엔티티에 직접 정의하여 유지보수성과 확장성을 향상시킵니다. * `컨트롤러`: 웹 요청 매개변수, 본문, 유효성 검사를 처리하고 컨텐츠를 인식하는 응답을 렌더링합니다. 최고의 성능, 단순성, 확장성을 위해 Axum을 사용합니다. 또한 컨트롤러를 통해 인증, 로깅, 오류 처리와 같은 로직을 추가할 수 있는 미들웨어를 쉽게 구축할 수 있습니다. * `뷰`: Loco는 템플릿에서 동적 HTML 콘텐츠를 생성하기 위해 템플릿 엔진과 통합할 수 있습니다. * `백그라운드 작업`: Redis 기반 큐 또는 스레드를 사용하여 계산이나 I/O 집약적인 작업을 백그라운드에서 수행합니다. Worker 트레이트에 대한 perform 함수를 구현하는 것만으로도 워커를 구현할 수 있습니다. * `스케줄러`: 전통적이고 번거로운 crontab 시스템을 단순화하여 작업이나 셸 스크립트를 더 쉽고 우아하게 예약할 수 있습니다. * `메일러`: 메일러는 기존 loco 백그라운드 워커 인프라를 사용하여 이메일을 백그라운드에서 전달합니다. 모든 과정이 매끄럽게 처리됩니다. * `스토리지`: Loco 스토리지는 여러 작업을 통해 파일 작업을 용이하게 합니다. 메모리 내, 디스크, AWS S3, GCP, Azure와 같은 클라우드 서비스를 사용할 수 있습니다. * `캐시`: Loco는 자주 접근하는 데이터를 저장하여 애플리케이션 성능을 향상시키는 캐시 레이어를 제공합니다. 더 많은 Loco 기능을 보려면 [문서 웹사이트](https://loco.rs/docs/getting-started/tour/)를 확인하세요. ## 시작하기 ```sh cargo install loco cargo install sea-orm-cli # Only when DB is needed ``` 이제 새로운 앱을 만들 수 있습니다 ("`SaaS 앱`" 선택). ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · Saas App with client side rendering ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) 🚂 Loco app generated successfully in: myapp/ - assets: You've selected `clientside` for your asset serving configuration. Next step, build your frontend: $ cd frontend/ $ npm install && npm run build ``` 이제 `myapp` 디렉토리로 이동하여 앱을 시작하세요: ```sh $ cargo loco start ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs listening on port 5150 ``` ## Loco 사용 사례 + [SpectralOps](https://spectralops.io) - Loco 프레임워크로 구동되는 다양한 서비스 + [Nativish](https://nativi.sh) - Loco 프레임워크로 구동되는 앱 백엔드 ## 기여자 ✨ 이 멋진 분들께 감사드립니다: ================================================ FILE: README.md ================================================

Welcome to Loco

🚂 Loco is Rust on Rails.

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
English · [中文](./README-zh_CN.md) · [Français](./README.fr.md) · [Portuguese (Brazil)](./README-pt_BR.md) ・ [日本語](./README.ja.md) · [한국어](./README.ko.md) · [Русский](./README.ru.md) · [Español](./README.es.md) ## What's Loco? `Loco` is strongly inspired by Rails. If you know Rails and Rust, you'll feel at home. If you only know Rails and new to Rust, you'll find Loco refreshing. We do not assume you know Rails. For a deeper dive into how Loco works, including detailed guides, examples, and API references, check out our [documentation website](https://loco.rs). ## Features of Loco: * `Convention Over Configuration:` Similar to Ruby on Rails, Loco emphasizes simplicity and productivity by reducing the need for boilerplate code. It uses sensible defaults, allowing developers to focus on writing business logic rather than spending time on configuration. * `Rapid Development:` Aim for high developer productivity, Loco’s design focuses on reducing boilerplate code and providing intuitive APIs, allowing developers to iterate quickly and build prototypes with minimal effort. * `ORM Integration:` Model your business with robust entities, eliminating the need to write SQL. Define relationships, validation, and custom logic directly on your entities for enhanced maintainability and scalability. * `Controllers`: Handle web requests parameters, body, validation, and render a response that is content-aware. We use Axum for the best performance, simplicity, and extensibility. Controllers also allow you to easily build middlewares, which can be used to add logic such as authentication, logging, or error handling before passing requests to the main controller actions. * `Views:` Loco can integrate with templating engines to generate dynamic HTML content from templates. * `Background Jobs:` Perform compute or I/O intensive jobs in the background with a Redis backed queue, or with threads. Implementing a worker is as simple as implementing a perform function for the Worker trait. * `Scheduler:` Simplifies the traditional, often cumbersome crontab system, making it easier and more elegant to schedule tasks or shell scripts. * `Mailers:` A mailer will deliver emails in the background using the existing loco background worker infrastructure. It will all be seamless for you. * `Storage:` In Loco Storage, we facilitate working with files through multiple operations. Storage can be in-memory, on disk, or use cloud services such as AWS S3, GCP, and Azure. * `Cache:` Loco provides an cache layer to improve application performance by storing frequently accessed data. So see more Loco features, check out our [documentation website](https://loco.rs/docs/getting-started/tour/). ## Getting Started ```sh cargo install loco cargo install sea-orm-cli # Only when DB is needed ``` Now you can create your new app (choose "`SaaS` app"). ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · Saas App with client side rendering ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) 🚂 Loco app generated successfully in: myapp/ - assets: You've selected `clientside` for your asset serving configuration. Next step, build your frontend: $ cd frontend/ $ npm install && npm run build ``` Now `cd` into your `myapp` and start your app: ```sh $ cargo loco start ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs listening on port 5150 ``` ## Powered by Loco + [SpectralOps](https://spectralops.io) - various services powered by Loco framework + [Nativish](https://nativi.sh) - app backend powered by Loco framework ## Contributors ✨ Thanks goes to these wonderful people: ================================================ FILE: README.ru.md ================================================

Добро пожаловать в *Loco*

🚂 Loco is Rust on Rails.

[![crate](https://img.shields.io/crates/v/loco-rs.svg)](https://crates.io/crates/loco-rs) [![docs](https://docs.rs/loco-rs/badge.svg)](https://docs.rs/loco-rs) [![Discord channel](https://img.shields.io/badge/discord-Join-us)](https://discord.gg/fTvyBzwKS8)
[English](./README.md) · [中文](./README-zh_CN.md) · [Français](./README.fr.md) · [Portuguese (Brazil)](./README-pt_BR.md) ・ [日本語](./README.ja.md) · Русский · [Español](./README.es.md) ## Что такое Loco? *Loco* сильно вдохновлён проектом *Ruby on Rails*. Если вы знакомы и с *Rails*, и с *Rust*, вы будете чувствовать себя как дома. Если вы знаете только *Rails*, и не знакомы с *Rust*, *Loco* будет для вас чем-то освежающим. Если вам интересно узнать внутрение устройство *Loco*, включая детальные гайды, примеры, и устройство API, почитайте нашу [документацию](https://loco.rs). ## Фишки Loco: - **Простота превыше конфигурации**: Подобно *Ruby on Rails*, *Loco* делает упор на простоту и продуктивность, снижая потребность в лишнем коде. *Loco* использует оптимальные настройки по-умолчанию, давая разработчикам возможность сфокусироваться на написании бизнес логики, а не конфигурации. - **Быстрая разработка**: Ставя акцент на высокой производительности разработчика, Дизайн *Loco* фокусируется на сокращении ненужного кода и предоставления интуитивного API. Это позволяет быстро создавать прототипы без лишних усилий. - **ORM интеграция**: Стройте свой бизнес с крепкими составляющими, убирая необходимость писать SQL. Определяйте взаимосвязи, проверку, и кастомную логику прямо в составляющих, упрощая поддержку и рост кодовой базы. - **Контролеры**: Обрабатывайте параметры и данные web-запросов, проверяйте их содержимое, отображайте ответ с учетом запроса. Мы используем *Axum* для достижения наилучшей производительности, простоты, и возможности расширения. Также, контролеры облегчают внедрение middleware. Это может быть использовано для добавления всевозможной логики: аутентификации, логгинга, или обработки ошибок перед отправкой на сервер. - **Виды**: *Loco* может интегрироваться с template-движками для генерации динамического HTML из шаблонов. - **Фоновые задачи**: Исполняйте I/O и другие тяжелые операции в фоновом режиме с помощью *Redis*, или потоков. Для написания функционала фоновой задачи нужно всего лишь написать функцию `perform` из `trait Worker`. - **Планировщик**: Облегчает традиционную, часто громоздкую систему, упрощая планировку задач и исполнение shell-скриптов. - **Отправка электронной почты**: Отправка электронной почты в фоновом режиме, без необходимости создавать новую фоновую задачу. - **Хранилище**: Мы способствуем работе с файлами несколькими путями: хранение в памяти, на диске, или использование облачных сервисов как *AWS*, *S3*, *GCP*, и *Azure*. - **Кэширование**: *Loco* кэширует частые запросы для улучшения производительности приложения. У *Loco* есть ещё множество фишек, котрые вы можете посмотреть на [сайте документации](https://loco.rs/docs/getting-started/tour/). ## Установка ```sh cargo install loco cargo install sea-orm-cli # Only when DB is needed ``` Теперь вы можете создать свое новое приложение (выберете "`SaaS` app"). ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · Saas App with client side rendering ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) 🚂 Loco app generated successfully in: myapp/ - assets: You've selected `clientside` for your asset serving configuration. Next step, build your frontend: $ cd frontend/ $ npm install && npm run build ``` Теперь выполните `cd` в папку `myapp` и запускайте приложение: ```sh $ cargo loco start ▄ ▀ ▀ ▄ ▄ ▀ ▄ ▄ ▄▀ ▄ ▀▄▄ ▄ ▀ ▀ ▀▄▀█▄ ▀█▄ ▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█ ██████ █████ ███ █████ ███ █████ ███ ▀█ ██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄ ██████ █████ ███ █████ █████ ███ ████▄ ██████ █████ ███ █████ ▄▄▄ █████ ███ █████ ██████ █████ ███ ████ ███ █████ ███ ████▀ ▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ https://loco.rs listening on port 5150 ``` ## Проекты, использующие *Loco* + [SpectralOps](https://spectralops.io) - различные сервисы, использующие *Loco* framework + [Nativish](https://nativi.sh) - backend приложения, использующий *Loco* ## Контрибьютеры ✨ Спасибо всем этим прекрасным людям: ================================================ FILE: SECURITY.md ================================================ # Security Policy By researching and submitting a vulnerability, you'll be helping open source and this project's goal to provide a fast to build fast to run Web framework based on Rust. ## Reporting a Vulnerability Please report directly to [dotan@rng0.io](mailto:dotan@rng0.io). We will credit you as a committer with every vulnerability you find that we can validate. ================================================ FILE: build/embedded_assets.rs ================================================ use std::collections::{HashMap, HashSet}; use std::{ env, fs::{self, File}, io::{self, Write}, path::{Path, PathBuf}, }; pub fn build_static_assets(out_dir: &Path) { // Determine the application root directory using Cargo environment variables let Some(app_dir) = find_app_directory(out_dir) else { eprintln!("Error: Could not determine application directory"); return; }; let app_dir_str = app_dir.to_string_lossy().to_string(); println!("cargo:warning=Building with embedded_assets feature"); println!("cargo:warning=Application directory: {app_dir_str}"); println!("cargo:warning=Assets will only be loaded from the application directory"); println!("cargo:rerun-if-changed={app_dir_str}/assets/"); println!("cargo:rerun-if-changed={app_dir_str}/src/assets/"); // Also run build script again if the build files change println!("cargo:rerun-if-changed=build/embedded_assets.rs"); let generated_path = out_dir.join("generated_code"); // Create the directory if it doesn't exist if let Err(e) = fs::create_dir_all(&generated_path) { eprintln!("Warning: Could not create directory: {e}"); return; } // Only search in the application directory let app_root = app_dir; // Find all directories recursively, without filtering by name let all_dirs = discover_all_directories(&app_root.join("assets")); println!("cargo:warning=Discovered directories for assets:"); for dir in &all_dirs { println!("cargo:warning= - {}", dir.display()); } // Single collection for all files let mut all_files = HashMap::new(); // Store the assets directory reference to pass to collect_all_files let assets_dir = app_root.join("assets"); // Process all discovered directories for dir in &all_dirs { // Process all files in this directory collect_all_files(dir, &assets_dir, &mut all_files); } // Generate code for all assets if all_files.is_empty() { println!("cargo:warning=No asset files found"); // Generate empty asset files if no files found if let Err(e) = generate_empty_asset_files(&generated_path) { eprintln!("Warning: Failed to generate empty asset files: {e}"); } } else { println!("cargo:warning=Found {} asset files", all_files.len()); if let Err(e) = generate_asset_code(&all_files, &generated_path) { eprintln!("Warning: Failed to generate asset code: {e}"); } } } pub fn find_app_directory(out_dir: &Path) -> Option { // Find project root from OUT_DIR by going up to parent of "target" directory let mut path = out_dir.to_path_buf(); while path.pop() { if path.file_name().and_then(|n| n.to_str()) == Some("target") && path.pop() { return Some(path); } // Safety check if path.as_os_str().is_empty() { break; } } // Fallback to current directory env::current_dir().ok() } pub fn discover_all_directories(app_root: &Path) -> Vec { let mut directories = Vec::new(); let mut visited = HashSet::new(); // Only include the directory if it exists if app_root.exists() { // Add the root directory itself directories.push(app_root.to_path_buf()); // Start recursive discovery recursively_collect_directories(app_root, &mut directories, &mut visited); } // Sort directories by their string representation to ensure consistent ordering directories.sort_by(|a, b| { a.to_string_lossy() .to_string() .cmp(&b.to_string_lossy().to_string()) }); directories } pub fn recursively_collect_directories( dir: &Path, directories: &mut Vec, visited: &mut std::collections::HashSet, ) { // Check if we've already visited this directory if !visited.insert(dir.to_path_buf()) { return; } // Continue recursively discovering subdirectories if let Ok(entries) = fs::read_dir(dir) { for entry in entries.flatten() { let path = entry.path(); if path.is_dir() { // Add this directory to our list directories.push(path.clone()); // Continue recursion recursively_collect_directories(&path, directories, visited); } } } } pub fn collect_all_files(dir: &Path, assets_dir: &Path, all_files: &mut HashMap) { if let Ok(entries) = fs::read_dir(dir) { for entry in entries.flatten() { let path = entry.path(); if path.is_file() { // Skip if we can't determine the file path or extension let full_path = path.to_string_lossy().to_string(); // Create a relative path based on the assets directory let Ok(rel_path) = path.strip_prefix(assets_dir) else { println!( "cargo:warning=Failed to strip prefix for path: {}", path.display() ); continue; // Skip this file if we can't determine its relative path }; // Format the key as a path, using forward slashes let mut key = format!("/{}", rel_path.to_string_lossy().replace('\\', "/")); // Remove any double slashes key = key.replace("//", "/"); // Special handling for templates in views directory if key.starts_with("/views/") { // For templates, we want to: // 1. Strip "/views/" prefix for proper Tera template inheritance // 2. Keep the relative path structure for nested templates key = key.trim_start_matches("/views/").to_string(); } // Log what we found println!("cargo:warning=Found asset: {} -> {}", path.display(), key); // Store the file all_files.insert(full_path, key); } } } } #[allow(clippy::too_many_lines)] pub fn generate_asset_code( all_files: &HashMap, output_path: &Path, ) -> io::Result<()> { // Create vectors to track which files go where let mut static_assets = Vec::new(); let mut template_files = Vec::new(); // Simple categorization: if file ends with .html or .htm, it's a template, otherwise static asset for (path, key) in all_files { if std::path::Path::new(key) .extension() .is_some_and(|ext| ext.eq_ignore_ascii_case("html")) || std::path::Path::new(key) .extension() .is_some_and(|ext| ext.eq_ignore_ascii_case("htm")) { template_files.push((path.clone(), key.clone())); } else { static_assets.push((path.clone(), key.clone())); } } // Sort static assets by key for consistent output static_assets.sort_by(|a, b| a.1.cmp(&b.1)); // Build template dependency map and sort templates let mut template_deps: HashMap> = HashMap::new(); println!("cargo:warning=Analyzing template dependencies..."); // First pass: read all template contents and find their dependencies for (path, key) in &template_files { println!("cargo:warning=Reading template: {key}"); match fs::read_to_string(path) { Ok(content) => { // Look for {% extends "..." %} pattern if let Some(extends) = content .lines() .find(|line| line.trim().starts_with("{% extends")) { if let Some(parent) = extends .split('"') .nth(1) .or_else(|| extends.split('\'').nth(1)) { template_deps.insert(key.clone(), Some(parent.to_string())); println!("cargo:warning=Template {key} extends {parent}"); } } else { template_deps.insert(key.clone(), None); println!("cargo:warning=Template {key} has no parent"); } } Err(e) => { println!("cargo:warning=Failed to read template {path}: {e}"); } } } println!("cargo:warning=Template dependencies:"); for (template, parent) in &template_deps { if let Some(p) = parent { println!("cargo:warning= {template} -> {p}"); } else { println!("cargo:warning= {template} (no parent)"); } } // Sort templates so that parents come before children let mut sorted_templates = Vec::new(); let mut processed = HashSet::new(); // First add all base templates (those with no parents), sorted alphabetically let mut base_templates: Vec<_> = template_deps .iter() .filter(|(_, parent)| parent.is_none()) .map(|(key, _)| key.clone()) .collect(); base_templates.sort(); // Sort base templates alphabetically for key in base_templates { println!("cargo:warning=Adding base template: {key}"); processed.insert(key.clone()); sorted_templates.push(key); } // Then add all child templates, level by level let mut added_in_this_pass; while { added_in_this_pass = false; let mut level_templates = Vec::new(); // Collect all templates at this level for (key, parent) in &template_deps { if processed.contains(key) { continue; } if let Some(parent) = parent { if processed.contains(parent) { level_templates.push(key.clone()); } } } // Sort templates at this level alphabetically level_templates.sort(); // Add them to the final list for key in level_templates { if let Some(Some(parent)) = template_deps.get(&key) { println!("cargo:warning=Adding child template: {key} (extends {parent})"); } processed.insert(key.clone()); sorted_templates.push(key); added_in_this_pass = true; } added_in_this_pass } {} // Add any remaining templates that weren't processed, sorted alphabetically let mut remaining: Vec<_> = template_deps .keys() .filter(|key| !processed.contains(*key)) .cloned() .collect(); remaining.sort(); for key in remaining { println!("cargo:warning=Adding unprocessed template: {key}"); sorted_templates.push(key); } println!("cargo:warning=Final template order:"); for (idx, template) in sorted_templates.iter().enumerate() { println!("cargo:warning= {}. {}", idx + 1, template); } // Generate static assets file let static_file = output_path.join("static_assets.rs"); // Create the static assets content let mut static_lines = vec![ "#[must_use]\n".to_string(), "pub fn get_embedded_static_assets() -> std::collections::HashMap {\n".to_string(), " let mut assets = std::collections::HashMap::new();\n".to_string() ]; for (path, key) in &static_assets { let insert_line = format!( r#" assets.insert("{0}".to_string(), include_bytes!("{1}") as &[u8]);"#, key, path.replace('\\', "/") ); static_lines.push(format!("{insert_line}\n")); } static_lines.push(" assets\n".to_string()); static_lines.push("}\n".to_string()); // Write static assets content to file let mut static_file = File::create(static_file)?; for line in static_lines { static_file.write_all(line.as_bytes())?; } // Generate templates file let templates_file = output_path.join("view_templates.rs"); // Create the templates content with detailed comments let mut template_lines = vec![ "/// Returns a BTreeMap of templates in dependency order (parents before children)\n" .to_string(), "#[must_use]\n".to_string(), "pub fn get_embedded_templates() -> std::collections::BTreeMap {\n" .to_string(), " let mut templates = std::collections::BTreeMap::new();\n".to_string(), ]; // Add templates in dependency order with comments for template_key in &sorted_templates { if let Some((path, _)) = template_files.iter().find(|(_, k)| k == template_key) { // Add a comment showing the dependency if let Some(Some(parent)) = template_deps.get(template_key) { template_lines.push(format!(" // Template that extends {parent}\n")); } else { template_lines.push(" // Base template with no parent\n".to_string()); } let insert_line = format!( r#" templates.insert("{0}".to_string(), include_str!("{1}"));"#, template_key, path.replace('\\', "/") ); template_lines.push(format!("{insert_line}\n")); } } template_lines.push("\n templates\n".to_string()); template_lines.push("}\n".to_string()); // Write templates content to file let mut templates_file = File::create(templates_file)?; for line in template_lines { templates_file.write_all(line.as_bytes())?; } println!( "cargo:warning=Generated code for {} static assets and {} templates", static_assets.len(), sorted_templates.len() ); Ok(()) } pub fn generate_empty_asset_files(output_path: &Path) -> io::Result<()> { // Generate empty static assets file let static_file = output_path.join("static_assets.rs"); let static_code = r"#[must_use] pub fn get_embedded_static_assets() -> std::collections::HashMap { // No assets found std::collections::HashMap::new() } "; let mut file = File::create(static_file)?; file.write_all(static_code.as_bytes())?; // Generate empty templates file let templates_file = output_path.join("view_templates.rs"); let templates_code = r"#[must_use] pub fn get_embedded_templates() -> std::collections::HashMap { // No templates found std::collections::HashMap::new() } "; let mut file = File::create(templates_file)?; file.write_all(templates_code.as_bytes())?; Ok(()) } ================================================ FILE: build.rs ================================================ #[cfg(feature = "embedded_assets")] use std::{env, path::Path}; fn main() { #[cfg(feature = "embedded_assets")] embedded_assets_main(); #[cfg(not(feature = "embedded_assets"))] { // No-op when feature is disabled } } #[cfg(feature = "embedded_assets")] fn embedded_assets_main() { // Import the embedded_assets module from the build directory #[path = "build/embedded_assets.rs"] mod embedded_assets; use embedded_assets::build_static_assets; // Get OUT_DIR environment variable - this is required for build scripts let out_dir = env::var("OUT_DIR").unwrap_or_else(|e| { // This should trigger a build failure panic!("OUT_DIR environment variable not set: {e}"); }); // Convert to a path let out_dir_path = Path::new(&out_dir); // Call the build_static_assets function with the OUT_DIR build_static_assets(out_dir_path); } ================================================ FILE: docs-site/.gitignore ================================================ public/ node_modules/ ================================================ FILE: docs-site/config.toml ================================================ # The URL the site will be built for base_url = "https://loco.rs" title = "Loco.rs - Productivity-first Rust Fullstack Web Framework" description = "Loco.rs is like Ruby on Rails for Rust. Use it to quickly build and deploy Rust based apps from zero to production." # Whether to automatically compile all Sass files in the sass directory compile_sass = true # Whether to generate a feed file for the site generate_feeds = true feed_filenames = ["blog/atom.xml", "blog/rss.xml"] # When set to "true", the generated HTML files are minified. minify_html = false # The taxonomies to be rendered for the site and their configuration. taxonomies = [ { name = "authors" }, # Basic definition: no feed or pagination ] # Whether to build a search index to be used later on by a JavaScript library build_search_index = true [markdown] # Whether to do syntax highlighting # Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola highlight_theme = "css" highlight_code = true highlight_themes_css = [ { theme = "OneHalfDark", filename = "syntax-theme-dark.css" }, { theme = "OneHalfLight", filename = "syntax-theme-light.css" }, ] [extra] # Put all your custom variables here # Menu items [[extra.menu.main]] name = "Docs" section = "docs" url = "/docs/getting-started/tour/" weight = 10 [[extra.menu.main]] name = "Blog" section = "blog" url = "/blog/" weight = 20 [[extra.menu.main]] name = "Casts" section = "casts" url = "/casts/" [[extra.menu.social]] name = "Twitter" pre = '' url = "https://twitter.com/jondot" weight = 10 [[extra.menu.social]] name = "GitHub" pre = '' url = "https://github.com/loco-rs/loco" post = "v0.1.0" weight = 20 [[extra.homepage.features]] name = "Models" description = 'Model your business with rich entities and avoid writing SQL, backed by SeaORM. Build relations, validation and custom logic on your entities for the best maintainability.' example = '''```rust impl Model { pub async fn find_by_email(db: &DatabaseConnection, email: &str) -> ModelResult { Users::find() .filter(eq(Column::Email, email)) .one(db).await? .ok_or_else(|| ModelError::EntityNotFound) } pub async fn create_report(&self, ctx: &AppContext) -> Result<()> { ReportWorker::perform_later( &ctx, ReportArgs{ user_id: self.id } ).await?; } } ``` ''' [[extra.homepage.features]] name = "Controllers" description = 'Handle Web requests parameters, body, validation, and render a response that is content-aware. We use Axum for the best performance, simplicity and extensibility.' example = '''```rust pub async fn get_one( respond_to: RespondTo, Path(id): Path, State(ctx): State, ) -> Result { let item = Notes::find_by_id(id).one(&ctx.db).await?; match respond_to { RespondTo::Html => html_view(&item), _ => format::json(item), } } pub fn routes() -> Routes { Routes::new() .prefix("notes") .add("/{id}", get(get_one)) } ``` ''' [[extra.homepage.features]] name = "Views" description = 'Use server-rendered templates with Tera or JSON. Loco can render views on the server or work with a frontend app seamlessly. Configure your fullstack set up any way you like.' example = '''```rust // Literals format::text("Loco") // Tera view engine format::render().view(v, "home/hello.html", json!({})) // strongly typed JSON responsed, backed by `serde` format::json(Health { ok: true }) // Etags, cookies, and more format::render().etag("loco-etag")?.empty() ``` ''' [[extra.homepage.features]] name = "Background Jobs" description = 'Perform compute or I/O intensive jobs in the background with a Redis backed queue, or with threads. Implementing a worker is as simple as implementing a perform function for the Worker trait.' example = '''```rust impl worker::Worker for UsersReportWorker { async fn perform(&self, args: DownloadArgs) -> worker::Result<()> { let all = Users::find() .all(&self.ctx.db) .await .map_err(Box::from)?; for user in &all { println!("user: {}", user.id); } Ok(()) } } ``` ''' [[extra.homepage.features]] name = "Deployment" description = 'Easily generate deployment configurations with a guided CLI interface. Select from deployment options for tailored deployment setups.' example = '''```sh $ cargo loco generate deployment ? ❯ Choose your deployment › ❯ Docker ❯ Nginx .. ✔ ❯ Choose your deployment · Docker skipped (exists): "Dockerfile" added: ".dockerignore" ``` ''' [[extra.homepage.features]] name = "Scheduler" description = 'Simplifies the traditional, often cumbersome crontab system, making it easier and more elegant to schedule tasks or shell scripts.' example = '''```yaml jobs: db_vaccum: run: "db_vaccum.sh" shell: true schedule: "0 0 * * *" tags: ["maintenance"] send_birthday: run: "user_birthday_task" schedule: "Run every 2 hours" tags: ["marketing"] ``` ''' ================================================ FILE: docs-site/content/_index.md ================================================ +++ title = "Loco" # The homepage contents [extra] lead = 'The one-person framework for Rust for side-projects and startups' url = "/docs/getting-started/tour/" url_button = "Get started" # Menu items [[extra.menu.main]] name = "Docs" section = "docs" url = "/docs/getting-started/tour/" weight = 10 [[extra.menu.main]] name = "Blog" section = "blog" url = "/blog/" weight = 20 [[extra.menu.main]] name = "Casts" section = "casts" url = "/casts/" weight = 20 [[extra.list]] title = "🔋 Batteries included" content = 'Empower the 1-person team. Service, data, emails, background jobs, tasks, CLI to drive it, everything is included.' [[extra.list]] title = "🔮 Rails is great" content = 'Loco follows Rails. There, I said it. Rails concepts are carefully adapted to modern Rust development.' [[extra.list]] title = "🏅 Deliver with confidence" content = "Unapologetically optimized for the solo developer. Complexity and heavylifting is tucked away." [[extra.list]] title = "⚡️ Scale when needed" content = "Split, reconfigure, or use only parts of Loco when you need to. Build and grow without pain." [[extra.list]] title = "🚀️ Build incrementally" content = "Use what you need. Just a service, a service with a database, a background job worker, or a task." [[extra.list]] title = "🚦Test driven everything" content = "Test your app with very little effort. Models, controllers, background jobs and more. Ship fast with confidence." +++ ================================================ FILE: docs-site/content/authors/_index.md ================================================ +++ title = "Authors" description = "The authurs of the blog articles." draft = false # If add a new author page in this section, please add a new item, # and the format is as follows: # # "author-name-in-url" = "the-full-path-of-the-author-page" # # Note: We use quoted keys here. [extra.author_pages] "team-loco" = "authors/team-loco.md" "limpidcrypto" = "authors/limpidcrypto.md" +++ The authors of the blog articles. ================================================ FILE: docs-site/content/authors/limpidcrypto.md ================================================ +++ title = "LimpidCrypto" description = "Building open source tools for cryptocurrency development" date = 2024-01-25T18:03:52+01:00 updated = 2024-01-25T18:03:52+01:00 draft = false +++ Creating the Building Blocks for Cryptocurrency Development. To my [website](https://limpidcrypto.com). ================================================ FILE: docs-site/content/authors/team-loco.md ================================================ +++ title = "Team Loco" description = "Creators of the Loco framework" date = 2021-04-01T08:50:45+00:00 updated = 2021-04-01T08:50:45+00:00 draft = false +++ Primary maintainers of the [Loco](https://loco.rs) framework: [Dotan Nahum](https://github.com/jondot), [Elad Kaplan](https://github.com/kaplanelad). ================================================ FILE: docs-site/content/blog/_index.md ================================================ +++ title = "Blog" description = "Blog" sort_by = "date" paginate_by = 10 template = "blog/section.html" +++ ================================================ FILE: docs-site/content/blog/angular-frontend.md ================================================ +++ title = "Creating Frontend Website Using Angular" description = "Setting up a Loco app for serving an Angular clientside app is easy. Learn how to configure and set up a full-stack Angular app with Loco." date = 2024-01-25T18:03:52+01:00 updated = 2024-01-25T18:03:52+01:00 draft = false template = "blog/page.html" [taxonomies] authors = ["LimpidCrypto"] +++ ## Overview 1. Create new SaaS project 2. Edit `.devcontainer/Dockerfile` 3. Reopen the project in the Dev Container 4. Delete frontend directory 5. Generate new Angular frontend 6. Build frontend 7. Edit `config/development.yml` 8. Start Loco ## Create new SaaS project 1. Run `loco new` to create a new project 2. Navigate through the instructions until you reach the point where to decide what type of project to create 3. Select "SaaS app (with DB and user auth)" ## Edit ".devcontainer/Dockerfile" 1. Open `.devcontainer/Dockerfile` 2. Replace the content with the following: ```Dockerfile FROM mcr.microsoft.com/vscode/devcontainers/rust:0-1 # Install postgresql-client and sea-orm-cli RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ && apt-get -y install --no-install-recommends postgresql-client \ && cargo install sea-orm-cli \ && chown -R vscode /usr/local/cargo # Install Node.js and npm RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \ && apt-get install -y nodejs # Install Angular CLI RUN npm install -g @angular/cli COPY .env /.env ``` The Dockerfile will provide you with everything you need to develop a Loco app with an Angular frontend. ## Reopen the project in the Dev Container With VSCode it is super easy to reopen and run the project in a Dev Container. 1. Press `Crtl + Shift + P` 2. Select `Dev Containers: Repopen in Container` 3. VSCode will open the project in the dev container. This can take a while when it is built for the first time. 4. Delete the existing `frontend` directory Loco comes with a Vite React frontend. We can delete the whole directory because the Angular CLI will set up everything we need ## Generate new Angular frontend 1. From the project root execute `ng new frontend` to create a new Angular project 2. Navigate through the instructions ## Build frontend 1. Run `ng build` to build the Angular frontend ## Edit "config/development.yml" As you may have noticed Angular has built the frontend into `frontend/dist/frontend/browser`. We now need to configure Loco to access the built frontend from there. 1. Open `config/development.yml` 2. Set the configs to the frontend build path: a. `server.middlewares.static.folder.path: "frontend/dist/frontend/browser"` b. `server.middlewares.static.fallback: "frontend/dist/frontend/browser/index.html"` ## Start Loco 1. Start Loco with `cargo loco start` 2. Open http://localhost:5150/ You should now see the Angular starter Website :smile: ================================================ FILE: docs-site/content/blog/axum-session.md ================================================ +++ title = "Building a Rust App with Axum Session" description = "Add sessions to your app with Axum Sessions. Configure a session provider, and set up Axum Session and Loco with simple app hooks." date = 2023-12-19T09:19:42+00:00 updated = 2023-12-19T09:19:42+00:00 draft = false template = "blog/page.html" [taxonomies] authors = ["Team Loco"] +++ To build a Rust app with [Axum session](https://crates.io/crates/axum_session), the first step is to choose your server. In this case, we'll use [loco](https://loco.rs) :) Start by creating a new project and selecting the `SaaS app` template: ```sh $ cargo install loco $ loco new ✔ ❯ App name? · myapp ? ❯ What would you like to build? › lightweight-service (minimal, only controllers and views) Rest API (with DB and user auth) ❯ SaaS app (with DB and user auth) ``` ## Creating Session Memory Store Only First, add the Axum session crate to Cargo.toml: ```toml axum_session = {version = "0.10.1", default-features = false} ``` Then, add an Axum session layer to your router. Open app.rs and add the following hook: ```rust pub struct App; #[async_trait] impl Hooks for App { fn app_name() -> &'static str { env!("CARGO_CRATE_NAME") } // Other hooks... async fn after_routes(router: AxumRouter, _ctx: &AppContext) -> Result { let session_config = axum_session::SessionConfig::default().with_table_name("sessions_table"); let session_store = axum_session::SessionStore::::new(None, session_config) .await .unwrap(); let router = router.layer(axum_session::SessionLayer::new(session_store)); Ok(router) } // Other hooks... } ``` Now, you can create your controller that uses Axum session. Use the `cargo loco generate controller` command: ```sh ❯ cargo loco generate controller mysession --api Finished dev [unoptimized + debuginfo] target(s) in 0.36s Running `target/debug/axum-session-cli generate controller mysession` added: "src/controllers/mysession.rs" injected: "src/controllers/mod.rs" injected: "src/app.rs" added: "tests/requests/mysession.rs" injected: "tests/requests/mod.rs" ``` Open the `src/controllers/mysession.rs` file created by the controller generator and replace its content with the following code: ```rust #![allow(clippy::unused_async)] use axum_session::{Session, SessionNullPool}; use loco_rs::prelude::*; pub async fn get_session(session: Session) -> Result<()> { println!("{:#?}", session); format::empty() } pub fn routes() -> Routes { Routes::new().prefix("mysession").add("/", get(get_session)) } ``` Now, you can call the `http://127.0.0.1:5150/mysession` endpoint to see the session. ## Creating Session With DB Encryption To add session DB encryption, include the Axum session crate and PostgreSQL with SQLx in Cargo.toml: ```toml axum_session = {version = "0.10.1"} sqlx = { version = "0.7.2", features = [ "macros", "postgres", "_unstable-all-types", "tls-rustls", "runtime-tokio", ] } ``` Create a `session.rs` file with the following content: The `connect_to_database` getting an `Database` configuration and returns a PgPool instance that axum session expected. ```rust use sqlx::postgres::PgPool; use loco_rs::{ config::Database, errors::Error, Result, }; async fn connect_to_database(config: &Database) -> Result { PgPool::connect(&config.uri) .await .map_err(|e| Error::Any(e.into())) } ``` Add the Axum session layer to your router in `app.rs`: ```rust use session; // This is the session.rs file pub struct App; #[async_trait] impl Hooks for App { fn app_name() -> &'static str { env!("CARGO_CRATE_NAME") } // Other hooks... async fn after_routes(router: AxumRouter, ctx: &AppContext) -> Result { let conn = session.connect_to_database(&ctx.config.database).await?; let session_config = axum_session::SessionConfig::default() .with_table_name("sessions_table") .with_key(axum_session::Key::generate()) .with_database_key(axum_session::Key::generate()) .with_security_mode(axum_session::SecurityMode::PerSession); let session_store = axum_session::SessionStore::::new( Some(conn.clone().into()), session_config, ) .await .unwrap(); let router = router.layer(axum_session::SessionLayer::new(session_store)); Ok(router) } // Other hooks... } ``` Create the controller as before using `cargo loco generate controller` ```sh ❯ cargo loco generate controller mysession --api Finished dev [unoptimized + debuginfo] target(s) in 0.36s Running `target/debug/axum-session-cli generate controller mysession` added: "src/controllers/mysession.rs" injected: "src/controllers/mod.rs" injected: "src/app.rs" added: "tests/requests/mysession.rs" injected: "tests/requests/mod.rs" ``` and replace the content of `src/controllers/mysession.rs` with the provided code. ```rust #![allow(clippy::unused_async)] use axum_session::{Session, SessionPgPool}; use loco_rs::prelude::*; pub async fn get_session(session: Session) -> Result<()> { println!("{:#?}", session); format::empty() } pub fn routes() -> Routes { Routes::new().prefix("mysession").add("/", get(get_session)) } ``` Now, calling the `http://127.0.0.1:5150/mysession` endpoint will display the session. ================================================ FILE: docs-site/content/blog/deploy-aws.md ================================================ +++ title = "Deploying Rust App with Terraform on AWS Fargate" description = "Learn how to deploy a Loco app with Terraform (IaC). Generate a deployment with Loco generators and set it up step-by-step." date = 2023-12-20T16:04:40+00:00 updated = 2023-12-16T04:20:40+00:00 draft = false template = "blog/page.html" [taxonomies] authors = ["Antonio Souza"] +++ In today's rapidly evolving technological landscape, Infrastructure as Code (IaC) has become a cornerstone for efficient, scalable, and maintainable cloud infrastructure deployment. IaC involves managing and provisioning computing infrastructure through machine-readable script files, rather than through physical hardware configuration or interactive configuration tools. This allows for the automation of infrastructure deployment and management, which in turn reduces the risk of human error and increases the speed of deployment. In this article, we will explore how to deploy a Rust app built with [loco](https://loco.rs) on AWS Fargate using Terraform. We will start by creating a new project and selecting the `Rest API` template: ````sh ```sh $ cargo install loco $ loco new ✔ ❯ App name? · myapp ? ❯ What would you like to build? › lightweight-service (minimal, only controllers and views) ❯ Rest API (with DB and user auth) SaaS app (with DB and user auth) ```` ## Prerequisites To deploy our app on AWS Fargate, we will need to have the following tools installed: - [Docker](https://docs.docker.com/get-docker/) - Docker is a containerization platform that allows you to package your application and all of its dependencies into a standardized unit for software development. - [Terraform](https://learn.hashicorp.com/tutorials/terraform/install-cli) - Terraform is an open-source infrastructure as code software tool that enables you to safely and predictably create, change, and improve infrastructure. - [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) - The AWS Command Line Interface (CLI) is a unified tool to manage your AWS services. ## Creating the Docker Image To create the Docker image for our app, we will use the loco CLI. The `cargo loco generate deployment` command will create a Docker image for our app. It will also create a `Dockerfile` for us, which we can use to build the image. ```sh $ cargo loco generate deployment ? ❯ Choose your deployment › ❯ Docker added: "Dockerfile" added: ".dockerignore" ``` Now, we can build the Docker image which will be used to deploy our app on AWS Fargate. ```sh $ docker build -t myapp . [+] Building 237.1s (16/16) FINISHED docker:desktop-linux => [internal] load build definition from Dockerfile 0.0s => => transferring Dockerfile: 331B 0.0s ... => => writing image sha256:07416ca8195e4026ab65bc567f990ea83141aa10890f8443deb8f54a8bae7f0a 0.0s => => naming to docker.io/library/myapp ``` ## Setting up AWS To deploy our app on AWS Fargate, we will need to create an AWS account and set up the AWS CLI. You can create an AWS account [here](https://portal.aws.amazon.com/billing/signup#/start/email). You will also need to install the AWS CLI. You can find instructions on how to do this [here](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html). Finally, you need to create an IAM user to use with the AWS CLI. You can find instructions on how to do this [here](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_create.html). Now, we can configure the AWS CLI with the credentials of the IAM user we just created. ```sh $ aws configure AWS Access Key ID [None]: AWS Secret Access Key [None]: Default region name [None]: Default output format [None]: json ``` ## Creating the repository on ECR To deploy our app on AWS Fargate, we will need to create a repository on ECR. You can do this by running the following command: ```sh $ aws ecr create-repository --repository-name myapp { "repository": { "repositoryArn": "arn:aws:ecr:us-east-1:123456789012:repository/myapp", "registryId": "123456789012", "repositoryName": "myapp", "repositoryUri": "123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp", "createdAt": 1627981234.0, "imageTagMutability": "MUTABLE", "imageScanningConfiguration": { "scanOnPush": false } } } ``` ## Pushing the Docker image to ECR Now, we can push the Docker image to ECR. You can do this by running the following commands: -1. Log in to ECR ```sh $ aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com ``` -2. Tag the Docker image ```sh $ docker tag myapp:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest ``` -3. Push the Docker image to ECR ```sh $ docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/myapp:latest ``` ## Creating the main.tf file for Terraform This is the main Terraform file that will be used to deploy our app on AWS Fargate. It will create the following resources: ```hcl terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 4.0" } archive = { source = "hashicorp/archive" version = "~> 2.2.0" } } required_version = "~> 1.0" } # Configure the AWS Provider provider "aws" { region = "us-east-1" // Change this to your region access_key = "" // Change this to your access key secret_key = "your secret key" // Change this to your secret key } resource "aws_ecr_repository" "myapp" { name = "myapp" } resource "aws_ecs_cluster" "myapp_cluster" { name = "myapp_cluster" } resource "aws_cloudwatch_log_group" "myapp" { name = "/ecs/myapp" } resource "aws_ecs_task_definition" "myapp_task" { family = "myapp-task" container_definitions = < AppRoutes { AppRoutes::with_default_routes() .prefix("/api") .add_route(controllers::notes::routes()) } ``` Build the frontend for production: ```sh pnpm build ``` In the `frontend` folder, a `dist` directory is created. Update the `config/development.yaml` file in the main folder to include a static middleware: ```yaml server: middlewares: static: enable: true must_exist: true folder: uri: "/" path: "frontend/dist" fallback: "frontend/dist/index.html" ``` Now, run the Loco server again and you should see frontend app serving via Loco ```sh $ cargo loco start ``` If you see the default fallback page, you have to disable the fallback middleware. The default fallback takes priority over the static handler, so no static content will be served if it is enabled. You can disable it like so: ```yaml server: middlewares: fallback: enable: false static: ... ``` # Developing the UI Install `react-router-dom`, `react-query` and `axios` ```sh $ pnpm install react-router-dom react-query axios ``` 1. Copy [main.jsx](https://github.com/loco-rs/todo-list-example/blob/main/frontend/src/main.jsx) to frontend/src/main.jsx. 2. Copy [App.jsx](https://github.com/loco-rs/todo-list-example/blob/main/frontend/src/App.jsx) to frontend/src/App.jsx. 3. Copy [App.css](https://github.com/loco-rs/todo-list-example/blob/main/frontend/src/App.css) to frontend/src/App.css. Now, run the server `cargo loco start` and the UI pnpm dev in the frontend folder, and start adding your todo list! ## Improve Development use [cargo-watch](https://crates.io/crates/cargo-watch) for hot reloading the server: ```sh $ cargo watch --ignore "frontend" -x check -s 'cargo run start' ``` Now, any changes in your Rust code will automatically reload the server, and any changes in your frontend Vite will reload the frontend app. ## Deploy To Production In the `frontend` folder, run `pnpm build`. After a successful build, go to the Loco server and run `cargo loco start`. Loco will serve the frontend static files directly from the server. ### Prepare Docker Image Run `cargo loco generate deployment` and select Docker as the deployment type: ```sh $ cargo loco generate deployment ✔ ❯ Choose your deployment · Docker added: "Dockerfile" added: ".dockerignore" ``` Loco will add a `Dockerfile` and a `.dockerignore `file. Note that Loco detect the static assent and included them as part of the image Build the container: ```sh $ docker build . -t loco-todo-list ``` Now run the container: ```sh $ docker run -e LOCO_ENV=production -p 5150:5150 loco-todo-list start ``` ================================================ FILE: docs-site/content/blog/hello-world.md ================================================ +++ title = "What if Rails was Built on Rust?" description = "Introducing Loco: a Rails-inspired Rust web framework. See how Rust can be as expressive as Ruby and how we can build a good deal of magic that Rails has with Rust." date = 2023-11-24T09:19:42+00:00 updated = 2023-11-24T09:19:42+00:00 draft = false template = "blog/page.html" [taxonomies] authors = ["Team Loco"] +++
**What if [Rails](https://rubyonrails.org) was built on Rust and not Ruby?**
Then it would look like this: ```rust async fn current( auth: middleware::auth::Auth, State(ctx): State, ) -> Result { let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; format::json(CurrentResponse::new(&user)) } pub fn routes() -> Routes { Routes::new().prefix("user").add("/current", get(current)) } ``` ## Introducing: Loco Loco is a Rails inspired web framework for Rust. It inlcudes _almost every Rails feature_ with best-effort Rust ergonomics: * Controllers and routing via [axum](https://github.com/tokio-rs/axum) * Models, migration, and ActiveRecord via [SeaORM](https://www.sea-ql.org/SeaORM/) * Views via [serde](https://serde.rs/json.html) * Seamless, Background jobs, multi modal: in process, out of process, async via Tokio * Mailers * Tasks * Seeding * Environment-aware configuration * Tracing, logging, seamlessly integrated via [tracing](https://docs.rs/tracing) * Generators via [rrgen](https://github.com/jondot/rrgen) * Batteries-included authentication (like Rails' `devise`) * Testing kit, with automatic truncation, fixture seeding, auto migration, snapshotting and redaction It's full stack for real. ## Why not Rails? If you're happy with Ruby, use Rails. Don't spend time looking elsewhere because of performance -- Rails and Ruby are good enough. **But if you love Rust**, you can now build companies like Rubyists have been building for ages -- use Loco. * You'll get **Rust's safety, strong typing, fantastic concurrency models, and super super stable libraries and ecosystem**. Build once, then forget about it. * Deployment is copying a **single binary** over to a server. * You'll be getting **an order of 100,000 requests/sec** without any effort. And 50k requests/sec with database calls. You will never need more than a couple servers. Heck, you can deploy on a Rasberry Pi and be happy.. ## The One Person Framework Inspired by [DHH's approach](https://world.hey.com/dhh/the-one-person-framework-711e6318), Loco's guiding principle is above all: > The one person framework From this single guiding principles comes everything else. For example, one person team, or one person company: * Has **no time to debate libraries**, tooling, linting rules: strong opinions are welcome. Tell me how I should work. * **Needs a driving tool** in addition to their brainpower -- that's the Loco CLI. Generate code, operate your project. * **Needs stability**, anything that breaks is a waste of time, any surprise is a waste of time * **Needs simplicity** -- don't surprise me * **Needs a single operability story**. Deploys should be simple. No Kubernetes, no IAC, no preconditions. * **Needs control**. Send emails and author the emails locally, not on some remote service * **Needs locality**. Everything that happens in production should first happen in development and locally * **Needs ad-hocness**. No holy grail ceremonies. Build tasks to run birthday emails to your users, rather than go on a crusade for an "Admin" project. Loco is the one person framework for **indy hackers, hobbyists, and startups**. With around **20mb of a deploy binary, and 50k requests/sec** - all you need is a single small/medium server, Postgres or Sqlite and an internet connection. Startups should be cheap! Get started with [Loco](https://loco.rs) today! ================================================ FILE: docs-site/content/casts/001-dynamic-responses-and-content-types.md ================================================ +++ title = "Dynamic responses and content types" description = "Learn how to respond to incoming requests with the appropriate content type. Match on the incoming format, and render JSON, HTML or other types of responses." date = 2024-06-10T09:19:42+00:00 updated = 2024-06-10T09:19:42+00:00 draft = false template = "casts/page.html" [taxonomies] authors = ["Team Loco"] [extra] num = "001" id = "l_hxXsHHSSU" +++ Reference material for this episode: * Loco.rs docs: [sending responses](https://loco.rs/docs/the-app/controller/#sending-responses) * Rails [responders](https://api.rubyonrails.org/v4.1/classes/ActionController/Responder.html) ================================================ FILE: docs-site/content/casts/002-routes-and-prefixes.md ================================================ +++ title = "Routes and prefixes" description = "Routes in Loco are derived from how Axum does Routes. Learn how to shape your API and draw your routes, from an individual controller to your global app route set up." date = 2024-06-13T09:19:42+00:00 updated = 2024-06-13T09:19:42+00:00 draft = false template = "casts/page.html" [taxonomies] authors = ["Team Loco"] [extra] num = "002" id = "IGPE0_ptaHY" +++ Reference material for this episode: * Loco.rs docs: [routes in controllers](https://loco.rs/docs/the-app/controller/#routes-in-controllers) * The [SaaS starter](https://loco.rs/docs/starters/saas/) ================================================ FILE: docs-site/content/casts/003-scaffolding-crud-with-html.md ================================================ +++ title = "Scaffolding full CRUD with HTML views" description = "Loco generators are very powerful. Generate a full CRUD app with a single command, by including the main entity type and its set of fields. Loco will generate models, controllers, views, and migrations for you." date = 2024-06-14T09:19:42+00:00 updated = 2024-06-14T09:19:42+00:00 draft = false template = "casts/page.html" [taxonomies] authors = ["Team Loco"] [extra] num = "003" id = "EircfwF8c0E" +++ Reference material for this episode: * Loco.rs docs: [routes in controllers](https://loco.rs/docs/the-app/controller/#routes-in-controllers) * The [SaaS starter](https://loco.rs/docs/starters/saas/) * The [REST API starter](https://loco.rs/docs/starters/rest-api/) ================================================ FILE: docs-site/content/casts/004-creating-tasks.md ================================================ +++ title = "Creating tasks" description = "Ever reached out to write a small script to reset a user password? send email notifications to your users? You can use Tasks in Loco to write these operational bits in pure Rust and access your full app from your task." date = 2024-06-18T09:19:42+00:00 updated = 2024-06-18T09:19:42+00:00 draft = false template = "casts/page.html" [taxonomies] authors = ["Team Loco"] [extra] num = "004" id = "gn7Hkq7T9dI" +++ Reference material for this episode: * Loco.rs docs: [routes in controllers](https://loco.rs/docs/the-app/task/) * The [SaaS starter](https://loco.rs/docs/starters/saas/) * The [REST API starter](https://loco.rs/docs/starters/rest-api/) * The [Lightweight starter](https://loco.rs/docs/starters/service/) ================================================ FILE: docs-site/content/casts/005-testing-tasks.md ================================================ +++ title = "Testing tasks" description = "See how tasks in Loco are a simple linear workflow with access to your full app context, and how to easily test them. You can write a linear business workflow and test it giving it input and asserting its output." date = 2024-06-18T09:20:42+00:00 updated = 2024-06-18T09:20:42+00:00 draft = false template = "casts/page.html" [taxonomies] authors = ["Team Loco"] [extra] num = "005" id = "485JlLA-T6U" +++ Reference material for this episode: * Loco.rs docs: [routes in controllers](https://loco.rs/docs/processing/task/) * The [SaaS starter](https://loco.rs/docs/getting-started/starters/#saas-starter) * The [REST API starter](https://loco.rs/docs/getting-started/starters/#rest-api-starter) * The [Lightweight starter](https://loco.rs/docs/getting-started/starters/#lightweight-service-starter) ================================================ FILE: docs-site/content/casts/006-mailers.md ================================================ +++ title = "Mailers" description = "Learn how to send emails from your app. As it turns out, emails are still an important core feature in business apps. You can send emails from multiple types of providers, and enjoy a great developer experience." date = 2024-06-27T09:20:42+00:00 updated = 2024-06-27T09:20:42+00:00 draft = false template = "casts/page.html" [taxonomies] authors = ["Team Loco"] [extra] num = "006" id = "ieGeihxLGC8" +++ Reference material for this episode: * Loco.rs docs: [Mailers](https://loco.rs/docs/processing/mailers/) ================================================ FILE: docs-site/content/casts/007-htmx.md ================================================ +++ title = "Full CRUD with HTMX scaffold generator" description = "Learn how to use HTMX with Loco. Using Loco's core generator scaffolding abilities, we added support for generating a set of HTMX powered views, which makes it a joy to build a fullstack UI app." date = 2024-06-27T14:20:42+00:00 updated = 2024-06-27T14:20:42+00:00 draft = false template = "casts/page.html" [taxonomies] authors = ["Team Loco"] [extra] num = "007" id = "OWUvUSC1KvY" +++ Reference material for this episode: * Loco.rs docs: [Views](https://loco.rs/docs/the-app/views/) * HTMX [website](https://htmx.org/) ================================================ FILE: docs-site/content/casts/_index.md ================================================ +++ title = "Loco Casts" description = "Loco Casts" sort_by = "date" paginate_by = 10 template = "casts/section.html" +++ ================================================ FILE: docs-site/content/docs/_index.md ================================================ +++ title = "Docs" description = "Docs for loco" sort_by = "weight" weight = 1 template = "docs/section.html" +++ ================================================ FILE: docs-site/content/docs/extras/_index.md ================================================ +++ title = "Extras" description = "" template = "docs/section.html" sort_by = "weight" weight = 5 draft = false +++ ================================================ FILE: docs-site/content/docs/extras/authentication.md ================================================ +++ title = "Authentication" description = "" date = 2021-05-01T18:20:00+00:00 updated = 2021-05-01T18:20:00+00:00 draft = false weight = 1 sort_by = "weight" template = "docs/page.html" [extra] lead = "" toc = true top = false flair =[] +++ ## User Password Authentication `Loco` simplifies the user authentication process, allowing you to set up a new website quickly. This feature not only saves time but also provides the flexibility to focus on crafting the core logic of your application. ### Authentication Configuration The `auth` feature comes as a default with the library. If desired, you can turn it off and handle authentication manually. ### Getting Started with a SaaS App Create your app using the [loco cli](/docs/getting-started/tour) and select the `SaaS app (with DB and user auth)` option. To explore the out-of-the-box auth controllers, run the following command: ```sh $ cargo loco routes . . . [POST] /api/auth/forgot [POST] /api/auth/login [POST] /api/auth/register [POST] /api/auth/reset [GET] /api/auth/verify [GET] /api/auth/current . . . ``` ### Registering a New User The `/api/auth/register` endpoint creates a new user in the database with an `email_verification_token` for account verification. A welcome email is sent to the user with a verification link. ##### Example Curl Request: ```sh curl --location '127.0.0.1:5150/api/auth/register' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "Loco user", "email": "user@loco.rs", "password": "12341234" }' ``` For security reasons, if the user is already registered, no new user is created, and a 200 status is returned without exposing user email details. ### Login After registering a new user, use the following request to log in: ##### Example Curl Request: ```sh curl --location '127.0.0.1:5150/api/auth/login' \ --header 'Content-Type: application/json' \ --data-raw '{ "email": "user@loco.rs", "password": "12341234" }' ``` The response includes a JWT token for authentication, user ID, name, and verification status. ```sh { "token": "...", "pid": "2b20f998-b11e-4aeb-96d7-beca7671abda", "name": "Loco user", "is_verified": false } ``` - **Token**: A JWT token enabling requests to authentication endpoints. Refer to the [configuration documentation](@/docs/the-app/your-project.md#your-app-configuration) to customize the default token expiration and ensure that the secret differs between environments. - **pid** - A unique identifier generated when creating a new user. - **Name** - The user's name associated with the account. - **Is Verified** - A flag indicating whether the user has verified their account. ### Account Verification Upon user registration, an email with a verification link is sent. Visiting this link updates the `email_verified_at` field in the database, changing the `is_verified` flag in the login response to true. #### Example Curl request: ```sh curl --location --request GET '127.0.0.1:5150/api/auth/verify/TOKEN' \ --header 'Content-Type: application/json' ``` ### Reset Password Flow #### Forgot Password The `forgot` endpoint requires only the user's email in the payload. An email is sent with a reset password link, and a `reset_token` is set in the database. ##### Example Curl request: ```sh curl --location '127.0.0.1:5150/api/auth/forgot' \ --header 'Content-Type: application/json' \ --data-raw '{ "email": "user@loco.rs" }' ``` #### Reset Password To reset the password, send the token generated in the `forgot` endpoint along with the new password. ##### Example Curl request: ```sh curl --location '127.0.0.1:5150/api/auth/reset' \ --header 'Content-Type: application/json' \ --data '{ "token": "TOKEN", "password": "new-password" }' ``` ### Get current user This endpoint is protected by auth middleware. ```sh curl --location --request GET '127.0.0.1:5150/api/auth/current' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer TOKEN' ``` ### Creating an Authenticated Endpoint To establish an authenticated endpoint, import `controller::extractor::auth` from the `loco_rs` library and incorporate the auth middleware into the function endpoint parameters. Consider the following example in Rust: ```rust use axum::{extract::State, Json}; use loco_rs::{ app::AppContext, controller::extractor::auth, Result, }; async fn current( auth: auth::JWT, State(ctx): State, ) -> Result { let user = users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; /// Some response } ``` ## API Authentication ### Creating new app For this time, let create your rest app using the [loco cli](/docs/getting-started/tour) and select the `Rest app` option. To create new app, run the following command and follow the instructions: ```sh $ loco new ``` To explore the out-of-the-box auth controllers, run the following command: ```sh $ cargo loco routes . . . [POST] /api/auth/forgot [POST] /api/auth/login [POST] /api/auth/register [POST] /api/auth/reset [GET] /api/auth/verify [GET] /api/auth/current . . . ``` ### Registering new user The `/api/auth/register` endpoint creates a new user in the database with an `api_key` for request authentication. `api_key` will be used for authentication in the future requests. #### Example Curl Request: ```sh curl --location '127.0.0.1:5150/api/auth/register' \ --header 'Content-Type: application/json' \ --data-raw '{ "name": "Loco user", "email": "user@loco.rs", "password": "12341234" }' ``` After registering a new user, make sure you see the `api_key` in the database for the new user. ### Creating an Authenticated Endpoint with API Authentication To set up an API-authenticated endpoint, import `controller::extractor::auth` from the loco_rs library and include the auth middleware in the function endpoint parameters using `auth::ApiToken`. Consider the following example in Rust: ```rust use loco_rs::prelude::*; use loco_rs::controller::extractor::auth; use crate::{models::_entities::users, views::user::CurrentResponse}; async fn current_by_api_key( auth: auth::ApiToken, State(_ctx): State, ) -> Result { format::json(CurrentResponse::new(&auth.user)) } pub fn routes() -> Routes { Routes::new() .prefix("user") .add("/current-api", get(current_by_api_key)) } ``` ### Requesting an API Authenticated Endpoint To request an authenticated endpoint, you need to pass the `API_KEY` in the `Authorization` header. #### Example Curl Request: ```sh curl --location '127.0.0.1:5150/api/user/current-api' \ --header 'Content-Type: application/json' \ --header 'Authorization: Bearer API_KEY' ``` If the `API_KEY` is valid, you will get the response with the user details. ================================================ FILE: docs-site/content/docs/extras/pluggability.md ================================================ +++ title = "Pluggability" description = "" date = 2021-05-01T18:10:00+00:00 updated = 2021-05-01T18:10:00+00:00 draft = false weight = 3 sort_by = "weight" template = "docs/page.html" [extra] lead = "" toc = true top = false flair =[] +++ ## Error levels and options As a reminder, error levels and their logging can be controlled in your `development.yaml`: ### Logger ```yaml # Application logging configuration logger: # Enable or disable logging. enable: true # Enable pretty backtrace (sets RUST_BACKTRACE=1) pretty_backtrace: true # Log level, options: trace, debug, info, warn or error. level: debug # Define the logging format. options: compact, pretty or json format: compact # By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries # Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters. # override_filter: trace ``` The most important knobs here are: - `level` - your standard logging levels. Typically `debug` or `trace` in development. In production, choose what you are used to. - `pretty_backtrace` - provides a clear, concise path to the line of code causing the error. Use `true` in development and turn it off in production. In cases where you are debugging things in production and need some extra hand, you can turn it on and then off when you're done. ### Controller logging In `server.middlewares` you will find: ```yaml server: middlewares: # # ... # # Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details. logger: # Enable/Disable the middleware. enable: true ``` You should enable it to get detailed request errors and a useful `request-id` that can help collate multiple request-scoped errors. ### Database You have the option of logging live SQL queries, in your `database` section: ```yaml database: # When enabled, the sql query will be logged. enable_logging: false ``` ### Operating around errors You'll be mostly looking at your terminal for errors while developing your app, it can look something like this: ```bash 2024-02-xxx DEBUG http-request: tower_http::trace::on_request: started processing request http.method=GET http.uri=/notes http.version=HTTP/1.1 http.user_agent=curl/8.1.2 environment=development request_id=8622e624-9bda-49ce-9730-876f2a8a9a46 2024-02-xxx11T12:19:25.295954Z ERROR http-request: loco_rs::controller: controller_error error.msg=invalid type: string "foo", expected a sequence error.details=JSON(Error("invalid type: string \"foo\", expected a sequence", line: 0, column: 0)) error.chain="" http.method=GET http.uri=/notes http.version=HTTP/1.1 http.user_agent=curl/8.1.2 environment=development request_id=8622e624-9bda-49ce-9730-876f2a8a9a46 ``` Usually you can expect the following from errors: - `error.msg` a `to_string()` version of an error, for operators. - `error.detail` a debug representation of an error, for developers. - An error **type** e.g. `controller_error` as the primary message tailored for searching, rather than a verbal error message. - Errors are logged as _tracing_ events and spans, so that you can build any infrastructure you want to provide custom tracing subscribers. Check out the [prometheus](https://github.com/loco-rs/loco-extras/blob/main/src/initializers/prometheus.rs) example in `loco-extras`. Notes: - An _error chain_ was experimented with, but provides little value in practice. - Errors that an end user sees are a completely different thing. We strive to provide **minimal internal details** about an error for an end user when we know a user can't do anything about an error (e.g. "database offline error"), mostly it will be a generic "Internal Server Error" on purpose -- for security reasons. ### Producing errors When you build controllers, you write your handlers to return `Result`. The `Result` here is a Loco `Result`, which means it also associates a Loco `Error` type. If you reach out for the Loco `Error` type you can use any of the following as a response: ```rust Err(Error::string("some custom message")); Err(Error::msg(other_error)); // turns other_error to its string representation Err(Error::wrap(other_error)); Err(Error::Unauthorized("some message")) // or through controller helpers: unauthorized("some message") // create a full response object, calling Err on a created error ``` ## Initializers Initializers are a way to encapsulate a piece of infrastructure "wiring" that you need to do in your app. You put initializers in `src/initializers/`. ### Writing initializers Currently, an initializer is anything that implements the `Initializer` trait: ```rust pub trait Initializer: Sync + Send { /// The initializer name or identifier fn name(&self) -> String; /// Occurs after the app's `before_run`. /// Use this to for one-time initializations, load caches, perform web /// hooks, etc. async fn before_run(&self, _app_context: &AppContext) -> Result<()> { Ok(()) } /// Occurs after the app's `after_routes`. /// Use this to compose additional functionality and wire it into an Axum /// Router async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { Ok(router) } /// Perform health checks for this initializer. /// This method is called during the doctor command to validate the initializer's configuration. /// Return `None` if no check is needed, or `Some(Check)` if a check should be performed. async fn check(&self, _app_context: &AppContext) -> Result> { Ok(None) } } ``` ### Example: Integrating Axum Session You might want to add sessions to your app using `axum-session`. Also, you might want to share that piece of functionality between your own projects, or grab that piece of code from someone else. You can achieve this reuse easily, if you code the integration as an _initializer_: ```rust // place this in `src/initializers/axum_session.rs` #[async_trait] impl Initializer for AxumSessionInitializer { fn name(&self) -> String { "axum-session".to_string() } async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { let session_config = axum_session::SessionConfig::default().with_table_name("sessions_table"); let session_store = axum_session::SessionStore::::new(None, session_config) .await .unwrap(); let router = router.layer(axum_session::SessionLayer::new(session_store)); Ok(router) } } ``` And now your app structure looks like this: ``` src/ bin/ controllers/ : : initializers/ <--- a new folder mod.rs <--- a new module axum_session.rs <--- your new initializer : : app.rs <--- register initializers here ``` ### Using initializers After you've implemented your own initializer, you should implement the `initializers(..)` hook in your `src/app.rs` and provide a Vec of your initializers: ```rust async fn initializers(_ctx: &AppContext) -> Result>> { let initializers: Vec> = vec![ Box::new(initializers::axum_session::AxumSessionInitializer), Box::new(initializers::view_engine::ViewEngineInitializer), Box::new(initializers::hello_view_engine::HelloViewEngineInitializer), ]; Ok(initializers) } ``` Loco will now run your initializer stack in the correct places during the app boot process. ### Initializer Health Checks Initializers can now provide their own health checks by implementing the `check` method. This allows each initializer to validate its configuration and test its connections during the `cargo loco doctor` command. #### Implementing Health Checks To add health checks to your initializer, implement the `check` method: ```rust use async_trait::async_trait; use loco_rs::app::{AppContext, Initializer}; use loco_rs::doctor::{Check, CheckStatus}; struct MyCustomInitializer; #[async_trait] impl Initializer for MyCustomInitializer { fn name(&self) -> String { "my_custom_initializer".to_string() } async fn check(&self, app_context: &AppContext) -> loco_rs::Result> { // Check if your configuration exists let config = app_context.config.initializers.as_ref() .and_then(|init| init.get("my_custom_initializer")) .ok_or_else(|| loco_rs::Error::Message("Configuration not found".to_string()))?; // Perform your health check match self.test_connection(config).await { Ok(()) => Ok(Some(Check { status: CheckStatus::Ok, message: "My custom service: success".to_string(), description: None, })), Err(err) => Ok(Some(Check { status: CheckStatus::NotOk, message: "My custom service: failed".to_string(), description: Some(err.to_string()), })), } } } ``` #### Health Check Return Values The `check` method returns `Result>`: - **`Ok(None)`**: No health check needed (default behavior) - **`Ok(Some(Check))`**: Health check result to be displayed #### Check Status Types ```rust pub enum CheckStatus { Ok, // ✅ Component is healthy NotOk, // ❌ Component has issues NotConfigure, // ⚠️ Component not configured (may be intentional) } ``` #### Running Initializer Health Checks Health checks are automatically run when you execute: ```sh cargo loco doctor ``` The output will include your initializer checks: ``` ✅ Database connection: success ✅ Initializer my_custom_initializer: My custom service: success ❌ Initializer failing_service: Service connection: failed connection timeout after 30 seconds ``` #### Optional Health Checks Health checks are completely optional. If your initializer doesn't implement the `check` method, it will use the default implementation that returns `Ok(None)`, meaning no health check will be performed. This makes the feature backward-compatible and allows initializers to opt-in to health checking when needed. ### What other things you can do? Right now initializers contain two integration points: - `before_run` - happens before running the app -- this is a pure "initialization" type of a hook. You can send web hooks, metric points, do cleanups, pre-flight checks, etc. - `after_routes` - happens after routes have been added. You have access to the Axum router and its powerful layering integration points, this is where you will spend most of your time. ### Compared to Rails initializers Rails initializers, are regular scripts that run once -- for initialization and have access to everything. They get their power from being able to access a "live" Rails app, modify it as a global instance. In Loco, accessing a global instance and mutating it is not possible in Rust (for a good reason!), and so we offer two integration points which are explicit and safe: 1. Pure initialization (without any influence on a configured app) 2. Integration with a running app (via Axum router) Rails initializers need _ordering_ and _modification_. Meaning, a user should be certain that they run in a specific order (or re-order them), and a user is able to remove initializers that other people set before them. In Loco, we circumvent this complexity by making the user _provide a full vec_ of initializers. Vecs are ordered, and there are no implicit initializers. ### The global logger initializer Some developers would like to customize their logging stack. In Loco this involves setting up tracing and tracing subscribers. Because at the moment tracing does not allow for re-initialization, or modification of an in-flight tracing stack, you _only get one chance to initialize and registr a global tracing stack_. This is why we added a new _App level hook_, called `init_logger`, which you can use to provide your own logging stack initialization. ```rust // in src/app.rs impl Hooks for App { // return `Ok(true)` if you took over initializing logger // otherwise, return `Ok(false)` to use the Loco logging stack. fn init_logger(_ctx: &AppContext) -> Result { Ok(false) } } ``` After you've set up your own logger, return `Ok(true)` to signal that you took over initialization. ## Middlewares `Loco` is a framework that is built on top of [`axum`](https://crates.io/crates/axum) and [`tower`](https://crates.io/crates/tower). They provide a way to add [layers](https://docs.rs/tower/latest/tower/trait.Layer.html) and [services](https://docs.rs/tower/latest/tower/trait.Service.html) as middleware to your routes and handlers. Middleware is a way to add pre- and post-processing to your requests. This can be used for logging, authentication, rate limiting, route-specific processing, and more. ### Source Code `Loco`'s implementation of route middleware/layer is similar to `axum`'s [`Router::layer`](https://github.com/tokio-rs/axum/blob/main/axum/src/routing/mod.rs#L275). You can find the source code for middleware in the [`src/controllers/routes`](https://github.com/loco-rs/loco/blob/master/src/controller/routes.rs) directory. This `layer` function will attach the middleware layer to each handler of the route. ```rust // src/controller/routes.rs use axum::{extract::Request, response::IntoResponse, routing::Route}; use tower::{Layer, Service}; impl Routes { pub fn layer(self, layer: L) -> Self where L: Layer + Clone + Send + 'static, L::Service: Service + Clone + Send + 'static, >::Response: IntoResponse + 'static, >::Error: Into + 'static, >::Future: Send + 'static, { Self { prefix: self.prefix, handlers: self .handlers .iter() .map(|handler| Handler { uri: handler.uri.clone(), actions: handler.actions.clone(), method: handler.method.clone().layer(layer.clone()), }) .collect(), } } } ``` ### Basic Middleware In this example, we will create a basic middleware that will log the request method and path. ```rust // src/controllers/middleware/log.rs use std::{ convert::Infallible, task::{Context, Poll}, }; use axum::{ body::Body, extract::{FromRequestParts, Request}, response::Response, }; use futures_util::future::BoxFuture; use loco_rs::prelude::{auth::JWTWithUser, *}; use tower::{Layer, Service}; use crate::models::{users}; #[derive(Clone)] pub struct LogLayer; impl LogLayer { pub fn new() -> Self { Self {} } } impl Layer for LogLayer { type Service = LogService; fn layer(&self, inner: S) -> Self::Service { Self::Service { inner, } } } #[derive(Clone)] pub struct LogService { // S is the inner service, in the case, it is the `/auth/register` handler inner: S, } /// Implement the Service trait for LogService /// # Generics /// * `S` - The inner service, in this case is the `/auth/register` handler /// * `B` - The body type impl Service> for LogService where S: Service, Response=Response, Error=Infallible> + Clone + Send + 'static, /* Inner Service must return Response and never error, which is typical for handlers */ S::Future: Send + 'static, B: Send + 'static, { // Response type is the same as the inner service / handler type Response = S::Response; // Error type is the same as the inner service / handler type Error = S::Error; // Future type is the same as the inner service / handler type Future = BoxFuture<'static, Result>; // poll_ready is used to check if the service is ready to process a request fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { // Our middleware doesn't care about backpressure, so it's ready as long // as the inner service is ready. self.inner.poll_ready(cx) } fn call(&mut self, req: Request) -> Self::Future { let clone = self.inner.clone(); // take the service that was ready let mut inner = std::mem::replace(&mut self.inner, clone); Box::pin(async move { let (mut parts, body) = req.into_parts(); tracing::info!("Request: {:?} {:?}", parts.method, parts.uri.path()); let req = Request::from_parts(parts, body); inner.call(req).await }) } } ``` At the first glance, this middleware is a bit overwhelming. Let's break it down. The `LogLayer` is a [`tower::Layer`](https://docs.rs/tower/latest/tower/trait.Layer.html) that wraps around the inner service. The `LogService` is a [`tower::Service`](https://docs.rs/tower/latest/tower/trait.Service.html) that implements the `Service` trait for the request. ### Generics Explanation **`Layer`** In the `Layer` trait, `S` represents the inner service, which in this case is the `/auth/register` handler. The `layer` function takes this inner service and returns a new service that wraps around it. **`Service`** `S` is the inner service, in this case, it is the `/auth/register` handler. If we have a look about the [`get`](https://docs.rs/axum/latest/axum/routing/method_routing/fn.get.html), [`post`](https://docs.rs/axum/latest/axum/routing/method_routing/fn.post.html), [`put`](https://docs.rs/axum/latest/axum/routing/method_routing/fn.put.html), [`delete`](https://docs.rs/axum/latest/axum/routing/method_routing/fn.delete.html) functions which we use for handlers, they all return a [`MethodRoute`(Which is a service)](https://docs.rs/axum/latest/axum/routing/method_routing/struct.MethodRouter.html). Therefore, `S: Service, Response = Response, Error = Infallible>` means it takes in a `Request`( Request with a body) and returns a `Response`. The `Error` is `Infallible` which means the handler never errors. `S::Future: Send + 'static` means the future of the inner service must implement `Send` trait and `'static`. `type Response = S::Response` means the response type of the middleware is the same as the inner service. `type Error = S::Error` means the error type of the middleware is the same as the inner service. `type Future = BoxFuture<'static, Result>` means the future type of the middleware is the same as the inner service. `B: Send + 'static` means the request body type must implement the `Send` trait and `'static`. ### Function Explanation **`LogLayer`** The `LogLayer::new` function is used to create a new instance of the `LogLayer`. **`LogService`** The `LogService::poll_ready` function is used to check if the service is ready to process a request. It can be used for backpressure, for more information see the [`tower::Service` documentation](https://docs.rs/tower/latest/tower/trait.Service.html) and [Tokio tutorial](https://tokio.rs/blog/2021-05-14-inventing-the-service-trait#backpressure). The `LogService::call` function is used to process the request. In this case, we are logging the request method and path. Then we are calling the inner service with the request. **Importance of `poll_ready`:** In the Tower framework, before a service can be used to handle a request, it must be checked for readiness using the `poll_ready` method. This method returns `Poll::Ready(Ok(()))` when the service is ready to process a request. If a service is not ready, it may return `Poll::Pending`, indicating that the caller should wait before sending a request. This mechanism ensures that the service has the necessary resources or state to process the request efficiently and correctly. **Cloning and Readiness** When cloning a service, particularly to move it into a boxed future or similar context, it's crucial to understand that the clone does not inherit the readiness state of the original service. Each clone of a service maintains its own state. This means that even if the original service was ready `(Poll::Ready(Ok(())))`, the cloned service might not be in the same state immediately after cloning. This can lead to issues where a cloned service is used before it is ready, potentially causing panics or other failures. **Correct approach to cloning services using `std::mem::replace`** To handle cloning correctly, it's recommended to use `std::mem::replace` to swap the ready service with its clone in a controlled manner. This approach ensures that the service being used to handle the request is the one that has been verified as ready. Here's how it works: - Clone the service: First, create a clone of the service. This clone will eventually replace the original service in the service handler. - Replace the original with the clone: Use `std::mem::replace` to swap the original service with the clone. This operation ensures that the service handler continues to hold a service instance. - Use the original service to handle the request: Since the original service was already checked for readiness (via `poll_ready`), it's safe to use it to handle the incoming request. The clone, now in the handler, will be the one checked for readiness next time. This method ensures that each service instance used to handle requests is always the one that has been explicitly checked for readiness, thus maintaining the integrity and reliability of the service handling process. Here is a simplified example to illustrate this pattern: ```rust // Wrong fn call(&mut self, req: Request) -> Self::Future { let mut inner = self.inner.clone(); Box::pin(async move { /* ... */ inner.call(req).await }) } // Correct fn call(&mut self, req: Request) -> Self::Future { let clone = self.inner.clone(); // take the service that was ready let mut inner = std::mem::replace(&mut self.inner, clone); Box::pin(async move { /* ... */ inner.call(req).await }) } ``` In this example, `inner` is the service that was ready, and after handling the request, `self.inner` now holds the clone, which will be checked for readiness in the next cycle. This careful management of service readiness and cloning is essential for maintaining robust and error-free service operations in asynchronous Rust applications using Tower. [Tower Service Cloning Documentation](https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services) ### Adding Middleware to Handler Add the middleware to the `auth::register` handler. ```rust // src/controllers/auth.rs pub fn routes() -> Routes { Routes::new() .prefix("auth") .add("/register", post(register).layer(middlewares::log::LogLayer::new())) } ``` Now when you make a request to the `auth::register` handler, you will see the request method and path logged. ```shell 2024-XX-XXTXX:XX:XX.XXXXXZ INFO http-request: xx::controllers::middleware::log Request: POST "/auth/register" http.method=POST http.uri=/auth/register http.version=HTTP/1.1 environment=development request_id=xxxxx ``` ## Adding Middleware to Route Add the middleware to the `auth` route. ```rust // src/main.rs pub struct App; #[async_trait] impl Hooks for App { fn routes(_ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() .add_route( controllers::auth::routes() .layer(middlewares::log::LogLayer::new()), ) } } ``` Now when you make a request to any handler in the `auth` route, you will see the request method and path logged. ```shell 2024-XX-XXTXX:XX:XX.XXXXXZ INFO http-request: xx::controllers::middleware::log Request: POST "/auth/register" http.method=POST http.uri=/auth/register http.version=HTTP/1.1 environment=development request_id=xxxxx ``` ### Advanced Middleware (With AppContext) There will be times when you need to access the `AppContext` in your middleware. For example, you might want to access the database connection to perform some authorization checks. To do this, you can add the `AppContext` to the `Layer` and `Service`. Here we will create a middleware that checks the JWT token and gets the user from the database then prints the user's name ```rust // src/controllers/middleware/log.rs use std::{ convert::Infallible, task::{Context, Poll}, }; use axum::{ body::Body, extract::{FromRequestParts, Request}, response::Response, }; use futures_util::future::BoxFuture; use loco_rs::prelude::{auth::JWTWithUser, *}; use tower::{Layer, Service}; use crate::models::{users}; #[derive(Clone)] pub struct LogLayer { state: AppContext, } impl LogLayer { pub fn new(state: AppContext) -> Self { Self { state } } } impl Layer for LogLayer { type Service = LogService; fn layer(&self, inner: S) -> Self::Service { Self::Service { inner, state: self.state.clone(), } } } #[derive(Clone)] pub struct LogService { inner: S, state: AppContext, } impl Service> for LogService where S: Service, Response=Response, Error=Infallible> + Clone + Send + 'static, /* Inner Service must return Response and never error */ S::Future: Send + 'static, B: Send + 'static, { // Response type is the same as the inner service / handler type Response = S::Response; // Error type is the same as the inner service / handler type Error = S::Error; // Future type is the same as the inner service / handler type Future = BoxFuture<'static, Result>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.inner.poll_ready(cx) } fn call(&mut self, req: Request) -> Self::Future { let state = self.state.clone(); let clone = self.inner.clone(); // take the service that was ready let mut inner = std::mem::replace(&mut self.inner, clone); Box::pin(async move { // Example of extracting JWT token from the request let (mut parts, body) = req.into_parts(); let auth = JWTWithUser::::from_request_parts(&mut parts, &state).await; match auth { Ok(auth) => { // Example of getting user from the database let user = users::Model::find_by_email(&state.db, &auth.user.email).await.unwrap(); tracing::info!("User: {}", user.name); let req = Request::from_parts(parts, body); inner.call(req).await } Err(_) => { // Handle error, e.g., return an unauthorized response Ok(Response::builder() .status(401) .body(Body::empty()) .unwrap() .into_response()) } } }) } } ``` In this example, we have added the `AppContext` to the `LogLayer` and `LogService`. We are using the `AppContext` to get the database connection and the JWT token for pre-processing. ### Adding Middleware to Route (advanced) Add the middleware to the `notes` route. ```rust // src/app.rs pub struct App; #[async_trait] impl Hooks for App { fn routes(ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() .add_route(controllers::notes::routes().layer(middlewares::log::LogLayer::new(ctx))) } } ``` Now when you make a request to any handler in the `notes` route, you will see the user's name logged. ```shell 2024-XX-XXTXX:XX:XX.XXXXXZ INFO http-request: xx::controllers::middleware::log User: John Doe environment=development request_id=xxxxx ``` ### Adding Middleware to Handler (advanced) In order to add the middleware to the handler, you need to add the `AppContext` to the `routes` function in `src/app.rs`. ```rust // src/app.rs pub struct App; #[async_trait] impl Hooks for App { fn routes(ctx: &AppContext) -> AppRoutes { AppRoutes::with_default_routes() .add_route( controllers::notes::routes(ctx) ) } } ``` Then add the middleware to the `notes::create` handler. ```rust // src/controllers/notes.rs pub fn routes(ctx: &AppContext) -> Routes { Routes::new() .prefix("notes") .add("/create", post(create).layer(middlewares::log::LogLayer::new(ctx))) } ``` Now when you make a request to the `notes::create` handler, you will see the user's name logged. ```shell 2024-XX-XXTXX:XX:XX.XXXXXZ INFO http-request: xx::controllers::middleware::log User: John Doe environment=development request_id=xxxxx ``` ## Application SharedStore Loco provides a flexible mechanism called `SharedStore` within the `AppContext` to store and share arbitrary custom data or services across your application. This feature allows you to inject your own types into the application context without modifying Loco's core structures, enhancing pluggability and customization. `AppContext.shared_store` is a type-safe, thread-safe heterogeneous storage. You can store any type that implements `'static + Send + Sync`. ### Why Use SharedStore? - **Sharing Custom Services:** Inject your own service clients (e.g., a custom API client) and access them from controllers or background workers. - **Storing Configuration:** Keep application-specific configuration objects accessible globally. - **Shared State:** Manage state needed by different parts of your application. ### How to Use SharedStore You typically insert your custom data into the `shared_store` during application startup (e.g., in `src/app.rs`) and then retrieve it within your controllers or other components. **1. Define Your Data Structures:** Create the structs for the data or services you want to share. Note whether they implement `Clone`. ```rust // In src/app.rs or a dedicated module (e.g., src/services.rs) // This service can be cloned #[derive(Clone, Debug)] pub struct MyClonableService { pub api_key: String, } // This service cannot (or should not) be cloned #[derive(Debug)] pub struct MyNonClonableService { pub api_key: String, } ``` **2. Insert into SharedStore (in `src/app.rs`):** A good place to insert your shared data is the `after_context` hook in your `App`'s `Hooks` implementation. ```rust // In src/app.rs use crate::MyClonableService; // Import your structs use crate::MyNonClonableService; pub struct App; #[async_trait] impl Hooks for App { // ... other Hooks methods (app_name, boot, etc.) ... async fn after_context(mut ctx: AppContext) -> Result { // Create instances of your services/data let clonable_service = MyClonableService { api_key: "key-cloned-12345".to_string(), }; let non_clonable_service = MyNonClonableService { api_key: "key-ref-67890".to_string(), }; // Insert them into the shared store ctx.shared_store.insert(clonable_service); ctx.shared_store.insert(non_clonable_service); Ok(ctx) } // ... rest of Hooks implementation ... } ``` **3. Retrieve from SharedStore (in Controllers):** You have two main ways to retrieve data in your controllers: - **Using the `SharedStore(var)` Extractor (for `Clone`-able types):** This is the most convenient way if your type implements `Clone`. The extractor retrieves and _clones_ the data for you. ```rust // In src/controllers/some_controller.rs use loco_rs::prelude::*; use crate::app::MyClonableService; // Or wherever it's defined #[debug_handler] pub async fn index( // Extracts and clones MyClonableService into `service` SharedStore(service): SharedStore, ) -> impl IntoResponse { tracing::info!("Using Cloned Service API Key: {}", service.api_key); format::empty() } ``` - **Using `ctx.shared_store.get_ref()` (for Non-`Clone`-able types or avoiding clones):** Use this method when your type doesn't implement `Clone` or when you want to avoid the performance cost of cloning. It gives you a reference (`RefGuard`) to the data. ```rust // In src/controllers/some_controller.rs use loco_rs::prelude::*; use crate::app::MyNonClonableService; // Or wherever it's defined #[debug_handler] pub async fn index( State(ctx): State, // Need the AppContext state ) -> Result { // Get a reference to the non-clonable service let service_ref = ctx.shared_store.get_ref::() .ok_or_else(|| { tracing::error!("MyNonClonableService not found in shared store"); Error::InternalServerError // Or a more specific error })?; // Access fields via the reference guard tracing::info!("Using Non-Cloned Service API Key: {}", service_ref.api_key); format::empty() } ``` **Summary:** - Use `SharedStore` in `AppContext` to share custom services or data. - Insert data during app setup (e.g., `after_context` in `src/app.rs`). - Use the `SharedStore(var)` extractor for convenient access to `Clone`-able types (clones the data). - Use `ctx.shared_store.get_ref::()` to get a reference to non-`Clone`-able types or to avoid cloning for performance reasons. ================================================ FILE: docs-site/content/docs/extras/upgrades.md ================================================ +++ title = "Upgrades" description = "" date = 2021-05-01T18:20:00+00:00 updated = 2021-05-01T18:20:00+00:00 draft = false weight = 4 sort_by = "weight" template = "docs/page.html" [extra] lead = "" toc = true top = false flair =[] +++ ## What to do when a new Loco version is out? - Create a clean branch in your code repo. - Update the Loco version in your main `Cargo.toml` - Consult with the [CHANGELOG](https://github.com/loco-rs/loco/blob/master/CHANGELOG.md) to find breaking changes and refactorings you should do (if any). - Run `cargo loco doctor` inside your project to verify that your app and environment is compatible with the new version As always, if anything turns wrong, [open an issue](https://github.com/loco-rs/loco/issues) and ask for help. ## Major Loco dependencies Loco is built on top of great libraries. It's wise to be mindful of their versions in new releases of Loco, and their individual changelogs. These are the major ones: - [SeaORM](https://www.sea-ql.org/SeaORM), [CHANGELOG](https://github.com/SeaQL/sea-orm/blob/master/CHANGELOG.md) - [Axum](https://github.com/tokio-rs/axum), [CHANGELOG](https://github.com/tokio-rs/axum/blob/main/axum/CHANGELOG.md) ## Upgrade from 0.15.x to 0.16.x ### Use `AppContext` instead of `Config` in `init_logger` in the `Hooks` trait PR: [#1418](https://github.com/loco-rs/loco/pull/1418) If you are supplying an implementation of `init_logger` in your `impl` of the `Hooks` trait in order to set up your own logging, you will need to make the following change: ```diff - fn init_logger(config: &config::Config, env: &Environment) -> Result { + fn init_logger(ctx: &AppContext) -> Result { ``` Any code in your `init_logger` implementation that makes use of the `config` can access it through `ctx.config`. In addition, you will also be able to access anything else in the `AppContext`, such as the new `shared_store`. The `env` parameter is also removed, as that is accessible from the `AppContext` as `ctx.environment`. ### Swap to validators builtin email validation PR: [#1359](https://github.com/loco-rs/loco/pull/1359) Swap from using the loco custom email validator, to the builtin email validator from `validator`. ```diff - #[validate(custom (function = "validation::is_valid_email"))] + #[validate(email(message = "invalid email"))] pub email: String, ``` ### Job system PR: [#1384](https://github.com/loco-rs/loco/pull/1384) PR: [#1396](https://github.com/loco-rs/loco/pull/1396) Two major changes have been made to the background job system: 1. The Redis provider is no longer Sidekiq-compatible and uses a custom implementation 2. All providers (Redis, PostgreSQL, SQLite) now support tag-based job filtering #### What Changed ##### Removing Sidekiq Compatibility The Redis background job system has been completely refactored, replacing the Sidekiq-compatible implementation with a new custom implementation. This provides greater flexibility and improved performance, but means: - Jobs pushed from older Loco versions (pre-0.16) will not be recognized or processed - The Redis data structures have changed entirely - There is no automatic migration path for existing queued jobs ##### Adding Job Filtering A new tag-based job filtering system has been added to all background worker providers: - Workers can now specify which tags they're interested in processing - Jobs can be tagged when enqueued - Workers with no tags only process untagged jobs, while tagged workers process jobs with matching tags - The same API is used across all providers #### How to Upgrade To upgrade to the new job system: 1. **Process existing jobs**: - Make sure all jobs in your queue are processed/completed before upgrading 2. **Clean up old data**: - For Redis: Flush the Redis database used for jobs (`FLUSHDB` command) - For PostgreSQL: Drop the job queue tables - For SQLite: Delete the job queue tables 3. **Update Loco**: - Update to Loco 0.16+ - Loco will automatically create new job tables with the correct schema on first run ### Generic Cache PR: [#1385](https://github.com/loco-rs/loco/pull/1385) The cache API has been refactored to support storing and retrieving any serializable type, not just strings. This is a breaking change that requires updates to your code: #### Breaking Changes: 1. **Type Parameters Required**: All cache methods now require explicit type parameters 2. **Method Signatures**: Some method signatures have changed to support generics 3. **Object Serialization**: Any type you store must implement `Serialize` and `Deserialize` from serde #### Migration Guide: **Before:** ```rust // Get a string value from cache let value = cache.get("key").await?; // Insert or get with callback let value = app_ctx.cache.get_or_insert("key", async { Ok("value".to_string()) }).await.unwrap(); // Insert or get with expiry let value = app_ctx.cache.get_or_insert_with_expiry("key", Duration::from_secs(300), async { Ok("value".to_string()) }).await.unwrap(); ``` **After:** ```rust // Get a string value from cache - specify the type let value = cache.get::("key").await?; // Direct insert with any serializable type cache.insert("key", &"value".to_string()).await?; // Insert or get with callback - specify return type let value = app_ctx.cache.get_or_insert::("key", async { Ok("value".to_string()) }).await.unwrap(); // Store complex types #[derive(Serialize, Deserialize)] struct User { name: String, age: u32, } let user = app_ctx.cache.get_or_insert_with_expiry::( "user:1", Duration::from_secs(300), async { Ok(User { name: "Alice".to_string(), age: 30 }) } ).await.unwrap(); ``` #### Implementing for Custom Types: For your custom types to work with the cache, ensure they implement `Serialize` and `Deserialize`: ```rust use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] struct MyType { // fields... } ``` ### Authentication Error Handling Authentication error handling has been improved to better distinguish between actual authorization failures and system errors: 1. **System errors now return 500**: Database errors during authentication now return Internal Server Error (500) instead of Unauthorized (401) 2. **Improved error logging**: Authentication errors are now logged with detailed messages using `tracing::error` 3. **Message changes**: Generic error messages have been updated from "other error: '{e}'" to "could not authorize" #### Migration Guide: If you have code that relies on database errors during authentication returning 401 status codes, you'll need to update your error handling. Any code expecting a 401 for database connectivity issues should now handle 500 responses as well. Client applications should be prepared to handle both 401 and 500 status codes during authentication failures, with 401 indicating authorization problems and 500 indicating system errors. ### Server side rendering We had some changes in Tera template. go to `src/initializers/view_engine.rs` and replace the `after_routes` function with: ```rust async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result { let tera_engine = if std::path::Path::new(I18N_DIR).exists() { let arc = std::sync::Arc::new( ArcLoader::builder(&I18N_DIR, unic_langid::langid!("en-US")) .shared_resources(Some(&[I18N_SHARED.into()])) .customize(|bundle| bundle.set_use_isolating(false)) .build() .map_err(|e| Error::string(&e.to_string()))?, ); info!("locales loaded"); engines::TeraView::build()?.post_process(move |tera| { tera.register_function("t", FluentLoader::new(arc.clone())); Ok(()) })? } else { engines::TeraView::build()? }; Ok(router.layer(Extension(ViewEngine::from(tera_engine)))) } ``` ## Upgrade from 0.14.x to 0.15.x ### Upgrade validator crate PR: [#1199](https://github.com/loco-rs/loco/pull/1199) Update the `validator` crate version in your `Cargo.toml`: From ``` validator = { version = "0.19" } ``` To ``` validator = { version = "0.20" } ``` ### User claims PR: [#1159](https://github.com/loco-rs/loco/pull/1159) - Flattened (De)Serialization of Custom User Claims: The `claims` field in `UserClaims` has changed from `Option` to `Map`. - Mandatory Map Value in `generate_token` function: When calling `generate_token`, the `Map` argument is now required. If you are not using custom claims, pass an empty map (`serde_json::Map::new()`). - Updated generate_token Signature: The `generate_token` function now takes `expiration` as a value instead of a reference. ### Pagination Response PR: [#1197](https://github.com/loco-rs/loco/pull/1197) The pagination response now includes the `total_items` field, providing the total number of items available. ```JSON {"results":[],"pagination":{"page":0,"page_size":0,"total_pages":0,"total_items":0}} ``` ### Explicit id in migrations PR: [#1268](https://github.com/loco-rs/loco/pull/1268) Migrations using `create_table` now require `("id", ColType::PkAuto)`, new migrations will have this field automatically added. ```diff async fn up(&self, m: &SchemaManager) -> Result<(), DbErr> { create_table(m, "movies", &[ + ("id", ColType::PkAuto), ("title", ColType::StringNull), ], &[ ("user", ""), ] ).await } ``` ## Upgrade from 0.13.x to 0.14.x ### Upgrading from Axum 0.7 to 0.8 PR: [#1130](https://github.com/loco-rs/loco/pull/1130) The upgrade to Axum 0.8 introduces a breaking change. For more details, refer to the [announcement](https://tokio.rs/blog/2025-01-01-announcing-axum-0-8-0). #### Steps to Upgrade - In your `Cargo.toml`, update the Axum version from `0.7.5` to `0.8.1`. - Replace use `axum::async_trait`; with use `async_trait::async_trait;`. For more information, see [here](https://tokio.rs/blog/2025-01-01-announcing-axum-0-8-0#async_trait-removal). - The URL parameter syntax has changed. Refer to [this section](https://tokio.rs/blog/2025-01-01-announcing-axum-0-8-0#path-parameter-syntax-changes) for the updated syntax. The new path parameter format is: The path parameter syntax has changed from `/:single` and `/*many` to `/{single}` and `/{*many}`. ### Extending the `boot` Function Hook PR: [#1143](https://github.com/loco-rs/loco/pull/1143) The `boot` hook function now accepts an additional Config parameter. The function signature has changed from: From ```rust async fn boot(mode: StartMode, environment: &Environment) -> Result { create_app::(mode, environment).await } ``` To: ```rust async fn boot(mode: StartMode, environment: &Environment, config: Config) -> Result { create_app::(mode, environment, config).await } ``` Make sure to import the `Config` type as needed. ### Upgrade validator crate PR: [#993](https://github.com/loco-rs/loco/pull/993) Update the `validator` crate version in your `Cargo.toml`: From ``` validator = { version = "0.18" } ``` To ``` validator = { version = "0.19" } ``` ### Extend truncate and seed hooks PR: [#1158](https://github.com/loco-rs/loco/pull/1158) The `truncate` and `seed` functions now receive `AppContext` instead of `DatabaseConnection` as their argument. From ```rust async fn truncate(db: &DatabaseConnection) -> Result<()> {} async fn seed(db: &DatabaseConnection, base: &Path) -> Result<()> {} ``` To ```rust async fn truncate(ctx: &AppContext) -> Result<()> {} async fn seed(_ctx: &AppContext, base: &Path) -> Result<()> {} ``` Impact on Testing: Testing code involving the seed function must also be updated accordingly. from: ```rust async fn load_page() { request::(|request, ctx| async move { seed::(&ctx.db).await.unwrap(); ... }) .await; } ``` to ```rust async fn load_page() { request::(|request, ctx| async move { seed::(&ctx).await.unwrap(); ... }) .await; } ``` ================================================ FILE: docs-site/content/docs/extras/websocket.md ================================================ +++ title = "Websocket" description = "" date = 2024-01-21T18:20:00+00:00 updated = 2024-01-21T18:20:00+00:00 draft = false weight = 2 sort_by = "weight" template = "docs/page.html" [extra] lead = "" toc = true top = false flair =[] +++ ## Chat Room Example For a simple example of a chat room implementation with [socketioxide](https://github.com/Totodore/socketioxide), refer to this [link](https://github.com/loco-rs/chat-rooms). ================================================ FILE: docs-site/content/docs/getting-started/_index.md ================================================ +++ title = "Getting Started" description = "" template = "docs/section.html" sort_by = "weight" weight = 1 draft = false +++ ================================================ FILE: docs-site/content/docs/getting-started/axum-users.md ================================================ +++ title = "Axum vs Loco" description = "Shows how to move from Axum to Loco" date = 2023-12-01T19:30:00+00:00 updated = 2023-12-01T19:30:00+00:00 draft = false weight = 5 sort_by = "weight" template = "docs/page.html" [extra] toc = true top = false flair =[] +++
NOTE: Loco is based on Axum, it is "Axum with batteries included", and is very easy to move your Axum code to Loco.
We will study [realworld-axum-sqlx](https://github.com/launchbadge/realworld-axum-sqlx) which is an Axum based app, that attempts to describe a real world project, using API, real database, and real world scenarios as well as real world operability requirements such as configuration and logging. Picking `realworld-axum-sqlx` apart piece by piece **we will show that by moving it from Axum to Loco, most of the code is already written for you**, you get better best practices, better dev experience, integrated testing, code generation, and build apps faster. **You can use this breakdown** to understand how to move your own Axum based app to Loco as well. For any questions, reach out [in discussions](https://github.com/loco-rs/loco/discussions) or join our [discord by clicking the green invite button](https://github.com/loco-rs/loco) ## `main` When working with Axum, you have to have your own `main` function which sets up every component of your app, gets your routers, adds middleware, sets context, and finally, eventually, goes and sets up a `listen` on a socket. This is a lot of manual, error prone work. In Loco you: * Toggle on/off your desired middleware in configuration * Use `cargo loco start`, no need for a `main` file at all * In production, you get a compiled binary named `your_app` which you run ### Moving to Loco * Set up your required middleware in Loco `config/` ```yaml server: middlewares: limit_payload: body_limit: 5mb # .. more middleware below .. ``` * Set your serving port in Loco `config/` ```yaml server: port: 5150 ``` ### Verdict * **No code to write**, you don't need to hand-code a main function unless you have to * **Best practices off the shelf**, you get a main file best practices uniform, shared across all your Loco apps * **Easy to change**, if you want to remove/add middleware to test things out, you can just flip a switch in configuration, no rebuild ## Env The realworld axum codebase uses [dotenv](https://github.com/launchbadge/realworld-axum-sqlx/blob/main/.env.sample), which needs explicit loading in `main`: ```rust dotenv::dotenv().ok(); ``` And a `.env` file to be available, maintained and loaded: ``` DATABASE_URL=postgresql://postgres:{password}@localhost/realworld_axum_sqlx HMAC_KEY={random-string} RUST_LOG=realworld_axum_sqlx=debug,tower_http=debug ``` This is a **sample** file which you get with the project, which you have to manually copy and edit, which is more often than not very error prone. ### Moving to Loco Loco: use your standard `config/[stage].yaml` configuration, and load specific values from environment using `get_env` ```yaml # config/development.yaml # Web server configuration server: # Port on which the server will listen. the server binding is 0.0.0.0:{PORT} port: {{/* get_env(name="NODE_PORT", default=5150) */}} ``` This configuration is strongly typed, contains most-used values like database URL, logger levels and filtering and more. No need to guess or reinvent the wheel. ### Verdict * **No coding needed**, when moving to Loco you write less code * **Less moving parts**, when using Axum only, you have to have configuration in addition to env vars, this is something you get for free with Loco ## Database Using Axum only, you typically have to set up your connection, pool, and set it up to be available for your routes, here's the code which you put in your `main.rs` typically: ```rust let db = PgPoolOptions::new() .max_connections(50) .connect(&config.database_url) .await .context("could not connect to database_url")?; ``` Then you have to hand-wire this connection ```rust .layer(AddExtensionLayer::new(ApiContext { config: Arc::new(config), db, })) ``` ### Moving to Loco In Loco you just set your values for the pool in your `config/` folder. We already pick up best effort default values so you don't have to do it, but if you want to, this is how it looks like: ```yaml database: enable_logging: false connect_timeout: 500 idle_timeout: 500 min_connections: 1 max_connections: 1 ``` ### Verdict * **No code to write** - save yourself the dangers of picking the right values for your db pool, or misconfiguring it * **Change is easy** - often you want to try different values under different loads in production, with Axum only, you have to recompile, redeploy. With Loco you can set a config and restart the process. ## Logging All around your app, you'll have to manually code a logging story. Which do you pick? `tracing` or `slog`? Is it logging or tracing? What is better? Here's what exists in the real-world-axum project. In serving: ```rust // Enables logging. Use `RUST_LOG=tower_http=debug` .layer(TraceLayer::new_for_http()), ``` And in `main`: ```rust // Initialize the logger. env_logger::init(); ``` And ad-hoc logging in various points: ```rust log::error!("SQLx error: {:?}", e); ``` ### Moving to Loco In Loco, we've already answered these hard questions and provide multi-tier logging and tracing: * Inside the framework, internally * Configured in the router * Low level DB logging and tracing * All of Loco's components such as tasks, background jobs, etc. all use the same facility And we picked `tracing` so that any and every Rust library can "stream" into your log uniformly. But we also made sure to create smart filters so you don't get bombarded with libraries you don't know, by default. You can configure your logger in `config/` ```yaml logger: enable: true pretty_backtrace: true level: debug format: compact ``` ### Verdict * **No code to write** - no set up code, no decision to make. We made the best decision for you so you can write more code for your app. * **Build faster** - you get traces for only what you want. You get error backtraces which are colorful, contextual, and with zero noise which makes it easier to debug stuff. You can change formats and levels for production. * **Change is easy** - often you want to try different values under different loads in production, with Axum only, you have to recompile, redeploy. With Loco you can set a config and restart the process. ## Routing Moving routes from Axum to Loco is actually drop-in. Loco uses the native Axum router. If you want to have facilities like route listing and information, you can use the native Loco router, which translates to an Axum router, or you can use your own Axum router. ### Moving to Loco If you want 1:1 complete copy-paste experience, just copy your Axum routes, and plug your router in Loco's `after_routes()` hook: ```rust async fn after_routes(router: AxumRouter, _ctx: &AppContext) -> Result { // use AxumRouter to mount your routes and return an AxumRouter } ``` If you want Loco to understand the metadata information about your routes (which can come in handy later), write your `routes()` function in each of your controllers in this way: ```rust // this is what people usually do using Axum only pub fn router() -> Router { Router::new() .route("/auth/register", post(create_user)) .route("/auth/login", post(login_user)) } // this is how it looks like using Loco (notice we use `Routes` and `add`) pub fn routes() -> Routes { Routes::new() .add("/auth/register", post(create_user)) .add("/auth/login", post(login_user)) } ``` ### Verdict * **A drop-in compatibility** - Loco uses Axum and keeps all of its building blocks intact so that you can just use your own existing Axum code with no efforts. * **Route metadata for free** - one gap that Axum routers has is the ability to describe the currently configured routes, which can be used for listing or automatic OpenAPI schema generation. Loco has a small metadata layer to support this. If you use `Routes` you get it for free, while all of the different signatures remain compatible with Axum router. ================================================ FILE: docs-site/content/docs/getting-started/guide.md ================================================ +++ title = "The Loco Guide" date = 2021-05-01T08:00:00+00:00 updated = 2021-05-01T08:00:00+00:00 draft = false weight = 3 sort_by = "weight" template = "docs/page.html" [extra] toc = true top = false flair =[] +++ ## Guide Assumptions This is a "long way round" tutorial. It is long and indepth on purpose, it shows you how to build things manually **and** automatically using generators, so that you learn the skills to build and also how things work. ### What's with the name? The name `Loco` comes from **loco**motive, as a tribute to Rails, and `loco` is easier to type than `locomotive` :-). Also, in some languages it means "crazy" but that was not the original intention (or, is it crazy to build a Rails on Rust? only time will tell!). ### How much Rust do I need to know? You need to be familiar with Rust to a beginner but not more than moderate-beginner level. You need to know how to build, test, and run Rust projects, have used some popular libraries such as `clap`, `regex`, `tokio`, `axum` or other web framework, nothing too fancy. There are no crazy lifetime twisters or complex / too magical, macros in Loco that you need to know how they work. ### What is Loco? Loco is strongly inspired by Rails. If you know Rails _and_ Rust, you'll feel at home. If you only know Rails and new to Rust, you'll find Loco refreshing. We do not assume you know Rails.
We think Rails is so great, that this guide is strongly inspired from the Rails guide, too
Loco is a Web or API framework for Rust. It's also a productivity suite for developers: it contains everything you need while building a hobby or your next startup. It's also strongly inspired by Rails. - **You have a variant of the MVC model**, which removes the paradox of option. You deal with building your app, not making academic decisions for what abstractions to use. - **Fat models, slim controllers**. Models should contain most of your logic and business implementation, controllers should just be a lightweight router that understands HTTP and moves parameters around. - **Command line driven** to keep your momentum and flow. Generate stuff over copying and pasting or coding from scratch. - **Every task is "infrastructure-ready"**, just plug in your code and wire it in: controllers, models, views, tasks, background jobs, mailers, and more. - **Convention over configuration**: decisions are already done for you -- the folder structure matter, configuration shape and values matter, and the way an app is wired matter to how an app operates and for you to be the most effective. ## Creating a New Loco App You can follow this guide for a step-by-step "bottom up" learning, or you can jump and go with the [tour](@/docs/getting-started/tour/index.md) instead for a quicker "top down" intro. ### Installing ```sh cargo install loco cargo install sea-orm-cli # Only when DB is needed ``` ### Creating a new Loco app Now you can create your new app (choose "SaaS app" for built-in authentication). ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · Saas App with client side rendering ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) 🚂 Loco app generated successfully in: myapp/ - assets: You've selected `clientside` for your asset serving configuration. Next step, build your frontend: $ cd frontend/ $ npm install && npm run build ``` Here's a rundown of what Loco creates for you by default: | File/Folder | Purpose | | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `src/` | Contains controllers, models, views, tasks and more | | `app.rs` | Main component registration point. Wire the important bits here. | | `lib.rs` | Various rust-specific exports of your components. | | `bin/` | Has your `main.rs` file, you don't need to worry about it | | `controllers/` | Contains controllers, all controllers are exported via `mod.rs` | | `models/` | Contains models, `models/_entities` contains auto-generated SeaORM models, and `models/*.rs` contains your model extension logic, which are exported via `mod.rs` | | `views/` | Contains JSON-based views. Structs which can `serde` and output as JSON through the API. | | `workers/` | Has your background workers. | | `mailers/` | Mailer logic and templates, for sending emails. | | `fixtures/` | Contains data and automatic fixture loading logic. | | `tasks/` | Contains your day to day business-oriented tasks such as sending emails, producing business reports, db maintenance, etc. | | `tests/` | Your app-wide tests: models, requests, etc. | | `config/` | A stage-based configuration folder: development, test, production | ## Hello, Loco! Let's get some responses quickly. For this, we need to start up the server. You can now switch to `myapp`: ```sh $ cd myapp ``` ### Starting the server ```sh cargo loco start ``` And now, let's see that it's alive: ```sh $ curl localhost:5150/_ping {"ok":true} ``` The built in `_ping` route will tell your load balancer everything is up. Let's see that all services that are required are up: ```sh $ curl localhost:5150/_health {"ok":true} ```
The built in _health route will tell you that you have configured your app properly: it can establish a connection to your Database and Redis instances successfully.
### Say "Hello", Loco Let's add a quick _hello_ response to our service. ```sh $ cargo loco generate controller guide --api added: "src/controllers/guide.rs" injected: "src/controllers/mod.rs" injected: "src/app.rs" added: "tests/requests/guide.rs" injected: "tests/requests/mod.rs" ``` This is the generated controller body: ```rust #![allow(clippy::missing_errors_doc)] #![allow(clippy::unnecessary_struct_initialization)] #![allow(clippy::unused_async)] use loco_rs::prelude::*; #[debug_handler] pub async fn index(State(_ctx): State) -> Result { format::empty() } pub fn routes() -> Routes { Routes::new() .prefix("api/guides/") .add("/", get(index)) } ``` Change the `index` handler body: ```rust // replace format::empty() // with this format::text("hello") ``` Start the server: ```sh cargo loco start ``` Now, let's test it out: ```sh $ curl localhost:5150/api/guides hello ``` Loco has powerful generators, which will make you 10x productive and drive your momentum when building apps. If you'd like to be entertained for a moment, let's "learn the hard way" and add a new controller manually as well. Add a file called `home.rs`, and line `pub mod home;` in `mod.rs`: ``` src/ controllers/ auth.rs home.rs <--- add this file users.rs mod.rs <--- 'pub mod home;' the module here ``` Next, set up a _hello_ route, this is the contents of `home.rs`: ```rust // src/controllers/home.rs use loco_rs::prelude::*; // _ctx contains your database connection, as well as other app resource that you'll need async fn hello(State(_ctx): State) -> Result { format::text("ola, mundo") } pub fn routes() -> Routes { Routes::new().prefix("home").add("/hello", get(hello)) } ``` Finally, register this new controller routes in `app.rs`: ```rust src/ controllers/ models/ .. app.rs <---- look here ``` Add the following in `routes()`: ```rust // in src/app.rs #[async_trait] impl Hooks for App { .. fn routes() -> AppRoutes { AppRoutes::with_default_routes() .add_route(controllers::guide::routes()) .add_route(controllers::auth::routes()) .add_route(controllers::home::routes()) // <--- add this } ``` That's it. Kill the server and bring it up again: ```sh cargo loco start ``` And hit `/home/hello`: ```sh $ curl localhost:5150/home/hello ola, mundo ``` You can take a look at all of your routes with: ``` $ cargo loco routes .. .. [POST] /api/auth/login [POST] /api/auth/register [POST] /api/auth/reset [POST] /api/auth/verify [GET] /home/hello <---- this is our new route! .. .. $ ```
The SaaS Starter keeps routes under /api because it is client-side ready and we are using the --api option in scaffolding.
When using client-side routing like React Router, we want to separate backend routes from client routes: the browser will use /home but not /api/home which is the backend route, and you can call /api/home from the client with no worries. Nevertheless, the routes: /_health and /_ping are exceptions, they stay at the root.
## MVC and You **Traditional MVC (Model-View-Controller) originated in desktop UI programming paradigms.** However, its applicability to web services led to its rapid adoption. MVC's golden era was around the early 2010s, and since then, many other paradigms and architectures have emerged. **MVC is still a very strong principle and architecture to follow for simplifying projects**, and this is what Loco follows too. Although web services and APIs don't have a concept of a _view_ because they do not generate HTML or UI responses, **we claim _stable_, _safe_ services and APIs indeed has a notion of a view** -- and that is the serialized data, its shape, its compatibility and its version. ``` // a typical loco app contains all parts of MVC src/ controllers/ users.rs mod.rs models/ _entities/ users.rs mod.rs users.rs mod.rs views/ users.rs mod.rs ``` **This is an important _cognitive_ principle**. And the principle claims that you can only create safe, compatible API responses if you treat those as a separate, independently governed _thing_ -- hence the 'V' in MVC, in Loco.
Models in Loco carry the same semantics as in Rails: fat models, slim controllers. This means that every time you want to build something -- you reach out to a model.
### Generating a model A model in Loco represents data *and* functionality. Typically the data is stored in your database. Most, if not all, business processes of your applications would be coded on the model (as an Active Record) or as an orchestration of a few models. Let's create a new model called `Article`: ```sh $ cargo loco generate model article title:string content:text added: "migration/src/m20231202_173012_articles.rs" injected: "migration/src/lib.rs" injected: "migration/src/lib.rs" added: "tests/models/articles.rs" injected: "tests/models/mod.rs" ``` ### Database migrations **Keeping your schema honest is done with migrations**. A migration is a singular change to your database structure: it can contain complete table additions, modifications, or index creation. ```rust // this was generated into `migrations/` from the command: // // $ cargo loco generate model article title:string content:text // // it is automatically applied by Loco's migrator framework. // you can also apply it manually using the command: // // $ cargo loco db migrate // #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .create_table( table_auto_tz(Articles::Table) .col(pk_auto(Articles::Id)) .col(string_null(Articles::Title)) .col(text(Articles::Content)) .to_owned(), ) .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { manager .drop_table(Table::drop().table(Articles::Table).to_owned()) .await } } ``` You can recreate a complete database **by applying migrations in-series onto a fresh database** -- this is done automatically by Loco's migrator (which is derived from SeaORM). When generating a new model, Loco will: - Generate a new "up" database migration - Apply the migration - Reflect the entities from database structure and generate back your `_entities` code You will find your new model as an entity, synchronized from your database structure in `models/_entities/`: ``` src/models/ ├── _entities │   ├── articles.rs <-- sync'd from db schema, do not edit │   ├── mod.rs │   ├── prelude.rs │   └── users.rs ├── articles.rs <-- generated for you, your logic goes here. ├── mod.rs └── users.rs ``` ### Using `playground` to interact with the database Your `examples/` folder contains: - `playground.rs` - a place to try out and experiment with your models and app logic. Let's fetch data using your models, using `playground.rs`: ```rust // located in examples/playground.rs // use this file to experiment with stuff use loco_rs::{cli::playground, prelude::*}; // to refer to articles::ActiveModel, your imports should look like this: use myapp::{app::App, models::_entities::articles}; #[tokio::main] async fn main() -> loco_rs::Result<()> { let ctx = playground::().await?; // add this: let res = articles::Entity::find().all(&ctx.db).await.unwrap(); println!("{:?}", res); Ok(()) } ``` ### Return a list of posts In the example, we use the following to return a list: ```rust let res = articles::Entity::find().all(&ctx.db).await.unwrap(); ``` To see how to run more queries, go to the [SeaORM docs](https://www.sea-ql.org/SeaORM/docs/next/basic-crud/select/). To execute your playground, run: ```rust $ cargo playground [] ``` Now, let's insert one item: ```rust async fn main() -> loco_rs::Result<()> { let ctx = playground::().await?; // add this: let active_model: articles::ActiveModel = articles::ActiveModel { title: Set(Some("how to build apps in 3 steps".to_string())), content: Set(Some("use Loco: https://loco.rs".to_string())), ..Default::default() }; active_model.insert(&ctx.db).await.unwrap(); let res = articles::Entity::find().all(&ctx.db).await.unwrap(); println!("{:?}", res); Ok(()) } ``` And run the playground again: ```sh $ cargo playground [Model { created_at: ..., updated_at: ..., id: 1, title: Some("how to build apps in 3 steps"), content: Some("use Loco: https://loco.rs") }] ``` We're now ready to plug this into an `articles` controller. First, generate a new controller: ```sh $ cargo loco generate controller articles --api added: "src/controllers/articles.rs" injected: "src/controllers/mod.rs" injected: "src/app.rs" added: "tests/requests/articles.rs" injected: "tests/requests/mod.rs" ``` Edit `src/controllers/articles.rs`: ```rust #![allow(clippy::unused_async)] use loco_rs::prelude::*; use crate::models::_entities::articles; pub async fn list(State(ctx): State) -> Result { let res = articles::Entity::find().all(&ctx.db).await?; format::json(res) } pub fn routes() -> Routes { Routes::new().prefix("api/articles").add("/", get(list)) } ``` Now, start the app: ```sh cargo loco start ``` And make a request: ```sh $ curl localhost:5150/api/articles [{"created_at":"...","updated_at":"...","id":1,"title":"how to build apps in 3 steps","content":"use Loco: https://loco.rs"}] ``` ## Building a CRUD API Next we'll see how to get a single article, delete, and edit a single article. Getting an article by ID is done using the `Path` extractor from `axum`. Replace the contents of `articles.rs` with this: ```rust // this is src/controllers/articles.rs #![allow(clippy::unused_async)] use loco_rs::prelude::*; use serde::{Deserialize, Serialize}; use crate::models::_entities::articles::{ActiveModel, Entity, Model}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Params { pub title: Option, pub content: Option, } impl Params { fn update(&self, item: &mut ActiveModel) { item.title = Set(self.title.clone()); item.content = Set(self.content.clone()); } } async fn load_item(ctx: &AppContext, id: i32) -> Result { let item = Entity::find_by_id(id).one(&ctx.db).await?; item.ok_or_else(|| Error::NotFound) } pub async fn list(State(ctx): State) -> Result { format::json(Entity::find().all(&ctx.db).await?) } pub async fn add(State(ctx): State, Json(params): Json) -> Result { let mut item: ActiveModel = Default::default(); params.update(&mut item); let item = item.insert(&ctx.db).await?; format::json(item) } pub async fn update( Path(id): Path, State(ctx): State, Json(params): Json, ) -> Result { let item = load_item(&ctx, id).await?; let mut item = item.into_active_model(); params.update(&mut item); let item = item.update(&ctx.db).await?; format::json(item) } pub async fn remove(Path(id): Path, State(ctx): State) -> Result { load_item(&ctx, id).await?.delete(&ctx.db).await?; format::empty() } pub async fn get_one(Path(id): Path, State(ctx): State) -> Result { format::json(load_item(&ctx, id).await?) } pub fn routes() -> Routes { Routes::new() .prefix("api/articles") .add("/", get(list)) .add("/", post(add)) .add("/{id}", get(get_one)) .add("/{id}", delete(remove)) .add("/{id}", patch(update)) } ``` A few items to note: - `Params` is a strongly typed required params data holder, and is similar in concept to Rails' _strongparams_, just safer. - `Path(id): Path` extracts the `:id` component from a URL. - Order of extractors is important and follows `axum`'s documentation (parameters, state, body). - It's always better to create a `load_item` helper function and use it in all singular-item routes. - While `use loco_rs::prelude::*` brings in anything you need to build a controller, you should note to import `crate::models::_entities::articles::{ActiveModel, Entity, Model}` as well as `Serialize, Deserialize` for params.
The order of the extractors is important, as changing the order of them can lead to compilation errors. Adding the #[debug_handler] macro to handlers can help by printing out better error messages. More information about extractors can be found in the axum documentation.
You can now test that it works, start the app: ```sh cargo loco start ``` Add a new article: ```sh $ curl -X POST -H "Content-Type: application/json" -d '{ "title": "Your Title", "content": "Your Content xxx" }' localhost:5150/api/articles {"created_at":"...","updated_at":"...","id":2,"title":"Your Title","content":"Your Content xxx"} ``` Get a list: ```sh $ curl localhost:5150/api/articles [{"created_at":"...","updated_at":"...","id":1,"title":"how to build apps in 3 steps","content":"use Loco: https://loco.rs"},{"created_at":"...","updated_at":"...","id":2,"title":"Your Title","content":"Your Content xxx"} ``` ### Adding a second model Let's add another model, this time: `Comment`. We want to create a relation - a comment belongs to a post, and each post can have multiple comments. Instead of coding the model and controller by hand, we're going to create a **comment scaffold** which will generate a fully working CRUD API comments. We're also going to use the special `references` type: ```sh $ cargo loco generate scaffold comment content:text article:references --api ```
The special <other_model>:references:<column_name> is also available. For when you want to have a different name for your column.
If you peek into the new migration, you'll discover a new database relation in the articles table: ```rust .. .. .col(integer(Comments::ArticleId)) .foreign_key( ForeignKey::create() .name("fk-comments-articles") .from(Comments::Table, Comments::ArticleId) .to(Articles::Table, Articles::Id) .on_delete(ForeignKeyAction::Cascade) .on_update(ForeignKeyAction::Cascade), ) .. .. ``` Now, lets modify our API in the following way: 1. Comments can be added through a shallow route: `POST comments/` 2. Comments can only be fetched in a nested route (forces a Post to exist): `GET posts/1/comments` 3. Comments cannot be updated, fetched singular, or deleted In `src/controllers/comments.rs`, remove unneeded routes and functions: ```rust pub fn routes() -> Routes { Routes::new() .prefix("api/comments") .add("/", post(add)) // .add("/", get(list)) // .add("/{id}", get(get_one)) // .add("/{id}", delete(remove)) // .add("/{id}", patch(update)) } ``` Also adjust the Params & update functions in `src/controllers/comments.rs`, by updating the scaffolded code marked with `<- add this` ```rust pub struct Params { pub content: Option, pub article_id: i32, // <- add this } impl Params { fn update(&self, item: &mut ActiveModel) { item.content = Set(self.content.clone()); item.article_id = Set(self.article_id.clone()); // <- add this } } ``` Now we need to fetch a relation in `src/controllers/articles.rs`. Add the following route: ```rust pub fn routes() -> Routes { // .. // .. .add("/{id}/comments", get(comments)) } ``` And implement the relation fetching: ```rust // to refer to comments::Entity, your imports should look like this: use crate::models::_entities::{ articles::{ActiveModel, Entity, Model}, comments, }; pub async fn comments( Path(id): Path, State(ctx): State, ) -> Result { let item = load_item(&ctx, id).await?; let comments = item.find_related(comments::Entity).all(&ctx.db).await?; format::json(comments) } ```
This is called "lazy loading", where we fetch the item first and later its associated relation. Don't worry - there is also a way to eagerly load comments along with an article.
Now start the app again: ```sh cargo loco start ``` Add a comment to Article `1`: ```sh $ curl -X POST -H "Content-Type: application/json" -d '{ "content": "this rocks", "article_id": 1 }' localhost:5150/api/comments {"created_at":"...","updated_at":"...","id":4,"content":"this rocks","article_id":1} ``` And, fetch the relation: ```sh $ curl localhost:5150/api/articles/1/comments [{"created_at":"...","updated_at":"...","id":4,"content":"this rocks","article_id":1}] ``` This ends our comprehensive _Guide to Loco_. If you made it this far, hurray!. ## Tasks: export data report Real world apps require handling real world situations. Say some of your users or customers require some kind of a report. You can: - Connect to your production database, issue ad-hoc SQL queries. Or use some kind of DB tool. _This is unsafe, insecure, prone to errors, and cannot be automated_. - Export your data to something like Redshift, or Google, and issue a query there. _This is a waste of resource, insecure, cannot be tested properly, and slow_. - Build an admin. _This is time-consuming, and waste_. - **Or build an adhoc task in Rust, which is quick to write, type safe, guarded by the compiler, fast, environment-aware, testable, and secure.** This is where `cargo loco task` comes in. First, run `cargo loco task` to see current tasks: ```sh $ cargo loco task seed_data [Task for seeding data] ``` Generate a new task `user_report` ```sh $ cargo loco generate task user_report added: "src/tasks/user_report.rs" injected: "src/tasks/mod.rs" injected: "src/app.rs" added: "tests/tasks/user_report.rs" injected: "tests/tasks/mod.rs" ``` In `src/tasks/user_report.rs` you'll see the task that was generated for you. Replace it with following: ```rust // find it in `src/tasks/user_report.rs` use loco_rs::prelude::*; use loco_rs::task::Vars; use crate::models::users; pub struct UserReport; #[async_trait] impl Task for UserReport { fn task(&self) -> TaskInfo { // description that appears on the CLI TaskInfo { name: "user_report".to_string(), detail: "output a user report".to_string(), } } // variables through the CLI: // `$ cargo loco task name:foobar count:2` // will appear as {"name":"foobar", "count":2} in `vars` async fn run(&self, app_context: &AppContext, vars: &Vars) -> Result<()> { let users = users::Entity::find().all(&app_context.db).await?; println!("args: {vars:?}"); println!("!!! user_report: listing users !!!"); println!("------------------------"); for user in &users { println!("user: {}", user.email); } println!("done: {} users", users.len()); Ok(()) } } ``` You can modify this task as you see fit. Access the models with `app_context`, or any other environmental resources, and fetch variables that were given through the CLI with `vars`. Running this task is done with: ```rust $ cargo loco task user_report var1:val1 var2:val2 ... args: Vars { cli: {"var1": "val1", "var2": "val2"} } !!! user_report: listing users !!! ------------------------ done: 0 users ``` If you have not added a user before, the report will be empty. To add a user check out chapter [Registering a New User](/docs/getting-started/tour/#registering-a-new-user) of [A Quick Tour with Loco](/docs/getting-started/tour/). Remember: this is environmental, so you write the task once, and then execute in development or production as you wish. Tasks are compiled into the main app binary. ## Authentication: authenticating your requests If you chose the `SaaS App` starter, you should have a fully configured authentication module baked into the app. Let's see how to require authentication when **adding comments**. Go back to `src/controllers/comments.rs` and take a look at the `add` function: ```rust pub async fn add(State(ctx): State, Json(params): Json) -> Result { let mut item: ActiveModel = Default::default(); params.update(&mut item); let item = item.insert(&ctx.db).await?; format::json(item) } ``` To require authentication, we need to modify the function signature in this way: ```rust async fn add( auth: auth::JWT, State(ctx): State, Json(params): Json, ) -> Result { // we only want to make sure it exists let _current_user = crate::models::users::Model::find_by_pid(&ctx.db, &auth.claims.pid).await?; // next, update // homework/bonus: make a comment _actually_ belong to user (user_id) let mut item: ActiveModel = Default::default(); params.update(&mut item); let item = item.insert(&ctx.db).await?; format::json(item) } ``` ================================================ FILE: docs-site/content/docs/getting-started/starters.md ================================================ +++ title = "Starters" date = 2021-12-19T08:00:00+00:00 updated = 2021-12-19T08:00:00+00:00 draft = false weight = 4 sort_by = "weight" template = "docs/page.html" [extra] toc = true top = false flair =[] +++ Simplify your project setup with Loco's predefined boilerplates, designed to make your development journey smoother. To get started, install our CLI and choose the template that suits your needs. ```sh cargo install loco cargo install sea-orm-cli # Only when DB is needed ``` Create a starter: ```sh ❯ loco new ✔ ❯ App name? · myapp ✔ ❯ What would you like to build? · Saas App with client side rendering ✔ ❯ Select a DB Provider · Sqlite ✔ ❯ Select your background worker type · Async (in-process tokio async tasks) 🚂 Loco app generated successfully in: myapp/ - assets: You've selected `clientside` for your asset serving configuration. Next step, build your frontend: $ cd frontend/ $ npm install && npm run build ``` ## Available Starters ### Command Line Options Print the command line options: ```console $ loco new --help Create a new Loco app Usage: loco[EXE] new [OPTIONS] Options: -p, --path Local path to generate into [default: .] -v, --verbose Verbosity level [default: ERROR] -n, --name App name -t, --template