├── .editorconfig ├── .github ├── FUNDING.yml └── workflows │ ├── ci.yml │ ├── docs.yml │ └── publish.yml ├── .gitignore ├── ARCHITECTURE.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── ROADMAP.md ├── docs ├── .gitignore ├── docs │ ├── .pages │ ├── CNAME │ ├── background-jobs │ │ ├── cron.md │ │ ├── index.md │ │ └── queue-guarantees.md │ ├── configuration.md │ ├── controllers │ │ ├── .pages │ │ ├── REST │ │ │ ├── index.md │ │ │ └── model-controller.md │ │ ├── authentication.md │ │ ├── cookies.md │ │ ├── custom-errors.md │ │ ├── index.md │ │ ├── middleware.md │ │ ├── pages.md │ │ ├── request.md │ │ ├── response.md │ │ ├── sessions.md │ │ ├── static-files.md │ │ └── websockets.md │ ├── index.md │ ├── logging.md │ ├── migrating-from-python.md │ ├── migrating-from-rails.md │ ├── models │ │ ├── .pages │ │ ├── connection-pool.md │ │ ├── create-records.md │ │ ├── custom-queries.md │ │ ├── customize-attributes.md │ │ ├── debug-queries.md │ │ ├── fetch-records.md │ │ ├── grouping.md │ │ ├── index.md │ │ ├── join-models.md │ │ ├── migrations.md │ │ ├── scopes.md │ │ ├── security.md │ │ └── update-records.md │ ├── robots.txt │ ├── security │ │ ├── CSRF.md │ │ ├── encryption.md │ │ └── hashing.md │ ├── style.css │ ├── user-guides │ │ ├── .pages │ │ ├── admin.md │ │ ├── build-your-app │ │ │ ├── add-users.md │ │ │ └── index.md │ │ ├── deploy-to-prod.md │ │ ├── hot-reload.md │ │ └── index.md │ └── views │ │ ├── index.md │ │ ├── templates │ │ ├── .pages │ │ ├── caching.md │ │ ├── context.md │ │ ├── for-loops.md │ │ ├── functions │ │ │ ├── .pages │ │ │ ├── float.md │ │ │ ├── hash.md │ │ │ ├── index.md │ │ │ ├── integer.md │ │ │ ├── list.md │ │ │ └── string.md │ │ ├── if-statements.md │ │ ├── index.md │ │ ├── nomenclature.md │ │ ├── partials.md │ │ ├── templates-in-controllers.md │ │ └── variables.md │ │ └── turbo │ │ ├── .pages │ │ ├── building-pages.md │ │ ├── index.md │ │ └── streams.md ├── mkdocs.yml └── requirements.txt ├── examples ├── auth │ ├── Cargo.toml │ ├── README.md │ ├── rwf.toml │ ├── src │ │ └── main.rs │ └── templates │ │ └── index.html ├── background-jobs │ ├── Cargo.toml │ ├── README.md │ ├── rwf.toml │ └── src │ │ └── main.rs ├── django │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── README.md │ ├── requirements.txt │ ├── rwf.toml │ ├── src │ │ └── main.rs │ └── todo │ │ ├── db.sqlite3 │ │ ├── manage.py │ │ └── todo │ │ ├── __init__.py │ │ ├── asgi.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py ├── dynamic-templates │ ├── Cargo.toml │ ├── README.md │ ├── src │ │ └── main.rs │ └── templates │ │ └── index.html ├── engine │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── files │ ├── Cargo.toml │ ├── migrations │ │ └── .gitkeep │ ├── src │ │ ├── controllers │ │ │ └── mod.rs │ │ ├── main.rs │ │ └── models │ │ │ └── mod.rs │ ├── static │ │ └── .gitkeep │ └── templates │ │ ├── .gitkeep │ │ ├── head.html │ │ ├── ok.html │ │ └── upload.html ├── middleware │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── orm │ ├── Cargo.toml │ ├── README.md │ ├── migrations │ │ ├── 1_users.down.sql │ │ └── 1_users.up.sql │ ├── rwf.toml │ └── src │ │ ├── bin │ │ └── migrate │ │ │ └── main.rs │ │ └── main.rs ├── quick-start │ ├── Cargo.toml │ ├── README.md │ └── src │ │ └── main.rs ├── rails │ ├── .cargo │ │ ├── config │ │ └── config.toml │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Gemfile │ ├── Gemfile.lock │ ├── src │ │ └── main.rs │ └── todo │ │ ├── .dockerignore │ │ ├── .gitattributes │ │ ├── .github │ │ ├── dependabot.yml │ │ └── workflows │ │ │ └── ci.yml │ │ ├── .gitignore │ │ ├── .rubocop.yml │ │ ├── .ruby-version │ │ ├── Dockerfile │ │ ├── Gemfile │ │ ├── Gemfile.lock │ │ ├── README.md │ │ ├── Rakefile │ │ ├── app │ │ ├── assets │ │ │ ├── config │ │ │ │ └── manifest.js │ │ │ ├── images │ │ │ │ └── .keep │ │ │ └── stylesheets │ │ │ │ └── application.css │ │ ├── channels │ │ │ └── application_cable │ │ │ │ ├── channel.rb │ │ │ │ └── connection.rb │ │ ├── controllers │ │ │ ├── application_controller.rb │ │ │ ├── concerns │ │ │ │ └── .keep │ │ │ └── todo_controller.rb │ │ ├── helpers │ │ │ ├── application_helper.rb │ │ │ └── todo_helper.rb │ │ ├── jobs │ │ │ └── application_job.rb │ │ ├── mailers │ │ │ └── application_mailer.rb │ │ ├── models │ │ │ ├── application_record.rb │ │ │ └── concerns │ │ │ │ └── .keep │ │ └── views │ │ │ ├── layouts │ │ │ ├── application.html.erb │ │ │ ├── mailer.html.erb │ │ │ └── mailer.text.erb │ │ │ └── pwa │ │ │ ├── manifest.json.erb │ │ │ └── service-worker.js │ │ ├── bin │ │ ├── brakeman │ │ ├── bundle │ │ ├── docker-entrypoint │ │ ├── rails │ │ ├── rake │ │ ├── rubocop │ │ └── setup │ │ ├── config.ru │ │ ├── config │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── cable.yml │ │ ├── credentials.yml.enc │ │ ├── database.yml │ │ ├── environment.rb │ │ ├── environments │ │ │ ├── development.rb │ │ │ ├── production.rb │ │ │ └── test.rb │ │ ├── initializers │ │ │ ├── assets.rb │ │ │ ├── content_security_policy.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── inflections.rb │ │ │ └── permissions_policy.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── puma.rb │ │ ├── routes.rb │ │ └── storage.yml │ │ ├── db │ │ └── seeds.rb │ │ ├── lib │ │ ├── assets │ │ │ └── .keep │ │ └── tasks │ │ │ └── .keep │ │ ├── log │ │ └── .keep │ │ ├── public │ │ ├── 404.html │ │ ├── 406-unsupported-browser.html │ │ ├── 422.html │ │ ├── 500.html │ │ ├── icon.png │ │ ├── icon.svg │ │ └── robots.txt │ │ ├── storage │ │ └── .keep │ │ ├── test │ │ ├── application_system_test_case.rb │ │ ├── channels │ │ │ └── application_cable │ │ │ │ └── connection_test.rb │ │ ├── controllers │ │ │ ├── .keep │ │ │ └── todo_controller_test.rb │ │ ├── fixtures │ │ │ └── files │ │ │ │ └── .keep │ │ ├── helpers │ │ │ └── .keep │ │ ├── integration │ │ │ └── .keep │ │ ├── mailers │ │ │ └── .keep │ │ ├── models │ │ │ └── .keep │ │ ├── system │ │ │ └── .keep │ │ └── test_helper.rb │ │ ├── tmp │ │ ├── .keep │ │ ├── pids │ │ │ └── .keep │ │ └── storage │ │ │ └── .keep │ │ └── vendor │ │ └── .keep ├── request-tracking │ ├── Cargo.toml │ ├── migrations │ │ └── .gitkeep │ ├── rwf.toml │ ├── src │ │ ├── controllers │ │ │ └── mod.rs │ │ ├── main.rs │ │ └── models │ │ │ └── mod.rs │ └── templates │ │ └── .gitkeep ├── rest │ ├── Cargo.toml │ ├── README.md │ ├── bin │ │ └── migrations │ │ │ └── main.rs │ ├── migrations │ │ ├── 1_users.down.sql │ │ └── 1_users.up.sql │ ├── rwf.toml │ └── src │ │ ├── main.rs │ │ ├── rest.rs │ │ └── secure.rs ├── scheduled-jobs │ ├── Cargo.toml │ ├── README.md │ ├── rwf.toml │ └── src │ │ └── main.rs ├── static-files │ ├── Cargo.toml │ ├── src │ │ └── main.rs │ └── static │ │ └── hello.txt ├── turbo │ ├── Cargo.toml │ ├── Dockerfile │ ├── README.md │ ├── migrations │ │ ├── 1728779072_users.down.sql │ │ ├── 1728779072_users.up.sql │ │ ├── 1728836660_messages.down.sql │ │ ├── 1728836660_messages.up.sql │ │ ├── 1729119889028371278_unnamed.down.sql │ │ └── 1729119889028371278_unnamed.up.sql │ ├── rwf.toml │ ├── src │ │ ├── bin │ │ │ └── run_migrations │ │ │ │ └── main.rs │ │ ├── controllers │ │ │ ├── chat │ │ │ │ ├── form.rs │ │ │ │ ├── mod.rs │ │ │ │ └── typing │ │ │ │ │ └── mod.rs │ │ │ ├── mod.rs │ │ │ └── signup │ │ │ │ ├── form.rs │ │ │ │ ├── middleware.rs │ │ │ │ └── mod.rs │ │ ├── main.rs │ │ └── models │ │ │ ├── chat_message.rs │ │ │ ├── mod.rs │ │ │ └── user.rs │ ├── static │ │ ├── css │ │ │ └── bootstrap.min.css │ │ └── js │ │ │ ├── bootstrap.min.js │ │ │ ├── form_controller.js │ │ │ ├── main.js │ │ │ ├── stimulus.js │ │ │ └── turbo.js │ └── templates │ │ ├── chat.html │ │ ├── chat_form.html │ │ ├── chat_message.html │ │ ├── footer.html │ │ ├── header.html │ │ ├── signup.html │ │ └── typing.html └── users │ ├── Cargo.toml │ ├── migrations │ ├── 1733265254409864495_users.down.sql │ └── 1733265254409864495_users.up.sql │ ├── rwf.toml │ ├── src │ ├── controllers.rs │ ├── main.rs │ └── models.rs │ └── templates │ ├── head.html │ ├── profile.html │ └── signup.html ├── rwf-admin ├── Cargo.toml ├── README.md ├── rwf.toml ├── src │ ├── controllers │ │ ├── index.rs │ │ ├── jobs.rs │ │ ├── mod.rs │ │ ├── models.rs │ │ └── requests.rs │ ├── lib.rs │ ├── main.rs │ └── models │ │ └── mod.rs ├── static │ └── rwf_admin │ │ ├── css │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ │ ├── images │ │ └── logo.svg │ │ └── js │ │ ├── bootstrap.min.js │ │ ├── bootstrap.min.js.map │ │ ├── popper.min.js │ │ ├── reload_controller.js │ │ └── requests_controller.js └── templates │ └── rwf_admin │ ├── footer.html │ ├── head.html │ ├── jobs.html │ ├── model.html │ ├── model_new.html │ ├── model_pages.html │ ├── models.html │ ├── nav.html │ ├── reload.html │ └── requests.html ├── rwf-cli ├── .gitignore ├── Cargo.toml ├── README.md └── src │ ├── add.rs │ ├── deploy.rs │ ├── logging.rs │ ├── main.rs │ ├── migrate.rs │ ├── remove.rs │ ├── setup.rs │ ├── templates │ ├── Procfile │ ├── controller.rs.tpl │ ├── mod.rs.tpl │ ├── page-controller.rs.tpl │ └── routes.rs.tpl │ └── util.rs ├── rwf-fuzz ├── Cargo.lock ├── Cargo.toml ├── in │ ├── path_fuzzer │ │ ├── url1 │ │ ├── url2 │ │ ├── url3 │ │ └── url4 │ └── router_fuzzer │ │ ├── request1 │ │ └── request2 └── src │ ├── bin │ ├── path_fuzzer.rs │ └── router_fuzzer.rs │ └── main.rs ├── rwf-macros ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── src │ ├── lib.rs │ ├── model.rs │ ├── prelude.rs │ └── render.rs └── tests │ └── model │ ├── relationship.expanded.rs │ └── relationship.rs ├── rwf-ruby ├── Cargo.lock ├── Cargo.toml ├── README.md ├── build.rs ├── headers.rb ├── src │ ├── .clangd │ ├── bindings.c │ ├── bindings.h │ └── lib.rs └── tests │ ├── config.ru │ └── todo │ ├── .dockerignore │ ├── .gitattributes │ ├── .github │ ├── dependabot.yml │ └── workflows │ │ └── ci.yml │ ├── .gitignore │ ├── .rubocop.yml │ ├── .ruby-version │ ├── Dockerfile │ ├── Gemfile │ ├── Gemfile.lock │ ├── README.md │ ├── Rakefile │ ├── app │ ├── assets │ │ ├── config │ │ │ └── manifest.js │ │ ├── images │ │ │ └── .keep │ │ └── stylesheets │ │ │ └── application.css │ ├── channels │ │ └── application_cable │ │ │ ├── channel.rb │ │ │ └── connection.rb │ ├── controllers │ │ ├── application_controller.rb │ │ └── concerns │ │ │ └── .keep │ ├── helpers │ │ └── application_helper.rb │ ├── javascript │ │ ├── application.js │ │ └── controllers │ │ │ ├── application.js │ │ │ ├── hello_controller.js │ │ │ └── index.js │ ├── jobs │ │ └── application_job.rb │ ├── mailers │ │ └── application_mailer.rb │ ├── models │ │ ├── application_record.rb │ │ └── concerns │ │ │ └── .keep │ └── views │ │ ├── layouts │ │ ├── application.html.erb │ │ ├── mailer.html.erb │ │ └── mailer.text.erb │ │ └── pwa │ │ ├── manifest.json.erb │ │ └── service-worker.js │ ├── bin │ ├── brakeman │ ├── bundle │ ├── docker-entrypoint │ ├── importmap │ ├── rails │ ├── rake │ ├── rubocop │ └── setup │ ├── config.ru │ ├── config │ ├── application.rb │ ├── boot.rb │ ├── cable.yml │ ├── credentials.yml.enc │ ├── database.yml │ ├── environment.rb │ ├── environments │ │ ├── development.rb │ │ ├── production.rb │ │ └── test.rb │ ├── importmap.rb │ ├── initializers │ │ ├── assets.rb │ │ ├── content_security_policy.rb │ │ ├── filter_parameter_logging.rb │ │ ├── inflections.rb │ │ └── permissions_policy.rb │ ├── locales │ │ └── en.yml │ ├── puma.rb │ ├── routes.rb │ └── storage.yml │ ├── db │ └── seeds.rb │ ├── lib │ ├── assets │ │ └── .keep │ └── tasks │ │ └── .keep │ ├── log │ └── .keep │ ├── public │ ├── 404.html │ ├── 406-unsupported-browser.html │ ├── 422.html │ ├── 500.html │ ├── icon.png │ ├── icon.svg │ └── robots.txt │ ├── storage │ └── .keep │ ├── test │ ├── application_system_test_case.rb │ ├── channels │ │ └── application_cable │ │ │ └── connection_test.rb │ ├── controllers │ │ └── .keep │ ├── fixtures │ │ └── files │ │ │ └── .keep │ ├── helpers │ │ └── .keep │ ├── integration │ │ └── .keep │ ├── mailers │ │ └── .keep │ ├── models │ │ └── .keep │ ├── system │ │ └── .keep │ └── test_helper.rb │ ├── tmp │ ├── .keep │ ├── pids │ │ └── .keep │ └── storage │ │ └── .keep │ └── vendor │ ├── .keep │ └── javascript │ └── .keep ├── rwf-tests ├── .gitignore ├── Cargo.toml ├── migrations │ ├── 0_rum.down.sql │ ├── 0_rum.up.sql │ ├── 1_users.down.sql │ ├── 1_users.up.sql │ ├── 2_orders.down.sql │ ├── 2_orders.up.sql │ ├── 3_products.down.sql │ ├── 3_products.up.sql │ ├── 4_order_items.down.sql │ ├── 4_order_items.up.sql │ ├── 5_data.down.sql │ └── 5_data.up.sql ├── src │ ├── components │ │ ├── login │ │ │ ├── controller.js │ │ │ ├── index.html │ │ │ ├── mod.rs │ │ │ ├── show.html │ │ │ └── style.scss │ │ └── mod.rs │ ├── controllers │ │ ├── login │ │ │ └── mod.rs │ │ └── mod.rs │ ├── main.rs │ └── models │ │ └── mod.rs ├── static │ └── test.txt └── templates │ ├── index.html │ └── test.html ├── rwf ├── .gitignore ├── Cargo.toml ├── README.md └── src │ ├── analytics │ ├── mod.rs │ └── requests.rs │ ├── colors.rs │ ├── comms.rs │ ├── config.rs │ ├── controller │ ├── auth.rs │ ├── engine.rs │ ├── error.rs │ ├── middleware │ │ ├── csrf.rs │ │ ├── mod.rs │ │ ├── prelude.rs │ │ ├── rate_limiter.rs │ │ ├── request_tracker.rs │ │ └── secure_id.rs │ ├── mod.rs │ ├── rack.rs │ ├── ser.rs │ ├── static_files.rs │ ├── turbo_stream.rs │ └── wsgi.rs │ ├── crypto.rs │ ├── error.rs │ ├── hmr.rs │ ├── http │ ├── authorization.rs │ ├── body.rs │ ├── cookies.rs │ ├── error.html │ ├── error.rs │ ├── form.rs │ ├── form_data.rs │ ├── handler.rs │ ├── head.rs │ ├── headers.rs │ ├── mod.rs │ ├── path │ │ ├── mod.rs │ │ ├── params.rs │ │ ├── query.rs │ │ ├── to_parameter.rs │ │ └── with_regex.rs │ ├── request.rs │ ├── response.rs │ ├── router.rs │ ├── server.rs │ ├── url.rs │ ├── websocket │ │ └── mod.rs │ └── wsgi │ │ ├── mod.rs │ │ ├── request.rs │ │ └── uwsgi_wrapper.py │ ├── job │ ├── clock.rs │ ├── cron.rs │ ├── error.rs │ ├── mod.rs │ ├── model.rs │ └── worker.rs │ ├── lib.rs │ ├── lock.rs │ ├── logging.rs │ ├── model │ ├── callbacks.rs │ ├── column.rs │ ├── error.rs │ ├── escape.rs │ ├── exists.rs │ ├── explain.rs │ ├── filter.rs │ ├── insert.rs │ ├── join.rs │ ├── limit.rs │ ├── lock.rs │ ├── migrations │ │ ├── bootstrap.sql │ │ ├── mod.rs │ │ └── model.rs │ ├── mod.rs │ ├── order_by.rs │ ├── picked.rs │ ├── placeholders.rs │ ├── pool │ │ ├── connection.rs │ │ ├── mod.rs │ │ └── transaction.rs │ ├── prelude.rs │ ├── row.rs │ ├── select.rs │ ├── update.rs │ └── value.rs │ ├── prelude.rs │ └── view │ ├── cache.rs │ ├── mod.rs │ ├── prelude.rs │ ├── template │ ├── context.rs │ ├── error.rs │ ├── head.html │ ├── language.txt │ ├── language │ │ ├── expression.rs │ │ ├── mod.rs │ │ ├── op.rs │ │ ├── program.rs │ │ ├── statement.rs │ │ └── term.rs │ ├── lexer │ │ ├── mod.rs │ │ ├── token.rs │ │ └── value.rs │ ├── mod.rs │ └── turbo-stream.html │ └── turbo │ ├── mod.rs │ └── stream.html └── scripts ├── Dockerfile ├── build_cli.sh ├── check_publish.sh └── docs.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [.*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: levkk 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 12 | polar: # Replace with a single Polar username 13 | buy_me_a_coffee: levkk 14 | thanks_dev: # Replace with a single thanks.dev username 15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: 3 | push: 4 | 5 | jobs: 6 | fmt: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions-rs/toolchain@v1 11 | with: 12 | toolchain: stable 13 | override: true 14 | - name: Format 15 | run: cargo fmt --all -- --check 16 | build: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | - uses: actions-rs/toolchain@v1 21 | with: 22 | toolchain: stable 23 | override: true 24 | - name: Build 25 | run: cargo build 26 | tests: 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Setup PostgreSQL 30 | run: | 31 | sudo sed -i 's/scram-sha-256/trust/g' /etc/postgresql/14/main/pg_hba.conf 32 | sudo service postgresql restart 33 | sudo -u postgres createuser --superuser --login $USER 34 | createdb $USER 35 | createdb rwf_users 36 | psql postgres://$USER@127.0.0.1:5432/$USER -c "SELECT 1" > /dev/null 37 | - uses: actions/checkout@v4 38 | - uses: actions-rs/toolchain@v1 39 | with: 40 | toolchain: stable 41 | override: true 42 | - uses: Swatinem/rust-cache@v2 43 | - name: Install test dependencies 44 | run: cargo install cargo-nextest cargo-expand 45 | - name: Run tests 46 | run: cargo nextest run 47 | - name: Run documentation tests 48 | run: cargo test --doc 49 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | permissions: 8 | contents: write 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Configure Git Credentials 15 | run: | 16 | git config user.name github-actions[bot] 17 | git config user.email 41898282+github-actions[bot]@users.noreply.github.com 18 | - uses: actions/setup-python@v5 19 | with: 20 | python-version: 3.x 21 | - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV 22 | - uses: actions/cache@v4 23 | with: 24 | key: mkdocs-material-${{ env.cache_id }} 25 | path: .cache 26 | restore-keys: | 27 | mkdocs-material- 28 | - run: pip install -r requirements.txt 29 | working-directory: docs 30 | - run: mkdocs gh-deploy --force 31 | working-directory: docs 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | *.tar.* 4 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | Rwf is built using [Model-View-Controller (MVC)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller). See [docs.rs](https://docs.rs/rwf) for internals documentation. 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | See [Releases](https://github.com/levkk/rwf/releases). 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "examples/auth", 5 | "examples/background-jobs", 6 | "examples/rest", 7 | "examples/dynamic-templates", 8 | "examples/middleware", 9 | "examples/orm", 10 | "examples/quick-start", 11 | "examples/scheduled-jobs", 12 | "examples/static-files", 13 | "examples/turbo", 14 | "rwf", 15 | "rwf-cli", 16 | "rwf-macros", 17 | "rwf-tests", 18 | "examples/request-tracking", 19 | "examples/engine", 20 | "rwf-admin", 21 | "examples/files", "examples/users", 22 | ] 23 | exclude = ["examples/rails", "rwf-ruby", "examples/django", "rwf-fuzz"] 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Rwf Developers 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 4 | associated documentation files (the “Software”), to deal in the Software without restriction, including 5 | without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 6 | copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 7 | following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial 10 | portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 13 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 14 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR 16 | THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | See [Issues](https://github.com/levkk/rwf/issues) and [Road to V1](https://github.com/users/levkk/projects/1). 4 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | *.md.bak 2 | -------------------------------------------------------------------------------- /docs/docs/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - 'index.md' 3 | - 'controllers' 4 | - 'models' 5 | - 'views' 6 | - 'configuration.md' 7 | - 'background-jobs' 8 | - 'user-guides' 9 | - '...' 10 | -------------------------------------------------------------------------------- /docs/docs/CNAME: -------------------------------------------------------------------------------- 1 | rustwebframework.org -------------------------------------------------------------------------------- /docs/docs/controllers/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - 'index.md' 3 | - 'request.md' 4 | - 'response.md' 5 | - 'cookies.md' 6 | - '...' 7 | -------------------------------------------------------------------------------- /docs/docs/controllers/static-files.md: -------------------------------------------------------------------------------- 1 | # Static files 2 | 3 | Rwf comes with a static files server built-in. It will handle serving files out of any directory 4 | and will automatically return the right `Content-Type` header (also known as [MIME](https://developer.mozilla.org/en-US/docs/Web/HTTP/MIME_types)), based on the file extension. 5 | 6 | ## Serve static files 7 | 8 | The static files server is just another [controller](index.md), implemented internally. To add it to your app, you can 9 | add it to the server at startup: 10 | 11 | ```rust 12 | use rwf::controller::StaticFiles; 13 | use rwf::http::{Server, self}; 14 | 15 | #[tokio::main] 16 | async fn main() -> Result<(), http::Error> { 17 | let server = Server::new(vec![ 18 | StaticFiles::serve("static")?, 19 | ]) 20 | .launch() 21 | .await 22 | } 23 | ``` 24 | 25 | This example will serve all static files in the `static` directory under the `/static` route. 26 | -------------------------------------------------------------------------------- /docs/docs/models/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - 'index.md' 3 | - 'create-records.md' 4 | - 'fetch-records.md' 5 | - 'update-records.md' 6 | - 'join-models.md' 7 | - 'scopes.md' 8 | - 'debug-queries.md' 9 | - 'custom-queries.md' 10 | - 'grouping.md' 11 | - '...' 12 | -------------------------------------------------------------------------------- /docs/docs/models/customize-attributes.md: -------------------------------------------------------------------------------- 1 | # Customize attributes 2 | 3 | When defining models, the `Model` macro makes certain assumptions about your database table and column names. For example, the name of the table is derived from the struct name: 4 | 5 | ```rust 6 | #[derive(Clone, macros::Model)] 7 | struct User { 8 | id: Option, 9 | } 10 | ``` 11 | 12 | The name of the struct, `User` is lowercased and pluralized, to derive the table name `"users"`. Similarly, the foreign key for the `"users"` table is derived to be `"user_id"`. 13 | 14 | It's possible to override this behavior, by specifying both table name and foreign key names manually: 15 | 16 | ```rust 17 | #[derive(Clone, macros::Model)] 18 | #[table_name("my_user_table")] 19 | #[foreign_key("u_id")] 20 | struct User { 21 | id: Option, 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /docs/docs/models/debug-queries.md: -------------------------------------------------------------------------------- 1 | # Debug queries 2 | 3 | When building queries using the ORM, the end result can be inspected by calling `to_sql()`: 4 | 5 | === "Rust" 6 | ```rust 7 | let query = User::all() 8 | .filter("created_at", Value::Null) 9 | .limit(5) 10 | .to_sql(); 11 | println!("Query: {}", query); 12 | ``` 13 | === "Output" 14 | ``` 15 | Query: SELECT * FROM "users" WHERE "created_at" IS NULL LIMIT 5 16 | ``` 17 | 18 | The query will not be sent to the database, so it's safe to inspect all queries, no matter if they are performant or not. 19 | 20 | ## Query plan 21 | 22 | Visual inspection of the query is often not sufficient to understand query performance. For this purpose, databases like PostgreSQL provide 23 | the `EXPLAIN` functionality which, instead of executing the query, produces an execution plan: 24 | 25 | === "Rust" 26 | ```rust 27 | let plan = User::find(15) 28 | .explain(&mut conn) 29 | .await?; 30 | println!("{}", plan); 31 | ``` 32 | === "SQL" 33 | ```postgresql 34 | EXPLAIN SELECT * FROM "users" WHERE "id" = $1 35 | ``` 36 | === "Output" 37 | ``` 38 | Seq Scan on users (cost=0.00..25.00 rows=6 width=40) 39 | Filter: (id = 5) 40 | ``` 41 | 42 | When optimizing queries, this functionality is useful for finding queries that should be using indexes but perform a sequential scan instead. 43 | -------------------------------------------------------------------------------- /docs/docs/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /docs/docs/style.css: -------------------------------------------------------------------------------- 1 | table { 2 | table-layout: fixed !important; 3 | display: table !important; 4 | } 5 | -------------------------------------------------------------------------------- /docs/docs/user-guides/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - 'index.md' 3 | - 'build-your-app' 4 | - 'hot-reload.md' 5 | - '...' 6 | - 'deploy-to-prod.md' 7 | - 'admin.md' 8 | -------------------------------------------------------------------------------- /docs/docs/user-guides/admin.md: -------------------------------------------------------------------------------- 1 | # Admin panel 2 | 3 | Rwf comes with its own admin panel which provides a real time overview of web activity, insights into the background jobs queue, and allows to manipulate database models. 4 | 5 | The admin panel is written with Rwf and can be included into any Rwf-powered application. 6 | 7 | ## Enable the admin panel 8 | 9 | The admin panel comes in its own crate: `rwf-admin`. To enable it, add it to your application dependencies: 10 | 11 | ```bash 12 | cargo add rwf-admin 13 | ``` 14 | 15 | ### Preload templates 16 | 17 | The admin panel has its own templates and static files which need to be preloaded at runtime: 18 | 19 | ```rust 20 | use rwf::prelude::*; 21 | use rwf::http::{Server, Error}; 22 | 23 | #[tokio::main] 24 | async fn main() -> Result<(), Error> { 25 | // Preload templates and static files. 26 | rwf_admin::install()?; 27 | 28 | // Launch the server below. 29 | } 30 | ``` 31 | 32 | ### Add routes 33 | 34 | Add the admin panel routes to your HTTP server: 35 | 36 | ```rust 37 | let mut routes = vec![ 38 | // Your application routes. 39 | ]; 40 | 41 | // Add admin routes. 42 | routes.extend(rwf_admin::routes()); 43 | 44 | // Launch the server. 45 | Server::new(routes) 46 | .launch() 47 | .await?; 48 | ``` 49 | 50 | ## Learn more 51 | 52 | - [examples/turbo](https://github.com/levkk/rwf/tree/main/examples/turbo) app uses the admin panel 53 | - [rwf-admin](https://github.com/levkk/rwf/tree/main/rwf-admin) 54 | -------------------------------------------------------------------------------- /docs/docs/user-guides/build-your-app/index.md: -------------------------------------------------------------------------------- 1 | # Build with Rwf 2 | 3 | Rwf has a lot of features and mixing them together can create powerful and efficient web apps. This guide will demonstrate how 4 | to build a generic application from scratch using Rwf as your web framework. 5 | 6 | !!! note 7 | This guide is a work in progress. Please check back soon for more updates. [Contributions](https://github.com/levkk/rwf/tree/main/docs/docs/user-guides/build-your-app) are welcome! 8 | 9 | ## Getting started 10 | 11 | If you'd like to build an application with this guide, make sure make sure to install the Rwf CLI first: 12 | 13 | ``` 14 | cargo install rwf-cli 15 | ``` 16 | 17 | Once the CLI is installed, make sure to follow the [instructions](../../index.md) on creating a new Rwf application. 18 | 19 | ## Chapters 20 | 21 | 1. [Add users](add-users.md) 22 | -------------------------------------------------------------------------------- /docs/docs/user-guides/index.md: -------------------------------------------------------------------------------- 1 | # Local dev overview 2 | 3 | Local development with Rwf benefits from the extensive Rust ecosystem, and makes some additions of its own, like [hot reloading](hot-reload.md) of frontend code. 4 | 5 | To make your development experience smoother, we recommend you install `cargo-watch` and `cargo-nextest`, like so: 6 | 7 | ```bash 8 | cargo install cargo-watch cargo-nextest 9 | ``` 10 | 11 | ## Watch for changes 12 | 13 | `cargo-watch` can monitor your code for changes and restart the server automatically. This makes local development much easier: as you make edits to your code, you don't have to stop and start the server manually: 14 | 15 | ```bash 16 | cargo watch --exec run 17 | ``` 18 | 19 | ### Hot reload 20 | 21 | Rwf can refresh pages automatically as they are being changed. If you enable [hot reload](hot-reload.md), and also use `cargo-watch`, make sure to tell it to ignore template changes: 22 | 23 | ```bash 24 | cargo watch --exec run --ignore "*.html" 25 | ``` 26 | 27 | ## Run tests 28 | 29 | Running tests with `cargo-nextest` is faster and more ergonomic as opposed to using built-in `cargo test`. If you end up writing tests for your app, you can run them all in parallel: 30 | 31 | ``` 32 | cargo nextest run 33 | ``` 34 | 35 | ## Learn more 36 | 37 | - [Hot reload](hot-reload.md) 38 | -------------------------------------------------------------------------------- /docs/docs/views/index.md: -------------------------------------------------------------------------------- 1 | # Views basics 2 | 3 | Rwf comes with a [templating](templates/index.md) library that can generate dynamic pages. Dynamic templates allow you to create unique HTML pages on the fly, and libraries like [Turbo](turbo/index.md) can use it in a way that feels like a native frontend application. 4 | 5 | ## What are views? 6 | 7 | Views are the **V** in MVC: they control what your users see and how they experience your web app. Separating views from [controllers](../controllers/index.md) allows controllers to reuse similar parts of your web app on different pages without code duplication. 8 | 9 | 10 | ## Using JavaScript frontends 11 | 12 | If you prefer to build your frontend with JavaScript libraries like React or Vue, take a look at Rwf's [REST](../controllers/REST/index.md) API documentation. Rwf templates are not required to build web applications. 13 | 14 | ## Learn more 15 | 16 | - [Templates](templates/index.md) 17 | - [Turbo](turbo/index.md) 18 | -------------------------------------------------------------------------------- /docs/docs/views/templates/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - 'index.md' 3 | - 'variables.md' 4 | - 'if-statements.md' 5 | - 'for-loops.md' 6 | - 'functions' 7 | - 'partials.md' 8 | - '...' 9 | - 'nomenclature.md' 10 | -------------------------------------------------------------------------------- /docs/docs/views/templates/for-loops.md: -------------------------------------------------------------------------------- 1 | # For loops 2 | 3 | Rwf templates have only one kind of for loop: for each. This allows writing more reliable templates, and to avoid common bugs like infinite loops, which will stall a web app in production. 4 | 5 | A for loop can iterate over a list of values, for example: 6 | 7 | ```erb 8 | 13 | ``` 14 | 15 | Template lists, unlike Rust's `Vec`, can hold [variables](variables.md) of different data types, and are dynamically evaluated at runtime: 16 | 17 | === "Template" 18 | ```erb 19 | <% for value in ["one", 2 * 5, 3/1.5] %> 20 | <%= value %> 21 | <% end %> 22 | ``` 23 | === "Output" 24 | ``` 25 | one 26 | 10 27 | 2.0 28 | ``` 29 | 30 | ## Do _n_ times 31 | 32 | If you need to execute some code multiple times, templates come with a handy `times` function: 33 | 34 | === "Template" 35 | ```erb 36 | <% for n in 5.times %> 37 |
  • <%= n %>.
  • 38 | <% end %> 39 | ``` 40 | === "Output" 41 | ``` 42 |
  • 1.
  • 43 |
  • 2.
  • 44 |
  • 3.
  • 45 |
  • 4.
  • 46 |
  • 5.
  • 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/docs/views/templates/functions/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - 'index.md' 3 | - 'string.md' 4 | - 'integer.md' 5 | - '...' 6 | -------------------------------------------------------------------------------- /docs/docs/views/templates/functions/float.md: -------------------------------------------------------------------------------- 1 | # Float functions 2 | 3 | ### `abs` 4 | 5 | Returns the absolute (non-negative) value of the floating point. 6 | 7 | === "Template" 8 | ```erb 9 | <%= -5.0.abs %> 10 | ``` 11 | === "Output" 12 | ``` 13 | 5.0 14 | ``` 15 | 16 | ### `to_string` 17 | 18 | Converts the floating point to a string. `to_s` is an alias for `to_string`. 19 | 20 | === "Template" 21 | ```erb 22 | <%= 5.2.to_string + " times" %> 23 | ``` 24 | === "Output" 25 | ``` 26 | 5.2 times 27 | ``` 28 | 29 | ### `to_integer` 30 | 31 | Converts the floating point to an integer, rounding it. `to_i` is an alias for `to_integer`. 32 | 33 | === "Template" 34 | ```erb 35 | <%= 5.2.to_integer * 5 %> 36 | ``` 37 | === "Output" 38 | ``` 39 | 25 40 | ``` 41 | 42 | ### `round` 43 | 44 | Rounds the floating point to the nearest whole value. 45 | 46 | === "Template" 47 | ```erb 48 | <%= 5.6.round %> 49 | ``` 50 | === "Output" 51 | ``` 52 | 6.0 53 | ``` 54 | 55 | ### `ceil` 56 | 57 | Rounds the float to the upper whole value. 58 | 59 | === "Template" 60 | ```erb 61 | <%= 5.2.ceil %> 62 | ``` 63 | === "Output" 64 | ``` 65 | 6.0 66 | ``` 67 | 68 | ### `floor` 69 | 70 | Rounds the float to the lower whole value. 71 | 72 | === "Template" 73 | ```erb 74 | <%= 5.9.ceil %> 75 | ``` 76 | === "Output" 77 | ``` 78 | 5.0 79 | ``` 80 | -------------------------------------------------------------------------------- /docs/docs/views/templates/functions/list.md: -------------------------------------------------------------------------------- 1 | # List functions 2 | 3 | ### `enumerate` 4 | 5 | Converts the list to a new list of tuples. Each tuple contains the element's position in the original list and its value. 6 | 7 | === "Template" 8 | ```erb 9 | <% for tuple in [1, 2, 3].enumerate %> 10 | <%= tuple.0 %>. <%= tuple.1 %> 11 | <% end %> 12 | ``` 13 | === "Output" 14 | ``` 15 | 0. 1 16 | 1. 2 17 | 2. 3 18 | ``` 19 | 20 | ### `reverse` 21 | 22 | Reverses the order of elements in the list. `rev` is an alias for `reverse`. 23 | 24 | === "Template" 25 | ```erb 26 | <% for value in [1, 2, 3].reverse %> 27 | <%= value %> 28 | <% end %> 29 | ``` 30 | === "Output" 31 | ``` 32 | 3 33 | 2 34 | 1 35 | ``` 36 | 37 | ### `len` 38 | 39 | Returns the length of the list. 40 | 41 | === "Template" 42 | ```erb 43 | <%= [1, 2, 3].len %> 44 | ``` 45 | === "Output" 46 | ``` 47 | 3 48 | ``` 49 | 50 | ### `empty` 51 | 52 | Returns true if the list is empty (length 0). 53 | 54 | === "Template" 55 | ```erb 56 | <%= [1, 2, 3].empty %> 57 | ``` 58 | === "Output" 59 | ``` 60 | false 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/docs/views/templates/index.md: -------------------------------------------------------------------------------- 1 | # Templates overview 2 | 3 | Dynamic templates are a mix of HTML and a programming language which directs how the HTML is displayed. For example, if you have a profile page for your web app users, you would want each of your users to have a page unique to them. To achieve this, you would write only one template and substitute unique aspects of each using template variables, for example: 4 | 5 | ```erb 6 |
    7 |

    <%= username %>

    8 |

    <%= bio %>

    9 |
    10 | ``` 11 | 12 | The variables `username` and `bio` can be substituted for values unique to each of your users, for example: 13 | 14 | === "Rust" 15 | ```rust 16 | use rwf::prelude::*; 17 | 18 | let template = Template::from_str(r#" 19 |
    20 |

    <%= username %>

    21 |

    <%= bio %>

    22 |
    23 | "#)?; 24 | 25 | let ctx = context!( 26 | "username" => "Alice", 27 | "bio" => "I like turtles" 28 | ); 29 | 30 | let html = template.render(&ctx)?; 31 | 32 | println!("{}", html); 33 | ``` 34 | === "Output" 35 | ```html 36 |
    37 |

    Alice

    38 |

    I like turtles

    39 |
    40 | ``` 41 | 42 | Templates help reuse HTML (and CSS, JavaScript) just like regular functions and structs help 43 | reuse code. 44 | 45 | ## Learn more 46 | 47 | - [Variables](variables.md) 48 | - [For loops](for-loops.md) 49 | - [If statements](if-statements.md) 50 | -------------------------------------------------------------------------------- /docs/docs/views/templates/nomenclature.md: -------------------------------------------------------------------------------- 1 | # Nomenclature 2 | 3 | ## Expressions 4 | 5 | Expressions are a combination of terms and operators which evaluate to a single value, for example: 6 | 7 | === "Template" 8 | ```erb 9 | <%= 5 * 2 / 10 %> 10 | ``` 11 | === "Output" 12 | ``` 13 | 1 14 | ``` 15 | 16 | ## Statements 17 | 18 | Statements are commands given to the template language interpreter to do something, e.g. output the value of an expression, or execute a for loop. 19 | -------------------------------------------------------------------------------- /docs/docs/views/turbo/.pages: -------------------------------------------------------------------------------- 1 | nav: 2 | - 'index.md' 3 | - 'streams.md' 4 | - '...' 5 | -------------------------------------------------------------------------------- /docs/docs/views/turbo/building-pages.md: -------------------------------------------------------------------------------- 1 | # Building pages 2 | 3 | Turbo can be used to update parts of the page, without having to render the entire page on every request. This is useful when you want to update sections of the page from any endpoint, without having to load several [partials](../templates/partials.md) or performing redirects. 4 | 5 | Partial updates uses Turbo Streams, a feature of Turbo that sends page updates via [forms](../../controllers/request.md#forms) or [WebSockets](../../controllers/websockets.md). 6 | -------------------------------------------------------------------------------- /docs/docs/views/turbo/index.md: -------------------------------------------------------------------------------- 1 | # Turbo basics 2 | 3 | [Hotwired Turbo](https://turbo.hotwired.dev/) is a JavaScript library that can intercept HTTP requests to your backend and perform updates to the frontend without reloading the browser page. The backend produces HTML, generated with [dynamic templates](../templates/index.md), and Turbo updates only the sections of the page that changed. This simulates the behavior of [Single-page applications](https://en.wikipedia.org/wiki/Single-page_application) (like the ones written with React or Vue) without using JavaScript on the frontend. 4 | 5 | ## Enabling Turbo 6 | 7 | If you're building pages using Rwf's [dynamic templates](../templates/index.md), you can enable Turbo by adding a declaration into the `` element of your pages: 8 | 9 | ```html 10 | 11 | 12 | <%= rwf_head %> 13 | 14 | 15 | ``` 16 | 17 | Otherwise, you can always get Turbo from a CDN, like [Skypack](https://www.skypack.dev/view/@hotwired/turbo). 18 | 19 | ## Using Turbo 20 | 21 | Once Turbo is loaded, all links and forms will use Turbo automatically. When visiting links or submitting forms, Turbo will intercept the request, send it on the browser's behalf, process the response and replace the contents of the page seamlessly. 22 | 23 | ## Learn more 24 | 25 | - [Turbo Streams](streams.md) 26 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Rust Web Framework 2 | repo_url: "https://github.com/levkk/rwf" 3 | site_url: "https://rustwebframework.org" 4 | extra_css: 5 | - style.css 6 | site_description: "Rust Web Framework - A comprehensive framework for building web applications with Rust" 7 | theme: 8 | name: material 9 | features: 10 | - content.code.copy 11 | palette: 12 | # Palette toggle for automatic mode 13 | - media: "(prefers-color-scheme)" 14 | toggle: 15 | icon: material/brightness-auto 16 | name: Switch to light mode 17 | 18 | # Palette toggle for light mode 19 | - media: "(prefers-color-scheme: light)" 20 | scheme: default 21 | primary: "white" 22 | toggle: 23 | icon: material/brightness-7 24 | name: Switch to dark mode 25 | 26 | # Palette toggle for dark mode 27 | - media: "(prefers-color-scheme: dark)" 28 | scheme: slate 29 | primary: "black" 30 | toggle: 31 | icon: material/brightness-4 32 | name: Switch to system preference 33 | docs_dir: docs 34 | markdown_extensions: 35 | - pymdownx.highlight: 36 | anchor_linenums: true 37 | line_spans: __span 38 | pygments_lang_class: true 39 | - pymdownx.inlinehilite 40 | - pymdownx.snippets 41 | - pymdownx.superfences 42 | - footnotes 43 | - admonition 44 | - pymdownx.details 45 | - pymdownx.superfences 46 | - pymdownx.tabbed: 47 | alternate_style: true 48 | plugins: 49 | - search 50 | - awesome-pages 51 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | babel==2.16.0 2 | bracex==2.5.post1 3 | certifi==2024.8.30 4 | charset-normalizer==3.4.0 5 | click==8.1.7 6 | colorama==0.4.6 7 | ghp-import==2.1.0 8 | idna==3.10 9 | Jinja2==3.1.4 10 | Markdown==3.7 11 | MarkupSafe==3.0.1 12 | mergedeep==1.3.4 13 | mkdocs==1.6.1 14 | mkdocs-awesome-pages-plugin==2.9.3 15 | mkdocs-get-deps==0.2.0 16 | mkdocs-material==9.5.41 17 | mkdocs-material-extensions==1.3.1 18 | mkdocs-terminal==4.6.0 19 | natsort==8.4.0 20 | packaging==24.1 21 | paginate==0.5.7 22 | pathspec==0.12.1 23 | platformdirs==4.3.6 24 | Pygments==2.18.0 25 | pymdown-extensions==10.11.2 26 | python-dateutil==2.9.0.post0 27 | PyYAML==6.0.2 28 | pyyaml_env_tag==0.1 29 | regex==2024.9.11 30 | requests==2.32.3 31 | six==1.16.0 32 | urllib3==2.2.3 33 | watchdog==5.0.3 34 | wcmatch==10.0 35 | -------------------------------------------------------------------------------- /examples/auth/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "auth" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"]} -------------------------------------------------------------------------------- /examples/auth/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | secret_key = "TRtZ2Ww4EeY3xfA82Bo9bNCQbkLiUZmiDO6wOE0W0qw=" -------------------------------------------------------------------------------- /examples/auth/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Rum authentication 5 | 6 | 12 | 13 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /examples/background-jobs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "background-jobs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"]} 11 | serde_json = "1" 12 | serde = { version = "1", features = ["derive"]} 13 | log = "*" -------------------------------------------------------------------------------- /examples/background-jobs/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | 3 | [database] 4 | name = "rwf_bg_jobs" -------------------------------------------------------------------------------- /examples/django/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "django" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf", features = ["wsgi"] } 10 | tokio = { version = "1", features = ["full"] } 11 | pyo3 = "0.22" 12 | tracing = "0.1" 13 | -------------------------------------------------------------------------------- /examples/django/README.md: -------------------------------------------------------------------------------- 1 | # Django + Rwf 2 | 3 | This application demonstrates how to run Django (and any other WSGI application) with Rwf. 4 | 5 | ## Quick start 6 | 7 | Create a virtual environment: 8 | 9 | ```bash 10 | python3 -m venv venv 11 | ``` 12 | 13 | Activate the venv and install dependencies: 14 | 15 | ```bash 16 | source venv/bin/activate 17 | pip install -r requirements.txt 18 | ``` 19 | 20 | Run the app: 21 | 22 | ``` 23 | cargo run 24 | ``` 25 | -------------------------------------------------------------------------------- /examples/django/requirements.txt: -------------------------------------------------------------------------------- 1 | django 2 | -------------------------------------------------------------------------------- /examples/django/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | host = "0.0.0.0" 3 | port = 8002 4 | -------------------------------------------------------------------------------- /examples/django/todo/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/django/todo/db.sqlite3 -------------------------------------------------------------------------------- /examples/django/todo/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /examples/django/todo/todo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/django/todo/todo/__init__.py -------------------------------------------------------------------------------- /examples/django/todo/todo/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for todo project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /examples/django/todo/todo/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for todo project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.1/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path 19 | 20 | from django.http import HttpResponse 21 | 22 | def index(request): 23 | if request.method == 'GET': 24 | return HttpResponse(""" 25 | 26 | 27 |

    This is served by Django

    28 |
    29 | 30 | 31 |
    32 | 33 | 34 | """) 35 | else: 36 | name = request.POST.get("name", "none") 37 | return HttpResponse("Name: " + name) 38 | 39 | urlpatterns = [ 40 | path('admin/', admin.site.urls), 41 | path('', index, name='index'), 42 | ] 43 | -------------------------------------------------------------------------------- /examples/django/todo/todo/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for todo project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'todo.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /examples/dynamic-templates/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynamic-templates" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"]} 11 | rand = "0.8" -------------------------------------------------------------------------------- /examples/engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "engine" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rwf = { path = "../../rwf" } 8 | tokio = { version = "1", features = ["full"] } 9 | -------------------------------------------------------------------------------- /examples/engine/src/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::{ 2 | controller::Engine, 3 | http::{self, Server}, 4 | prelude::*, 5 | }; 6 | 7 | #[derive(Default)] 8 | struct Index; 9 | 10 | #[async_trait] 11 | impl Controller for Index { 12 | async fn handle(&self, _request: &Request) -> Result { 13 | Ok(Response::new().text("Engine")) 14 | } 15 | } 16 | 17 | #[tokio::main] 18 | async fn main() -> Result<(), http::Error> { 19 | Logger::init(); 20 | 21 | let engine = Engine::new(vec![route!("/index" => Index)]); 22 | Server::new(vec![engine!("/engine" => engine)]) 23 | .launch() 24 | .await 25 | } 26 | -------------------------------------------------------------------------------- /examples/files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "files" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rwf = { path = "../../rwf" } 8 | tokio = { version = "1", features = ["full"] } 9 | -------------------------------------------------------------------------------- /examples/files/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/files/migrations/.gitkeep -------------------------------------------------------------------------------- /examples/files/src/controllers/mod.rs: -------------------------------------------------------------------------------- 1 | use rwf::prelude::*; 2 | 3 | #[derive(Default, macros::PageController)] 4 | pub struct Upload; 5 | 6 | #[async_trait] 7 | impl PageController for Upload { 8 | /// Upload page. 9 | async fn get(&self, req: &Request) -> Result { 10 | render!(req, "templates/upload.html") 11 | } 12 | 13 | /// Handle upload file. 14 | async fn post(&self, req: &Request) -> Result { 15 | let form_data = req.form_data()?; 16 | let comment = form_data.get_required::("comment")?; 17 | 18 | if let Some(file) = form_data.file("file") { 19 | render!(req, "templates/ok.html", 20 | "name" => file.name(), 21 | "size" => file.body().len() as i64, 22 | "content_type" => file.content_type(), 23 | "comment" => comment, 24 | 201); // 201 = created 25 | } else { 26 | Ok(Response::bad_request()) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/files/src/main.rs: -------------------------------------------------------------------------------- 1 | mod controllers; 2 | mod models; 3 | 4 | use rwf::http::{self, Server}; 5 | use rwf::prelude::*; 6 | 7 | #[tokio::main] 8 | async fn main() -> Result<(), http::Error> { 9 | Logger::init(); 10 | 11 | Server::new(vec![route!("/" => controllers::Upload)]) 12 | .launch() 13 | .await 14 | } 15 | -------------------------------------------------------------------------------- /examples/files/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/files/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/files/static/.gitkeep -------------------------------------------------------------------------------- /examples/files/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/files/templates/.gitkeep -------------------------------------------------------------------------------- /examples/files/templates/head.html: -------------------------------------------------------------------------------- 1 | 2 | <%= rwf_head() %> 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/files/templates/ok.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%% "templates/head.html" %> 4 | 5 |
    6 |

    Upload successful

    7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
    File nameFile sizeContent typeComment
    <%= name %><%= size %> bytes<%= content_type %><%= comment %>
    25 |
    26 | Back 27 |
    28 |
    29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/files/templates/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%% "templates/head.html" %> 4 | 5 |
    6 |

    Upload file

    7 |
    8 | <%= csrf_token() %> 9 |
    10 | 11 | 12 |
    13 | 14 |
    15 | 16 | 17 |
    18 | 19 |
    20 | 23 |
    24 |
    25 |
    26 | 27 | 28 | -------------------------------------------------------------------------------- /examples/middleware/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "middleware" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"]} -------------------------------------------------------------------------------- /examples/middleware/src/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::controller::middleware::prelude::*; 2 | use rwf::http::Server; 3 | use rwf::prelude::*; 4 | 5 | #[derive(Default)] 6 | struct BlockBadHeader; 7 | 8 | #[rwf::async_trait] 9 | impl Middleware for BlockBadHeader { 10 | async fn handle_request(&self, request: Request) -> Result { 11 | if let Some(value) = request.headers().get("x-user-id") { 12 | if let Ok(_id) = value.parse::() { 13 | return Ok(Outcome::Forward(request)); 14 | } 15 | } 16 | 17 | Ok(Outcome::Stop(request, Response::bad_request())) 18 | } 19 | } 20 | 21 | struct IndexController { 22 | middleware: MiddlewareSet, 23 | } 24 | 25 | #[rwf::async_trait] 26 | impl Controller for IndexController { 27 | fn middleware(&self) -> &MiddlewareSet { 28 | &self.middleware 29 | } 30 | 31 | async fn handle(&self, _request: &Request) -> Result { 32 | Ok(Response::new().text("You are allowed in!")) 33 | } 34 | } 35 | 36 | #[tokio::main] 37 | async fn main() -> Result<(), Error> { 38 | Logger::init(); 39 | 40 | Server::new(vec![IndexController { 41 | middleware: MiddlewareSet::new(vec![BlockBadHeader::default().middleware()]), 42 | } 43 | .route("/")]) 44 | .launch() 45 | .await?; 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /examples/orm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "orm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | default-run = "orm" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rwf = { path = "../../rwf" } 11 | tokio = { version = "1", features = ["full"]} 12 | time = "0.3" -------------------------------------------------------------------------------- /examples/orm/migrations/1_users.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS tasks; 2 | DROP TABLE IF EXISTS users; 3 | -------------------------------------------------------------------------------- /examples/orm/migrations/1_users.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id BIGSERIAL PRIMARY KEY, 3 | email VARCHAR NOT NULL UNIQUE, 4 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 5 | admin BOOLEAN NOT NULL DEFAULT false, 6 | completed_tasks BIGINT NOT NULL DEFAULT 0 7 | ); 8 | 9 | CREATE TABLE tasks ( 10 | id BIGSERIAL PRIMARY KEY, 11 | user_id BIGINT NOT NULL REFERENCES users(id), 12 | name VARCHAR NOT NULL, 13 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 14 | completed_at TIMESTAMPTZ 15 | ); 16 | -------------------------------------------------------------------------------- /examples/orm/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | log_queries = true 3 | 4 | [database] 5 | name = "rwf_orm" -------------------------------------------------------------------------------- /examples/orm/src/bin/migrate/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::prelude::*; 2 | 3 | #[tokio::main] 4 | async fn main() { 5 | Logger::init(); 6 | 7 | Migrations::migrate().await.expect("migrations failed"); 8 | } 9 | -------------------------------------------------------------------------------- /examples/quick-start/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "quick-start" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"]} -------------------------------------------------------------------------------- /examples/rails/.cargo/config: -------------------------------------------------------------------------------- 1 | [build] 2 | rustflags = ["-C", "link-args=-L/opt/homebrew/Cellar/ruby/3.3.4/lib"] 3 | -------------------------------------------------------------------------------- /examples/rails/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | config -------------------------------------------------------------------------------- /examples/rails/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | -------------------------------------------------------------------------------- /examples/rails/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rails" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rwf = { path = "../../rwf", features = ["rack"] } 8 | -------------------------------------------------------------------------------- /examples/rails/Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | # gem "rails" 6 | 7 | gem "rails", "~> 7.2" 8 | 9 | gem "bootsnap", "~> 1.18" 10 | -------------------------------------------------------------------------------- /examples/rails/src/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::controller::RackController; 2 | use rwf::http::{self, Server}; 3 | use rwf::prelude::*; 4 | 5 | #[tokio::main(flavor = "multi_thread")] 6 | async fn main() -> Result<(), http::Error> { 7 | Logger::init(); 8 | 9 | let controller = RackController::new("todo"); 10 | 11 | Server::new(vec![route!("/rust" => Index), controller.wildcard("/")]) 12 | .launch() 13 | .await 14 | } 15 | 16 | #[derive(Default)] 17 | struct Index; 18 | 19 | #[async_trait] 20 | impl Controller for Index { 21 | async fn handle(&self, _request: &Request) -> Result { 22 | Ok(Response::new().html("

    This is served by Rust!

    ")) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/rails/todo/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | /.gitignore 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all default key files. 15 | /config/master.key 16 | /config/credentials/*.key 17 | 18 | # Ignore all logfiles and tempfiles. 19 | /log/* 20 | /tmp/* 21 | !/log/.keep 22 | !/tmp/.keep 23 | 24 | # Ignore pidfiles, but keep the directory. 25 | /tmp/pids/* 26 | !/tmp/pids/.keep 27 | 28 | # Ignore storage (uploaded files in development and any SQLite databases). 29 | /storage/* 30 | !/storage/.keep 31 | /tmp/storage/* 32 | !/tmp/storage/.keep 33 | 34 | # Ignore assets. 35 | /node_modules/ 36 | /app/assets/builds/* 37 | !/app/assets/builds/.keep 38 | /public/assets 39 | 40 | # Ignore CI service files. 41 | /.github 42 | 43 | # Ignore development files 44 | /.devcontainer 45 | 46 | # Ignore Docker-related files 47 | /.dockerignore 48 | /Dockerfile* 49 | -------------------------------------------------------------------------------- /examples/rails/todo/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /examples/rails/todo/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /examples/rails/todo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # Temporary files generated by your text editor or operating system 4 | # belong in git's global ignore instead: 5 | # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore storage (uploaded files in development and any SQLite databases). 26 | /storage/* 27 | !/storage/.keep 28 | /tmp/storage/* 29 | !/tmp/storage/ 30 | !/tmp/storage/.keep 31 | 32 | /public/assets 33 | 34 | # Ignore master key for decrypting credentials and more. 35 | /config/master.key 36 | -------------------------------------------------------------------------------- /examples/rails/todo/.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Omakase Ruby styling for Rails 2 | inherit_gem: { rubocop-rails-omakase: rubocop.yml } 3 | 4 | # Overwrite or add rules to create your own house style 5 | # 6 | # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` 7 | # Layout/SpaceInsideArrayLiteralBrackets: 8 | # Enabled: false 9 | -------------------------------------------------------------------------------- /examples/rails/todo/.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.3.4 2 | -------------------------------------------------------------------------------- /examples/rails/todo/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | -------------------------------------------------------------------------------- /examples/rails/todo/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /examples/rails/todo/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | -------------------------------------------------------------------------------- /examples/rails/todo/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/app/assets/images/.keep -------------------------------------------------------------------------------- /examples/rails/todo/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /examples/rails/todo/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /examples/rails/todo/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /examples/rails/todo/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. 3 | allow_browser versions: :modern 4 | end 5 | -------------------------------------------------------------------------------- /examples/rails/todo/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /examples/rails/todo/app/controllers/todo_controller.rb: -------------------------------------------------------------------------------- 1 | class TodoController < ApplicationController 2 | skip_before_action :verify_authenticity_token 3 | 4 | def create 5 | puts params 6 | puts params[:hello] 7 | redirect_to "/" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /examples/rails/todo/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /examples/rails/todo/app/helpers/todo_helper.rb: -------------------------------------------------------------------------------- 1 | module TodoHelper 2 | end 3 | -------------------------------------------------------------------------------- /examples/rails/todo/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /examples/rails/todo/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /examples/rails/todo/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /examples/rails/todo/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/app/models/concerns/.keep -------------------------------------------------------------------------------- /examples/rails/todo/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= content_for(:title) || "Todo" %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | 12 | 13 | 14 | 15 | 16 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> 17 | 18 | 19 | 20 | <%= yield %> 21 | 22 | 23 | -------------------------------------------------------------------------------- /examples/rails/todo/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/rails/todo/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /examples/rails/todo/app/views/pwa/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Todo", 3 | "icons": [ 4 | { 5 | "src": "/icon.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | }, 9 | { 10 | "src": "/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "scope": "/", 19 | "description": "Todo.", 20 | "theme_color": "red", 21 | "background_color": "red" 22 | } 23 | -------------------------------------------------------------------------------- /examples/rails/todo/app/views/pwa/service-worker.js: -------------------------------------------------------------------------------- 1 | // Add a service worker for processing Web Push notifications: 2 | // 3 | // self.addEventListener("push", async (event) => { 4 | // const { title, options } = await event.data.json() 5 | // event.waitUntil(self.registration.showNotification(title, options)) 6 | // }) 7 | // 8 | // self.addEventListener("notificationclick", function(event) { 9 | // event.notification.close() 10 | // event.waitUntil( 11 | // clients.matchAll({ type: "window" }).then((clientList) => { 12 | // for (let i = 0; i < clientList.length; i++) { 13 | // let client = clientList[i] 14 | // let clientPath = (new URL(client.url)).pathname 15 | // 16 | // if (clientPath == event.notification.data.path && "focus" in client) { 17 | // return client.focus() 18 | // } 19 | // } 20 | // 21 | // if (clients.openWindow) { 22 | // return clients.openWindow(event.notification.data.path) 23 | // } 24 | // }) 25 | // ) 26 | // }) 27 | -------------------------------------------------------------------------------- /examples/rails/todo/bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /examples/rails/todo/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then 5 | export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)" 6 | fi 7 | 8 | # If running the rails server then create or migrate existing database 9 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 10 | ./bin/rails db:prepare 11 | fi 12 | 13 | exec "${@}" 14 | -------------------------------------------------------------------------------- /examples/rails/todo/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /examples/rails/todo/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /examples/rails/todo/bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /examples/rails/todo/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | APP_NAME = "todo" 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | 34 | # puts "\n== Configuring puma-dev ==" 35 | # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" 36 | # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" 37 | end 38 | -------------------------------------------------------------------------------- /examples/rails/todo/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /examples/rails/todo/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Todo 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 7.2 13 | 14 | # Please, add to the `ignore` list any other `lib` subdirectories that do 15 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 16 | # Common ones are `templates`, `generators`, or `middleware`, for example. 17 | config.autoload_lib(ignore: %w[assets tasks]) 18 | 19 | # Configuration for the application, engines, and railties goes here. 20 | # 21 | # These settings can be overridden in specific environments using the files 22 | # in config/environments, which are processed later. 23 | # 24 | # config.time_zone = "Central Time (US & Canada)" 25 | # config.eager_load_paths << Rails.root.join("extras") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /examples/rails/todo/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /examples/rails/todo/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: todo_production 11 | -------------------------------------------------------------------------------- /examples/rails/todo/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | VG/UrWpQbn13O80QailJkk5aWtnjoQFUN7OInjT7mnsGiTQVbxsnZ4i9HzRw4lR7HT3cjdItfiFRszqxQC1andUAHvY19f+5TRHVgCBTQDlGGs2EKotlRo0urxQrP7ZvooYKUcvXoci6tqLjNxuieooJ6WWNI2ZOqZ14cLO3qgKuRjiF521r0H1cl30KHgs4BofSTsJha9NmalMZYBH+hEvpyV7VHCUJm7quk/FoxI7PuEEG3Iors6qGhJXVizxtQacNzRTka/Pgj1yZXJqM38bgylPx5yDhMEWv5ocRnKWE64EHx2qUHlsxg064/qmmyYe/dtttL/s/hXi31pCJzgFlcH5BFhS34zr30ZbXc51AJkHjAKX/uQdpQHQkrO4caUNNJo5MmJaD1htSbYQ0M1PSbd5P--/3cXHqbQ6q0lXPvV--mKAOr64zucTnW41PfWHWQQ== -------------------------------------------------------------------------------- /examples/rails/todo/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | 24 | # SQLite3 write its data on the local filesystem, as such it requires 25 | # persistent disks. If you are deploying to a managed service, you should 26 | # make sure it provides disk persistence, as many don't. 27 | # 28 | # Similarly, if you deploy your application as a Docker container, you must 29 | # ensure the database is located in a persisted volume. 30 | production: 31 | <<: *default 32 | # database: path/to/persistent/storage/production.sqlite3 33 | -------------------------------------------------------------------------------- /examples/rails/todo/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /examples/rails/todo/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w[ admin.js admin.css ] 13 | -------------------------------------------------------------------------------- /examples/rails/todo/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /examples/rails/todo/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /examples/rails/todo/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /examples/rails/todo/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /examples/rails/todo/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /examples/rails/todo/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 3 | 4 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 5 | # Can be used by load balancers and uptime monitors to verify that the app is live. 6 | get "up" => "rails/health#show", as: :rails_health_check 7 | 8 | # Render dynamic PWA files from app/views/pwa/* 9 | get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker 10 | get "manifest" => "rails/pwa#manifest", as: :pwa_manifest 11 | 12 | resources :todo 13 | 14 | # Defines the root path route ("/") 15 | # root "posts#index" 16 | end 17 | -------------------------------------------------------------------------------- /examples/rails/todo/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /examples/rails/todo/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should ensure the existence of records required to run the application in every environment (production, 2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Example: 6 | # 7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 8 | # MovieGenre.find_or_create_by!(name: genre_name) 9 | # end 10 | -------------------------------------------------------------------------------- /examples/rails/todo/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/lib/assets/.keep -------------------------------------------------------------------------------- /examples/rails/todo/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/lib/tasks/.keep -------------------------------------------------------------------------------- /examples/rails/todo/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/log/.keep -------------------------------------------------------------------------------- /examples/rails/todo/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/public/icon.png -------------------------------------------------------------------------------- /examples/rails/todo/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /examples/rails/todo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /examples/rails/todo/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/storage/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] 5 | end 6 | -------------------------------------------------------------------------------- /examples/rails/todo/test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module ApplicationCable 4 | class ConnectionTest < ActionCable::Connection::TestCase 5 | # test "connects with cookies" do 6 | # cookies.signed[:user_id] = 42 7 | # 8 | # connect 9 | # 10 | # assert_equal connection.user_id, "42" 11 | # end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /examples/rails/todo/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/test/controllers/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/controllers/todo_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TodoControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /examples/rails/todo/test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/test/fixtures/files/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/test/helpers/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/test/integration/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/test/mailers/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/test/models/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/test/system/.keep -------------------------------------------------------------------------------- /examples/rails/todo/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | module ActiveSupport 6 | class TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /examples/rails/todo/tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/tmp/.keep -------------------------------------------------------------------------------- /examples/rails/todo/tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/tmp/pids/.keep -------------------------------------------------------------------------------- /examples/rails/todo/tmp/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/tmp/storage/.keep -------------------------------------------------------------------------------- /examples/rails/todo/vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/rails/todo/vendor/.keep -------------------------------------------------------------------------------- /examples/request-tracking/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "request-tracking" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"] } 11 | rand = "0.8" 12 | -------------------------------------------------------------------------------- /examples/request-tracking/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/request-tracking/migrations/.gitkeep -------------------------------------------------------------------------------- /examples/request-tracking/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | track_requests = true 3 | log_queries = true 4 | 5 | [database] 6 | name = "rwf_request_tracking" 7 | -------------------------------------------------------------------------------- /examples/request-tracking/src/controllers/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/request-tracking/src/main.rs: -------------------------------------------------------------------------------- 1 | mod controllers; 2 | mod models; 3 | 4 | use rand::Rng; 5 | use rwf::{ 6 | http::{self, Server}, 7 | prelude::*, 8 | }; 9 | 10 | #[derive(Default)] 11 | struct Index; 12 | 13 | #[async_trait] 14 | impl Controller for Index { 15 | async fn handle(&self, _request: &Request) -> Result { 16 | let ok = rand::thread_rng().gen::(); 17 | 18 | if ok { 19 | // This is tracked. 20 | Ok(Response::new().html(" 21 |

    All requests are tracked

    22 |

    To view requests, connect to the rwf_request_tracking database and run:

    23 | SELECT * FROM rwf_requests ORDER BY id 24 | ")) 25 | } else { 26 | // This is tracked also. 27 | Err(Error::HttpError(Box::new(http::Error::MissingParameter))) 28 | } 29 | } 30 | } 31 | 32 | #[tokio::main] 33 | async fn main() -> Result<(), http::Error> { 34 | Logger::init(); 35 | Migrations::migrate().await?; 36 | 37 | Server::new(vec![route!("/" => Index)]).launch().await 38 | } 39 | -------------------------------------------------------------------------------- /examples/request-tracking/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/request-tracking/templates/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/request-tracking/templates/.gitkeep -------------------------------------------------------------------------------- /examples/rest/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rest" 3 | version = "0.1.0" 4 | edition = "2021" 5 | default-run = "rest" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | [[bin]] 9 | name = "migrations" 10 | path = "bin/migrations/main.rs" 11 | 12 | [dependencies] 13 | rwf = { path = "../../rwf" } 14 | serde = { version = "1", features = ["derive"]} 15 | time = { version = "*", features = ["serde"] } 16 | tokio = { version = "1", features = ["full"]} 17 | serde_json = "1" -------------------------------------------------------------------------------- /examples/rest/bin/migrations/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::logging::Logger; 2 | use rwf::model::{Error, Migrations}; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), Error> { 6 | // Enable logging. 7 | Logger::init(); 8 | 9 | // Run migrations. 10 | Migrations::migrate().await?; 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /examples/rest/migrations/1_users.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE users; -------------------------------------------------------------------------------- /examples/rest/migrations/1_users.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id BIGSERIAL PRIMARY KEY, 3 | email VARCHAR NOT NULL, 4 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 5 | ); 6 | -------------------------------------------------------------------------------- /examples/rest/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | secret_key = "TRtZ2Ww4EeY3xfA82Bo9bNCQbkLiUZmiDO6wOE0W0qw=" 3 | 4 | [database] 5 | name = "rwf_rest" -------------------------------------------------------------------------------- /examples/rest/src/rest.rs: -------------------------------------------------------------------------------- 1 | use rwf::prelude::*; 2 | 3 | #[derive(rwf::macros::RestController, Default)] 4 | pub struct MyController; 5 | 6 | #[rwf::async_trait] 7 | impl RestController for MyController { 8 | type Resource = i64; // Use integers as the resource identifiers. 9 | // Can be any other data type that implements `rwf::controller::ToParameter` trait. 10 | 11 | /// GET / 12 | async fn list(&self, _request: &Request) -> Result { 13 | let result = serde_json::json!([ 14 | {"id": 5, "email": "test@test.com"}, 15 | {"id": 7, "email": "hello@test.com"}, 16 | ]); 17 | 18 | Ok(Response::new().json(result)?) 19 | } 20 | 21 | /// GET /:id 22 | async fn get(&self, _request: &Request, id: &Self::Resource) -> Result { 23 | let result = serde_json::json!({ 24 | "id": *id, 25 | "email": "guest@test.com", 26 | }); 27 | 28 | Ok(Response::new().json(result)?) 29 | } 30 | 31 | // All other methods will return HTTP 501. 32 | } 33 | -------------------------------------------------------------------------------- /examples/rest/src/secure.rs: -------------------------------------------------------------------------------- 1 | use rwf::controller::middleware::{prelude::*, SecureId}; 2 | use rwf::prelude::*; 3 | use serde::{Deserialize, Serialize}; 4 | use time::OffsetDateTime; 5 | 6 | #[derive(Clone, Serialize, Deserialize, rwf::macros::Model)] 7 | pub struct User { 8 | #[serde(with = "rwf::controller::ser::secure_id", default, skip_deserializing)] 9 | id: Option, 10 | 11 | email: String, 12 | 13 | #[serde(with = "time::serde::iso8601", default = "OffsetDateTime::now_utc")] 14 | created_at: OffsetDateTime, 15 | } 16 | 17 | pub struct SecureUserController { 18 | middleware: MiddlewareSet, 19 | } 20 | 21 | impl SecureUserController { 22 | pub fn default() -> SecureUserController { 23 | SecureUserController { 24 | middleware: MiddlewareSet::new(vec![SecureId::default().middleware()]), 25 | } 26 | } 27 | } 28 | 29 | #[rwf::async_trait] 30 | impl Controller for SecureUserController { 31 | fn middleware(&self) -> &MiddlewareSet { 32 | &self.middleware 33 | } 34 | 35 | /// Make the ModelController handle the request. 36 | /// This is required because Rust traits call the base trait method 37 | /// if it has a default implementation. 38 | async fn handle(&self, request: &Request) -> Result { 39 | ModelController::handle(self, request).await 40 | } 41 | } 42 | 43 | impl ModelController for SecureUserController { 44 | type Model = User; 45 | } 46 | -------------------------------------------------------------------------------- /examples/scheduled-jobs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scheduled-jobs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"]} 11 | serde = "1" 12 | serde_json = "1" -------------------------------------------------------------------------------- /examples/scheduled-jobs/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Scheduled jobs 3 | 4 | Scheduled jobs are [background jobs](../background-jobs) that run automatically based on a schedule, typically defined in the form of a cron: 5 | 6 | ```rust 7 | let daily_email = SendEmail { 8 | email: "boss@hello.com".into(), 9 | body: "I'm running 5 minutes late, the bus is delayed again.".into(), 10 | }; 11 | 12 | let scheduled_job = SendEmail::default() 13 | .schedule( 14 | serde_json::to_value(&daily_email)?, 15 | "0 0 9 * * *", 16 | ); 17 | 18 | Worker::new(vec![SendEmail::default().job(),]) 19 | .clock(vec![scheduled_job,]) 20 | .start() 21 | .await?; 22 | ``` 23 | 24 | ## Cron format 25 | 26 | The cron accepts the standard Unix cron format. Up to second precision is allowed (6 stars for every second), with 5 being the minimum (every minute). Non-standard extensions, like `@yearly` are not currently supported, but a PR is welcome. 27 | 28 | ## Clock ticks 29 | 30 | The scheduler runs every second. If a job is available, it will execute it and immediately (without waiting for the next tick) fetch the next available job from then queue. If no more jobs are available, the scheduler will go back to polling the queue once a second. 31 | 32 | ## Timezone 33 | 34 | The clock runs on the UTC timezone (+00:00 / GMT). -------------------------------------------------------------------------------- /examples/scheduled-jobs/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | 3 | [database] 4 | name = "rwf_bg_jobs" 5 | -------------------------------------------------------------------------------- /examples/scheduled-jobs/src/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::job::{Error as JobError, Job, Worker}; 2 | use rwf::prelude::*; 3 | 4 | use serde::{Deserialize, Serialize}; 5 | use tokio::time::sleep; 6 | 7 | #[derive(Clone, Serialize, Deserialize, Default)] 8 | struct MyJob; 9 | 10 | #[rwf::async_trait] 11 | impl Job for MyJob { 12 | async fn execute(&self, _args: serde_json::Value) -> Result<(), JobError> { 13 | sleep(std::time::Duration::from_secs(1)).await; 14 | Ok(()) 15 | } 16 | } 17 | 18 | #[tokio::main] 19 | async fn main() -> Result<(), Error> { 20 | Logger::init(); 21 | 22 | Migrations::migrate().await?; 23 | 24 | Worker::new(vec![MyJob::default().job()]) 25 | .clock(vec![ 26 | MyJob::default().schedule(serde_json::Value::Null, "*/5 * * * * *")? 27 | ]) 28 | .start() 29 | .await?; 30 | 31 | sleep(std::time::Duration::MAX).await; 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /examples/static-files/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "static-files" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../../rwf" } 10 | tokio = { version = "1", features = ["full"]} -------------------------------------------------------------------------------- /examples/static-files/src/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::controller::StaticFiles; 2 | use rwf::http::Server; 3 | use rwf::prelude::*; 4 | 5 | #[tokio::main] 6 | async fn main() -> Result<(), Error> { 7 | Logger::init(); 8 | 9 | Server::new(vec![StaticFiles::serve("static")?]) 10 | .launch() 11 | .await?; 12 | 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /examples/static-files/static/hello.txt: -------------------------------------------------------------------------------- 1 | hello from Rum! -------------------------------------------------------------------------------- /examples/turbo/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "turbo" 3 | version = "0.1.0" 4 | edition = "2021" 5 | default-run = "turbo" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | rwf = { path = "../../rwf" } 11 | tokio = { version = "1", features = ["full"]} 12 | tracing = "*" 13 | rand = "0.8" 14 | serde_json = "1" 15 | serde = "1" 16 | time = "0.3" 17 | rwf-admin = { path = "../../rwf-admin" } 18 | -------------------------------------------------------------------------------- /examples/turbo/Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the app in a separate container. 2 | FROM rust:1-bullseye AS builder 3 | COPY . /build 4 | WORKDIR /build 5 | RUN cargo build --release 6 | 7 | # This container will run in production, without 8 | # intermediate build artifacts. 9 | FROM debian:bullseye 10 | COPY --from=builder /build/target/release/turbo /app/turbo 11 | 12 | # Copy assets 13 | COPY templates /app/templates 14 | COPY migrations /app/migrations 15 | COPY static /app/static 16 | 17 | WORKDIR /app 18 | CMD ["turbo"] 19 | -------------------------------------------------------------------------------- /examples/turbo/README.md: -------------------------------------------------------------------------------- 1 | # Rwf + Turbo 2 | 3 | ## Quick start 4 | 5 | Create the PostgreSQL database: 6 | 7 | ``` 8 | createdb rwf_turbo 9 | ``` 10 | 11 | Run the app: 12 | 13 | ``` 14 | cargo run 15 | ``` 16 | 17 | ## Concepts 18 | 19 | This app demonstrates: 20 | 21 | 1. Tight integration of Rwf with Turbo 22 | 2. WebSockets 23 | 3. Stimulus controllers 24 | 4. Rwf controllers 25 | 5. Migrations 26 | 6. Querying the database using the ORM 27 | 28 | And how everything ties together to build a Single Page Application. -------------------------------------------------------------------------------- /examples/turbo/migrations/1728779072_users.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS users; -------------------------------------------------------------------------------- /examples/turbo/migrations/1728779072_users.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id BIGSERIAL PRIMARY KEY, 3 | name VARCHAR NOT NULL UNIQUE, 4 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 5 | ); -------------------------------------------------------------------------------- /examples/turbo/migrations/1728836660_messages.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS chat_messages; -------------------------------------------------------------------------------- /examples/turbo/migrations/1728836660_messages.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE chat_messages ( 2 | id BIGSERIAL PRIMARY KEY, 3 | user_id BIGINT NOT NULL, 4 | body VARCHAR NOT NULL DEFAULT '', 5 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() 6 | ) -------------------------------------------------------------------------------- /examples/turbo/migrations/1729119889028371278_unnamed.down.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/turbo/migrations/1729119889028371278_unnamed.down.sql -------------------------------------------------------------------------------- /examples/turbo/migrations/1729119889028371278_unnamed.up.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/examples/turbo/migrations/1729119889028371278_unnamed.up.sql -------------------------------------------------------------------------------- /examples/turbo/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | secret_key = "TRtZ2Ww4EeY3xfA82Bo9bNCQbkLiUZmiDO6wOE0W0qw=" 3 | log_queries = true 4 | cache_templates = false 5 | track_requests = true 6 | 7 | [database] 8 | name = "rwf_turbo" 9 | idle_timeout = 1000 10 | -------------------------------------------------------------------------------- /examples/turbo/src/bin/run_migrations/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::prelude::*; 2 | use std::env::args; 3 | 4 | #[tokio::main] 5 | async fn main() { 6 | Logger::init(); 7 | 8 | let mut args = args(); 9 | 10 | if let Some(direction) = args.nth(1) { 11 | match direction.as_str() { 12 | "flush" => Migrations::flush().await.expect("flush failed"), 13 | _ => Migrations::migrate().await.expect("migrations failed"), 14 | }; 15 | } else { 16 | Migrations::migrate().await.expect("migrations failed"); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/turbo/src/controllers/chat/form.rs: -------------------------------------------------------------------------------- 1 | #[derive(rwf::macros::Form)] 2 | pub struct MessageForm { 3 | pub body: String, 4 | } 5 | -------------------------------------------------------------------------------- /examples/turbo/src/controllers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod chat; 2 | pub mod signup; 3 | 4 | pub use chat::typing::*; 5 | pub use chat::*; 6 | pub use signup::*; 7 | -------------------------------------------------------------------------------- /examples/turbo/src/controllers/signup/form.rs: -------------------------------------------------------------------------------- 1 | #[derive(rwf::macros::Form)] 2 | pub struct SignupForm { 3 | pub name: String, 4 | } 5 | -------------------------------------------------------------------------------- /examples/turbo/src/controllers/signup/middleware.rs: -------------------------------------------------------------------------------- 1 | use rwf::controller::middleware::prelude::*; 2 | 3 | // If a user is already logged in, redirect them to the chat page. 4 | #[derive(Default)] 5 | pub struct LoggedInCheck; 6 | 7 | #[rwf::async_trait] 8 | impl Middleware for LoggedInCheck { 9 | async fn handle_request(&self, request: Request) -> Result { 10 | if request.session().authenticated() { 11 | return Ok(Outcome::Stop(request, Response::new().redirect("/chat"))); 12 | } 13 | 14 | Ok(Outcome::Forward(request)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/turbo/src/models/chat_message.rs: -------------------------------------------------------------------------------- 1 | //! Chat message. 2 | use super::User; 3 | use time::OffsetDateTime; 4 | 5 | #[derive(Clone, rwf::macros::Model)] 6 | #[belongs_to(User)] 7 | pub struct ChatMessage { 8 | pub id: Option, 9 | pub user_id: i64, 10 | pub body: String, 11 | pub created_at: OffsetDateTime, 12 | } 13 | -------------------------------------------------------------------------------- /examples/turbo/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod chat_message; 2 | pub mod user; 3 | 4 | pub use chat_message::ChatMessage; 5 | pub use user::User; 6 | -------------------------------------------------------------------------------- /examples/turbo/src/models/user.rs: -------------------------------------------------------------------------------- 1 | //! A chat user. 2 | 3 | use super::ChatMessage; 4 | use time::OffsetDateTime; 5 | 6 | #[derive(Clone, rwf::macros::Model)] 7 | #[has_many(ChatMessage)] 8 | pub struct User { 9 | pub id: Option, 10 | pub name: String, 11 | pub created_at: OffsetDateTime, 12 | } 13 | -------------------------------------------------------------------------------- /examples/turbo/static/js/form_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "hotwired/stimulus"; // see header.html for importmap 2 | 3 | export default class extends Controller { 4 | connect() { 5 | window.scrollTo(0, document.body.scrollHeight); 6 | } 7 | 8 | typingStart() { 9 | if (this.startedTyping) { 10 | clearTimeout(this.typingStartTimeout); 11 | } else { 12 | this.startedTyping = true; 13 | } 14 | 15 | this.typingStartTimeout = setTimeout(() => { 16 | this.typing(true); 17 | 18 | clearTimeout(this.typingStopTimeout); 19 | this.typingStopTimeout = setTimeout(() => { 20 | this.typing(false); 21 | this.startedTyping = false; 22 | }, 3000); 23 | }, 300); 24 | } 25 | 26 | typing(typing) { 27 | const csrf_token = this.element.dataset.csrfToken; 28 | fetch("/chat/typing", { 29 | method: "POST", 30 | headers: { 31 | "Content-Type": "application/json", 32 | "X-CSRF-Token": csrf_token, 33 | }, 34 | body: JSON.stringify({ 35 | typing, 36 | }), 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/turbo/static/js/main.js: -------------------------------------------------------------------------------- 1 | import { Application } from 'hotwired/stimulus' 2 | import FormController from '/static/js/form_controller.js' 3 | 4 | const application = Application.start() 5 | application.register('form', FormController) -------------------------------------------------------------------------------- /examples/turbo/templates/chat.html: -------------------------------------------------------------------------------- 1 | <%% "templates/header.html" %> 2 | 3 |
    4 |
    5 | <% for message in messages %> 6 | <%% "templates/chat_message.html" %> 7 | <% end %> 8 |
    9 |
    10 |
    11 |
    12 |
    13 |
    14 |
    15 |
    16 |
    17 |
    18 |
    19 | <%% "templates/chat_form.html" %> 20 |
    21 |
    22 |
    23 | 24 | <%% "templates/footer.html" %> 25 | -------------------------------------------------------------------------------- /examples/turbo/templates/chat_form.html: -------------------------------------------------------------------------------- 1 |
    8 | <%= csrf_token() %> 9 |
    10 | 18 |
    19 | 20 |
    21 | 22 |
    23 |
    24 | -------------------------------------------------------------------------------- /examples/turbo/templates/chat_message.html: -------------------------------------------------------------------------------- 1 |
    2 | <% if message.mine %> 3 |
    4 | <% else %> 5 |
    6 | <% end %> 7 |
    8 | <% if message.mine %> 9 |
    10 | <% else %> 11 |
    12 | <% end %> 13 |
    14 |
    15 |
    <%= message.user.name %>
    16 |

    <%= message.message.created_at %>

    17 |
    18 |
    19 |
    20 |

    <%= message.message.body.br %>

    21 |
    22 |
    23 |
    24 |
    25 | -------------------------------------------------------------------------------- /examples/turbo/templates/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /examples/turbo/templates/header.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= rwf_head() %> 5 | 9 | <% if title %> 10 | <%= title %> 11 | <% end %> 12 | 13 | 14 | 15 | 16 | 17 | <%= rwf_turbo_stream("/turbo-stream") %> 18 | -------------------------------------------------------------------------------- /examples/turbo/templates/signup.html: -------------------------------------------------------------------------------- 1 | <%% "templates/header.html" %> 2 | 3 |
    4 |
    5 |
    6 |
    7 |
    8 | <%= csrf_token() %> 9 |
    10 | 11 | 12 |
    13 | 14 | 15 |
    16 | 17 |
    18 |
    19 |
    20 |
    21 |
    22 | 23 | <%% "templates/footer.html" %> 24 | -------------------------------------------------------------------------------- /examples/turbo/templates/typing.html: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | <%= user.name %> is typing... 4 |
    5 |
    -------------------------------------------------------------------------------- /examples/users/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "users" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rwf = { path = "../../rwf" } 8 | time = "0.3" 9 | argon2 = "0.5" 10 | -------------------------------------------------------------------------------- /examples/users/migrations/1733265254409864495_users.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE users; 2 | -------------------------------------------------------------------------------- /examples/users/migrations/1733265254409864495_users.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id BIGSERIAL PRIMARY KEY, 3 | email VARCHAR NOT NULL UNIQUE, 4 | password VARCHAR NOT NULL, 5 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW () 6 | ); 7 | -------------------------------------------------------------------------------- /examples/users/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | secret_key = "9Sk2t2G40QC3QrdVr6e6RzYAKJLGTFjpDiRrmA7eGQk=" 3 | log_queries = true 4 | 5 | [database] 6 | name = "rwf_users" 7 | -------------------------------------------------------------------------------- /examples/users/src/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::{http::Server, prelude::*}; 2 | 3 | mod controllers; 4 | mod models; 5 | 6 | #[tokio::main] 7 | async fn main() { 8 | Logger::init(); 9 | 10 | Server::new(vec![ 11 | route!("/signup" => controllers::Signup), 12 | route!("/login" => controllers::login), 13 | route!("/profile" => controllers::profile), 14 | ]) 15 | .launch() 16 | .await 17 | .unwrap(); 18 | } 19 | -------------------------------------------------------------------------------- /examples/users/templates/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= rwf_head() %> 5 | -------------------------------------------------------------------------------- /examples/users/templates/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%% "templates/head.html" %> 5 | 6 | 7 |
    8 |

    Profile

    9 |
    10 | 11 |
    12 |
    13 |
    14 |
    15 | <%= user.email %> 16 |
    17 |
    18 | 19 |
    20 |
    21 |
    22 |
    23 |
    24 | 25 | 26 | -------------------------------------------------------------------------------- /rwf-admin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwf-admin" 3 | version = "0.1.12" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Admin panel for applications written with the Rust Web Framework" 7 | homepage = "https://levkk.github.io/rwf/" 8 | repository = "https://github.com/levkk/rwf" 9 | keywords = ["mvc", "web", "framework", "admin"] 10 | authors = ["Lev Kokotov "] 11 | include = ["/templates", "/src", "/static"] 12 | readme = "README.md" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | rwf = { path = "../rwf", version = ">=0.1.11" } 18 | tokio = { version = "1", features = ["full"] } 19 | serde = { version = "1", features = ["derive"] } 20 | serde_json = "1" 21 | time = { version = "0.3", features = [ 22 | "formatting", 23 | "serde", 24 | "parsing", 25 | "macros", 26 | ] } 27 | once_cell = "1" 28 | uuid = { version = "1", features = ["v4"] } 29 | -------------------------------------------------------------------------------- /rwf-admin/README.md: -------------------------------------------------------------------------------- 1 | # Rwf admin 2 | 3 | [![Documentation](https://img.shields.io/badge/documentation-blue?style=flat)](https://levkk.github.io/rwf/) 4 | [![Latest crate](https://img.shields.io/crates/v/rwf-admin.svg)](https://crates.io/crates/rwf-admin) 5 | [![Reference docs](https://img.shields.io/docsrs/rwf-admin)](https://docs.rs/rwf-admin) 6 | 7 | [Rwf](https://crates.io/crates/rwf) admin panel is a web application that provides a real time overview into web activity, background jobs queue insights, and allows to manipulate database models. 8 | 9 | The admin panel can run as a standalone application or be integrated into an existing Rwf application. 10 | 11 | ## Installation 12 | 13 | To install Rwf admin panel into your application, you need to add it to your routes and preload its templates at application startup: 14 | 15 | ```rust 16 | use rwf::prelude::*; 17 | use rwf::http::{Server, Error}; 18 | 19 | #[tokio::main] 20 | async fn main() -> Result<(), Error> { 21 | rwf_admin::install()?; 22 | 23 | let mut routes = vec![]; 24 | // Add your routes... 25 | 26 | routes.extend(rwf_admin::routes()); 27 | 28 | Server::new(routes) 29 | .launch() 30 | .await 31 | } 32 | ``` 33 | 34 | The admin panel is now running on [https://localhost:8000/admin/](https://localhost:8000/admin/). 35 | -------------------------------------------------------------------------------- /rwf-admin/rwf.toml: -------------------------------------------------------------------------------- 1 | [general] 2 | track_requests = true 3 | log_queries = true 4 | 5 | [database] 6 | name = "rwf_admin" 7 | -------------------------------------------------------------------------------- /rwf-admin/src/controllers/index.rs: -------------------------------------------------------------------------------- 1 | use rwf::prelude::*; 2 | 3 | #[derive(Default)] 4 | pub struct Index; 5 | 6 | #[async_trait] 7 | impl Controller for Index { 8 | async fn handle(&self, _request: &Request) -> Result { 9 | Ok(Response::new().redirect("jobs")) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rwf-admin/src/controllers/mod.rs: -------------------------------------------------------------------------------- 1 | // This file is automatically generated by rwf-cli. 2 | // Manual modifications to this file will not be preserved. 3 | pub mod index; 4 | pub mod jobs; 5 | pub mod models; 6 | pub mod requests; 7 | -------------------------------------------------------------------------------- /rwf-admin/src/controllers/requests.rs: -------------------------------------------------------------------------------- 1 | use crate::models::{RequestByCode, RequestsDuration}; 2 | use rwf::prelude::*; 3 | 4 | #[derive(Default)] 5 | pub struct Requests; 6 | 7 | #[async_trait] 8 | impl Controller for Requests { 9 | async fn handle(&self, request: &Request) -> Result { 10 | let minutes = request.query().get::("minutes").unwrap_or(60); 11 | let requests = { 12 | let mut conn = Pool::connection().await?; 13 | RequestByCode::count(minutes).fetch_all(&mut conn).await? 14 | }; 15 | 16 | let duration = { 17 | let mut conn = Pool::connection().await?; 18 | RequestsDuration::count(minutes) 19 | .fetch_all(&mut conn) 20 | .await? 21 | }; 22 | 23 | let requests = serde_json::to_string(&requests)?; 24 | let duration = serde_json::to_string(&duration)?; 25 | 26 | render!(request, "templates/rwf_admin/requests.html", 27 | "title" => "Requests | Rust Web Framework", 28 | "requests" => requests, 29 | "duration" => duration, 30 | "interval" => match minutes { 31 | 60 => "Last hour".into(), 32 | 180 => "Last 3 hours".into(), 33 | 1440 => "Last 24 hours".into(), 34 | m => format!("Last {} minutes", m) 35 | } 36 | ) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /rwf-admin/src/main.rs: -------------------------------------------------------------------------------- 1 | use rwf::controller::BasicAuth; 2 | use rwf::{ 3 | controller::{StaticFiles, TurboStream}, 4 | http::{self, Server}, 5 | prelude::*, 6 | }; 7 | use std::path::PathBuf; 8 | 9 | #[derive(Default)] 10 | struct Redirect; 11 | 12 | #[async_trait] 13 | impl Controller for Redirect { 14 | async fn handle(&self, _: &Request) -> Result { 15 | Ok(Response::new().redirect("/admin/")) 16 | } 17 | } 18 | 19 | #[tokio::main] 20 | async fn main() -> Result<(), http::Error> { 21 | Logger::init(); 22 | Migrations::migrate().await?; 23 | 24 | // Enable HMR. 25 | rwf::hmr::hmr(PathBuf::from("templates")); 26 | 27 | // Basic auth is just an example, it's not secure. I would recommend using SessionAuth 28 | // and checking that the user is an admin using an internal check. 29 | let admin = rwf_admin::engine().auth(AuthHandler::new(BasicAuth { 30 | user: "admin".to_string(), 31 | password: "admin".to_string(), 32 | })); 33 | 34 | Server::new(vec![ 35 | route!("/" => Redirect), 36 | engine!("/admin" => admin), 37 | route!("/turbo-stream" => TurboStream), 38 | StaticFiles::serve("static")?, 39 | ]) 40 | .launch() 41 | .await 42 | } 43 | -------------------------------------------------------------------------------- /rwf-admin/static/rwf_admin/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /rwf-admin/static/rwf_admin/js/reload_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "hotwired/stimulus"; 2 | 3 | export default class extends Controller { 4 | reload() { 5 | Turbo.visit(window.location.href, { action: "replace" }); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/footer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <% if title %><%= title %><% end %> 6 | 7 | <%- rwf_head() %> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 26 | 27 | 28 | <%- rwf_turbo_stream("/turbo-stream") %> 29 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/model.html: -------------------------------------------------------------------------------- 1 | <%% "templates/rwf_admin/head.html" %> 2 | <%% "templates/rwf_admin/nav.html" %> 3 | 4 |
    5 | 6 | <% for name in [table_name.camelize] %> 7 | <%% "templates/rwf_admin/reload.html" %> 8 | <% end %> 9 | 10 |
    11 | <%% "templates/rwf_admin/model_pages.html" %> 12 | 13 |
    14 | 15 | 16 | 17 | <% for column in selected_columns %> 18 | 19 | <% end %> 20 | 21 | 22 | 23 | <% for row in rows %> 24 | 25 | <% for column in selected_columns %> 26 | <% if row[column] %> 27 | 28 | <% else %> 29 | 30 | <% end %> 31 | <% end %> 32 | 33 | <% end %> 34 | 35 |
    <%= column %>
    <%= row[column] %>null
    36 |
    37 | <% if rows.len > 15 %> 38 | <%% "templates/rwf_admin/model_pages.html" %> 39 | <% end %> 40 |
    41 |
    42 | 43 | <%% "templates/rwf_admin/footer.html" %> 44 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/model_pages.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/models.html: -------------------------------------------------------------------------------- 1 | <%% "templates/rwf_admin/head.html" %> 2 | <%% "templates/rwf_admin/nav.html" %> 3 | 4 |
    5 | <% for name in ["models"] %> 6 | <%% "templates/rwf_admin/reload.html" %> 7 | <% end %> 8 |
    9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | <% for model in models %> 17 | 18 | 23 | 24 | <% end %> 25 | 26 |
    Model
    19 | 20 | <%= model.table_name.camelize %> 21 | 22 |
    27 |
    28 |
    29 | 30 | <%% "templates/rwf_admin/footer.html" %> 31 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/nav.html: -------------------------------------------------------------------------------- 1 | 19 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/reload.html: -------------------------------------------------------------------------------- 1 |
    5 |

    6 | 7 | <% if name == "jobs" %> 8 | work_history 9 | <% elsif name == "requests" %> 10 | equalizer 11 | <% else %> 12 | database 13 | <% end %> 14 | 15 | <%= name.capitalize %> 16 |

    17 |
    18 | 27 | 28 | <% if name != "models" && name != "requests" && name != "jobs" %> 29 | 30 | 31 | add 32 | 33 | New 34 | 35 | <% end %> 36 |
    37 |
    38 | -------------------------------------------------------------------------------- /rwf-admin/templates/rwf_admin/requests.html: -------------------------------------------------------------------------------- 1 | <%% "templates/rwf_admin/head.html" %> 2 | <%% "templates/rwf_admin/nav.html" %> 3 | 4 |
    5 | <% for name in ["requests"] %> 6 | <%% "templates/rwf_admin/reload.html" %> 7 | <% end %> 8 |
    9 | 19 |
    20 |
    21 | 22 | 25 | 28 |
    29 |
    30 | 31 | <%% "templates/rwf_admin/footer.html" %> 32 | -------------------------------------------------------------------------------- /rwf-cli/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /rwf-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwf-cli" 3 | version = "0.1.14" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Rust Web Framework CLI" 7 | readme = "README.md" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | clap = { version = "4.5.18", features = ["derive"] } 13 | rwf = { path = "../rwf", version = "0.2" } 14 | tokio = { version = "1", features = ["full"] } 15 | log = "0.4" 16 | time = "0.3" 17 | regex = "1" 18 | toml = "0.8" 19 | flate2 = "1" 20 | tar = "0.4" 21 | serde_json = "1" 22 | which = "7" 23 | -------------------------------------------------------------------------------- /rwf-cli/README.md: -------------------------------------------------------------------------------- 1 | # Rwf CLI 2 | 3 | [![Documentation](https://img.shields.io/badge/documentation-blue?style=flat)](https://levkk.github.io/rwf/) 4 | [![Latest crate](https://img.shields.io/crates/v/rwf-cli.svg)](https://crates.io/crates/rwf-cli) 5 | 6 | [Rwf](https://levkk.github.io/rwf/) comes with its own command-line tool that helps managing projects. The CLI can generate code for controllers, models, and migrations, and 7 | deploy Rwf-powered applications to production. 8 | 9 | ## Installation 10 | 11 | You can install `rwf-cli` using `cargo`: 12 | 13 | ``` 14 | $ cargo install rwf-cli 15 | ``` 16 | 17 | The CLI should now be available globally. If not, make sure that `~/.cargo/bin/` is in your `PATH`. 18 | 19 | ## Using the CLI 20 | 21 | ``` 22 | $ rwf-cli --help 23 | ``` 24 | 25 | ## Documentation 26 | 27 | 📘 The documentation **[is available here](https://levkk.github.io/rwf/)**. 28 | -------------------------------------------------------------------------------- /rwf-cli/src/logging.rs: -------------------------------------------------------------------------------- 1 | use rwf::colors::MaybeColorize; 2 | 3 | pub fn written(something: impl ToString) { 4 | eprintln!("{} {}", " Written".green().bold(), something.to_string()); 5 | } 6 | 7 | pub fn created(something: impl ToString) { 8 | eprintln!("{} {}", " Created".green().bold(), something.to_string()); 9 | } 10 | 11 | pub fn error(something: impl ToString) { 12 | eprintln!("{}: {}", "error".red().bold(), something.to_string()); 13 | } 14 | 15 | pub fn warning(something: impl ToString) { 16 | eprintln!( 17 | "{}: {}", 18 | " Warning".yellow().bold(), 19 | something.to_string() 20 | ); 21 | } 22 | 23 | pub fn removed(something: impl ToString) { 24 | eprintln!("{} {}", " Removed".red().bold(), something.to_string()); 25 | } 26 | 27 | pub fn packaging(something: impl ToString) { 28 | eprintln!( 29 | "{} {}", 30 | " Packaging".green().bold(), 31 | something.to_string() 32 | ); 33 | } 34 | 35 | pub fn using(something: impl ToString) { 36 | eprintln!(" {} {}", "Using".green().bold(), something.to_string()); 37 | } 38 | -------------------------------------------------------------------------------- /rwf-cli/src/remove.rs: -------------------------------------------------------------------------------- 1 | use crate::add::modules; 2 | use crate::logging::{error, removed}; 3 | use rwf::controller::Error; 4 | use std::path::Path; 5 | use tokio::fs::remove_file; 6 | 7 | pub async fn controller(name: &str) -> Result<(), Error> { 8 | let snake = rwf::snake_case(name); 9 | let mod_path = Path::new("src/controllers"); 10 | let path = mod_path.join(format!("{}.rs", snake)); 11 | 12 | if path.exists() { 13 | remove_file(&path).await?; 14 | 15 | removed(path.display().to_string()); 16 | 17 | modules(&mod_path).await?; 18 | } else { 19 | error(format!("{} doesn't exist", path.display())); 20 | } 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /rwf-cli/src/templates/Procfile: -------------------------------------------------------------------------------- 1 | web: <%= name %> 2 | -------------------------------------------------------------------------------- /rwf-cli/src/templates/controller.rs.tpl: -------------------------------------------------------------------------------- 1 | use rwf::prelude::*; 2 | 3 | #[derive(Default)] 4 | pub struct <%= name %>; 5 | 6 | #[async_trait] 7 | impl Controller for <%= name %> { 8 | async fn handle(&self, _request: &Request) -> Result { 9 | Ok(Response::not_implemented()) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /rwf-cli/src/templates/mod.rs.tpl: -------------------------------------------------------------------------------- 1 | // This file is automatically generated by rwf-cli. 2 | // Manual modifications to this file will not be preserved. 3 | <% for module in modules %>pub mod <%= module %>; 4 | <% end %> 5 | -------------------------------------------------------------------------------- /rwf-cli/src/templates/page-controller.rs.tpl: -------------------------------------------------------------------------------- 1 | use rwf::prelude::*; 2 | 3 | #[derive(Default, macros::PageController)] 4 | pub struct <%= name %>; 5 | 6 | #[async_trait] 7 | impl PageController for <%= name %> { 8 | async fn get(&self, _request: &Request) -> Result { 9 | Ok(Response::not_implemented()) 10 | } 11 | 12 | async fn post(&self, _request: &Request) -> Result { 13 | Ok(Response::method_not_allowed()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /rwf-cli/src/templates/routes.rs.tpl: -------------------------------------------------------------------------------- 1 | use rwf::http::Handler; 2 | use rwf::prelude::*; 3 | 4 | <% for route in routes %>use crate::controllers::<%= route %>::<%= route.camelize %>; 5 | <% end %> 6 | pub fn routes() -> Vec { 7 | vec![<% for route in routes %> 8 | route!("/<%= route %>" => <%= route.camelize %>),<% end %> 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /rwf-cli/src/util.rs: -------------------------------------------------------------------------------- 1 | use tokio::fs::read_to_string; 2 | use tokio::process::Command; 3 | use toml::Value; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct PackageInfo { 7 | pub name: String, 8 | #[allow(dead_code)] 9 | pub version: String, 10 | pub target_dir: String, 11 | } 12 | 13 | async fn cargo_toml() -> Result> { 14 | let cargo_toml = read_to_string("Cargo.toml").await?; 15 | let toml: Value = toml::from_str(&cargo_toml)?; 16 | 17 | Ok(toml) 18 | } 19 | 20 | pub async fn package_info() -> Result> { 21 | let toml = cargo_toml().await?; 22 | 23 | let name = toml 24 | .get("package") 25 | .expect("Cargo.toml to have a valid [package] attribute") 26 | .get("name") 27 | .expect("Cargo.toml to have a valid \"name\" field"); 28 | 29 | let version = toml 30 | .get("package") 31 | .expect("Cargo.toml to have a valid [package] attribute") 32 | .get("version") 33 | .expect("Cargo.toml to have a valid \"name\" field"); 34 | 35 | let metadata = Command::new("cargo").arg("metadata").output().await?.stdout; 36 | let json: serde_json::Value = serde_json::from_slice(&metadata)?; 37 | let target_dir = json["target_directory"].as_str().unwrap().to_string(); 38 | 39 | Ok(PackageInfo { 40 | name: name.as_str().unwrap().to_string(), 41 | version: version.as_str().unwrap().to_string(), 42 | target_dir, 43 | }) 44 | } 45 | -------------------------------------------------------------------------------- /rwf-fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwf-fuzz" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | afl = "*" 8 | rwf = { version = "*", path = "../rwf" } 9 | tokio = "*" 10 | 11 | [[bin]] 12 | name = "path_fuzzer" 13 | 14 | [[bin]] 15 | name = "router_fuzzer" 16 | -------------------------------------------------------------------------------- /rwf-fuzz/in/path_fuzzer/url1: -------------------------------------------------------------------------------- 1 | /hello/world 2 | -------------------------------------------------------------------------------- /rwf-fuzz/in/path_fuzzer/url2: -------------------------------------------------------------------------------- 1 | /api/:id/one 2 | -------------------------------------------------------------------------------- /rwf-fuzz/in/path_fuzzer/url3: -------------------------------------------------------------------------------- 1 | /apples/oranges/134/?id=234 2 | -------------------------------------------------------------------------------- /rwf-fuzz/in/path_fuzzer/url4: -------------------------------------------------------------------------------- 1 | no_forward_shash/:param/?query=value?query2=id+234/sdff 2 | -------------------------------------------------------------------------------- /rwf-fuzz/in/router_fuzzer/request1: -------------------------------------------------------------------------------- 1 | GET /hello/world/?id=34 HTTP/1.1 2 | Host: example.com 3 | Connection: keep-alive 4 | Content-Length: 0 5 | -------------------------------------------------------------------------------- /rwf-fuzz/in/router_fuzzer/request2: -------------------------------------------------------------------------------- 1 | POST /one/two/4545/?hello=world&arg=34 HTTP/1.1 2 | Host: example.com 3 | Connection: keep-alive 4 | Content-Length: 10 5 | Authorization: Basic incorrectencoding 6 | Content-Type: text/plain 7 | 8 | helloworld 9 | -------------------------------------------------------------------------------- /rwf-fuzz/src/bin/path_fuzzer.rs: -------------------------------------------------------------------------------- 1 | use afl::*; 2 | use rwf::http::Path; 3 | 4 | fn main() { 5 | fuzz!(|data: &[u8]| { 6 | if let Ok(s) = std::str::from_utf8(data) { 7 | let _ = Path::parse(s); 8 | } 9 | }); 10 | } 11 | -------------------------------------------------------------------------------- /rwf-fuzz/src/bin/router_fuzzer.rs: -------------------------------------------------------------------------------- 1 | use afl::*; 2 | use rwf::http::Request; 3 | use tokio::runtime::*; 4 | 5 | fn main() { 6 | let runtime = Builder::new_current_thread().enable_all().build().unwrap(); 7 | fuzz!(|data: &[u8]| { 8 | runtime.block_on(async move { 9 | let addr = "127.0.0.1:8000".parse().unwrap(); 10 | Request::read(addr, data).await.unwrap() 11 | }); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /rwf-fuzz/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /rwf-macros/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /rwf-macros/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "proc-macro2" 7 | version = "1.0.86" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 10 | dependencies = [ 11 | "unicode-ident", 12 | ] 13 | 14 | [[package]] 15 | name = "quote" 16 | version = "1.0.36" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 19 | dependencies = [ 20 | "proc-macro2", 21 | ] 22 | 23 | [[package]] 24 | name = "rum-macros" 25 | version = "0.1.0" 26 | dependencies = [ 27 | "proc-macro2", 28 | "quote", 29 | "syn", 30 | ] 31 | 32 | [[package]] 33 | name = "syn" 34 | version = "2.0.69" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "201fcda3845c23e8212cd466bfebf0bd20694490fc0356ae8e428e0824a915a6" 37 | dependencies = [ 38 | "proc-macro2", 39 | "quote", 40 | "unicode-ident", 41 | ] 42 | 43 | [[package]] 44 | name = "unicode-ident" 45 | version = "1.0.12" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 48 | -------------------------------------------------------------------------------- /rwf-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwf-macros" 3 | version = "0.2.1" 4 | edition = "2021" 5 | license = "MIT" 6 | description = "Macros for the Rust Web Framework" 7 | readme = "README.md" 8 | homepage = "https://levkk.github.io/rwf/" 9 | repository = "https://github.com/levkk/rwf" 10 | keywords = ["rwf", "web", "framework", "macros"] 11 | authors = ["Lev Kokotov "] 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [dependencies] 19 | syn = { version = "2", features = ["full"] } 20 | quote = "1" 21 | proc-macro2 = "1" 22 | pluralizer = "0.4" 23 | 24 | [dev-dependencies] 25 | macrotest = "1" 26 | -------------------------------------------------------------------------------- /rwf-macros/README.md: -------------------------------------------------------------------------------- 1 | # Rwf macros 2 | 3 | [![Documentation](https://img.shields.io/badge/documentation-blue?style=flat)](https://levkk.github.io/rwf/) 4 | [![Latest crate](https://img.shields.io/crates/v/rwf-macros.svg)](https://crates.io/crates/rwf-macros) 5 | [![Reference docs](https://img.shields.io/docsrs/rwf-macros)](https://docs.rs/rwf-macros) 6 | 7 | `rwf-macros` is a crate that contains all derive and procedural macros used in [Rwf](https://crates.io/crates/rwf). All these macros are imported in `rwf` and re-exported as `rwf::macros`. 8 | -------------------------------------------------------------------------------- /rwf-macros/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use proc_macro::TokenStream; 2 | pub use quote::quote; 3 | pub use syn::parse::*; 4 | pub use syn::*; 5 | -------------------------------------------------------------------------------- /rwf-macros/tests/model/relationship.rs: -------------------------------------------------------------------------------- 1 | use rwf_macros::*; 2 | 3 | #[derive(Model, Clone)] 4 | #[has_many(Task)] 5 | pub struct User { 6 | id: Option, 7 | email: String, 8 | } 9 | 10 | #[derive(Model, Clone)] 11 | #[belongs_to(User)] 12 | pub struct Task { 13 | id: Option, 14 | user_id: i64, 15 | } 16 | 17 | fn main() {} 18 | -------------------------------------------------------------------------------- /rwf-ruby/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwf-ruby" 3 | license = "MIT" 4 | description = "Ruby (specifically Rack/Rails) bindings for the Rust Web Framework" 5 | version = "0.1.1" 6 | edition = "2021" 7 | readme = "README.md" 8 | homepage = "https://levkk.github.io/rwf/" 9 | repository = "https://github.com/levkk/rwf" 10 | keywords = ["rwf", "web", "framework", "ruby", "bindings"] 11 | authors = ["Lev Kokotov "] 12 | 13 | [dependencies] 14 | libc = "0.2" 15 | once_cell = "1" 16 | thiserror = "1" 17 | parking_lot = "0.9" 18 | tracing = "0.1" 19 | 20 | [build-dependencies] 21 | cc = "1" 22 | bindgen = "0.68" 23 | -------------------------------------------------------------------------------- /rwf-ruby/README.md: -------------------------------------------------------------------------------- 1 | # Rwf ruby 2 | 3 | [![Documentation](https://img.shields.io/badge/documentation-blue?style=flat)](https://levkk.github.io/rwf/) 4 | [![Latest crate](https://img.shields.io/crates/v/rwf-ruby.svg)](https://crates.io/crates/rwf-ruby) 5 | [![Reference docs](https://img.shields.io/docsrs/rwf-ruby)](https://docs.rs/rwf-ruby) 6 | 7 | `rwf-ruby` contains Rust bindings for running Ruby applications built on top of [Rack](https://github.com/rack/rack). While there exists other projects that bind Ruby to Rust in a generic way, 8 | running arbitrary Ruby code inside Rust requires wrapping `ruby_exec_node` directly. 9 | 10 | This project is experimental, and needs additional testing to ensure production stability. The bindings are written in C, see [src/bindings.c](src/bindings.c). 11 | -------------------------------------------------------------------------------- /rwf-ruby/build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | 3 | fn main() { 4 | println!("cargo:rerun-if-changed=src/bindings.c"); 5 | println!("cargo:rerun-if-changed=src/bindings.h"); // Bindings are generated manually because bindgen goes overboard with ruby.h 6 | 7 | let output = Command::new("ruby") 8 | .arg("headers.rb") 9 | .output() 10 | .expect("Is ruby installed on your system?") 11 | .stdout; 12 | let flags = String::from_utf8_lossy(&output).to_string(); 13 | 14 | let mut build = cc::Build::new(); 15 | 16 | for flag in flags.split(" ") { 17 | build.flag(flag); 18 | } 19 | 20 | // Github actions workaround. I don't remember if this works or not. 21 | match Command::new("find") 22 | .arg("/opt/hostedtoolcache/Ruby") 23 | .arg("-name") 24 | .arg("libruby.so") 25 | .output() 26 | { 27 | Ok(output) => { 28 | let lib = String::from_utf8_lossy(&output.stdout) 29 | .to_string() 30 | .trim() 31 | .to_string(); 32 | let lib = lib.split("\n").next().unwrap_or("").trim(); 33 | if !lib.is_empty() { 34 | build.flag(format!("-L{}", lib)); 35 | } 36 | } 37 | 38 | Err(_) => (), 39 | }; 40 | 41 | build.file("src/bindings.c").compile("rwf_ruby"); 42 | } 43 | -------------------------------------------------------------------------------- /rwf-ruby/headers.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # Source: https://silverhammermba.github.io/emberb/embed/ 3 | require 'shellwords' 4 | 5 | # location of ruby.h 6 | hdrdir = Shellwords.escape RbConfig::CONFIG["rubyhdrdir"] 7 | # location of ruby/config.h 8 | archhdrdir = Shellwords.escape RbConfig::CONFIG["rubyarchhdrdir"] 9 | # location of libruby 10 | libdir = Shellwords.escape RbConfig::CONFIG["libdir"] 11 | 12 | # args for GCC 13 | puts "-I#{hdrdir} -I#{archhdrdir} -L#{libdir} -Wl,-rpath,#{libdir}" 14 | -------------------------------------------------------------------------------- /rwf-ruby/src/.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | Add: 3 | - "-I/usr/include/ruby-3.3.0" 4 | - "-I/usr/include/ruby-3.3.0/x86_64-linux" 5 | - "-I/opt/homebrew/Cellar/ruby/3.3.4/include/ruby-3.3.0" 6 | -------------------------------------------------------------------------------- /rwf-ruby/src/bindings.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #ifndef BINDINGS_H 6 | #define BINDINGS_H 7 | 8 | typedef struct EnvKey { 9 | const char *key; 10 | const char *value; 11 | } KeyValue; 12 | 13 | typedef struct RackResponse { 14 | uintptr_t value; 15 | int code; 16 | int num_headers; 17 | KeyValue *headers; 18 | char* body; 19 | int is_file; 20 | } RackResponse; 21 | 22 | typedef struct RackRequest { 23 | const KeyValue* env; 24 | const int length; 25 | const char *body; 26 | } RackRequest; 27 | 28 | 29 | int rwf_load_app(const char *path); 30 | void rwf_init_ruby(void); 31 | RackResponse rwf_rack_response_new(VALUE value); 32 | int rwf_app_call(RackRequest request, const char *app_name, RackResponse *res); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /rwf-ruby/tests/config.ru: -------------------------------------------------------------------------------- 1 | require 'yaml' 2 | 3 | run do |env| 4 | puts env.to_yaml 5 | return [200, {}, ['Hello world']] 6 | end 7 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/.dockerignore: -------------------------------------------------------------------------------- 1 | # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files. 2 | 3 | # Ignore git directory. 4 | /.git/ 5 | /.gitignore 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all default key files. 15 | /config/master.key 16 | /config/credentials/*.key 17 | 18 | # Ignore all logfiles and tempfiles. 19 | /log/* 20 | /tmp/* 21 | !/log/.keep 22 | !/tmp/.keep 23 | 24 | # Ignore pidfiles, but keep the directory. 25 | /tmp/pids/* 26 | !/tmp/pids/.keep 27 | 28 | # Ignore storage (uploaded files in development and any SQLite databases). 29 | /storage/* 30 | !/storage/.keep 31 | /tmp/storage/* 32 | !/tmp/storage/.keep 33 | 34 | # Ignore assets. 35 | /node_modules/ 36 | /app/assets/builds/* 37 | !/app/assets/builds/.keep 38 | /public/assets 39 | 40 | # Ignore CI service files. 41 | /.github 42 | 43 | # Ignore development files 44 | /.devcontainer 45 | 46 | # Ignore Docker-related files 47 | /.dockerignore 48 | /Dockerfile* 49 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | config/credentials/*.yml.enc diff=rails_credentials 9 | config/credentials.yml.enc diff=rails_credentials 10 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: bundler 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: daily 12 | open-pull-requests-limit: 10 13 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # Temporary files generated by your text editor or operating system 4 | # belong in git's global ignore instead: 5 | # `$XDG_CONFIG_HOME/git/ignore` or `~/.config/git/ignore` 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all environment files (except templates). 11 | /.env* 12 | !/.env*.erb 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore storage (uploaded files in development and any SQLite databases). 26 | /storage/* 27 | !/storage/.keep 28 | /tmp/storage/* 29 | !/tmp/storage/ 30 | !/tmp/storage/.keep 31 | 32 | /public/assets 33 | 34 | # Ignore master key for decrypting credentials and more. 35 | /config/master.key 36 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/.rubocop.yml: -------------------------------------------------------------------------------- 1 | # Omakase Ruby styling for Rails 2 | inherit_gem: { rubocop-rails-omakase: rubocop.yml } 3 | 4 | # Overwrite or add rules to create your own house style 5 | # 6 | # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]` 7 | # Layout/SpaceInsideArrayLiteralBrackets: 8 | # Enabled: false 9 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.3.5 2 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../javascript .js 4 | //= link_tree ../../../vendor/javascript .js 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/app/assets/images/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Only allow modern browsers supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has. 3 | allow_browser versions: :modern 4 | end 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | import "@hotwired/turbo-rails" 3 | import "controllers" 4 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.debug = false 7 | window.Stimulus = application 8 | 9 | export { application } 10 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/javascript/controllers/hello_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | connect() { 5 | this.element.textContent = "Hello World!" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Import and register all your controllers from the importmap via controllers/**/*_controller 2 | import { application } from "controllers/application" 3 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" 4 | eagerLoadControllersFrom("controllers", application) 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/app/models/concerns/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= content_for(:title) || "Todo" %> 5 | 6 | 7 | <%= csrf_meta_tags %> 8 | <%= csp_meta_tag %> 9 | 10 | <%= yield :head %> 11 | 12 | 13 | 14 | 15 | 16 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> 17 | <%= javascript_importmap_tags %> 18 | 19 | 20 | 21 | <%= yield %> 22 | 23 | 24 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/views/pwa/manifest.json.erb: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Todo", 3 | "icons": [ 4 | { 5 | "src": "/icon.png", 6 | "type": "image/png", 7 | "sizes": "512x512" 8 | }, 9 | { 10 | "src": "/icon.png", 11 | "type": "image/png", 12 | "sizes": "512x512", 13 | "purpose": "maskable" 14 | } 15 | ], 16 | "start_url": "/", 17 | "display": "standalone", 18 | "scope": "/", 19 | "description": "Todo.", 20 | "theme_color": "red", 21 | "background_color": "red" 22 | } 23 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/app/views/pwa/service-worker.js: -------------------------------------------------------------------------------- 1 | // Add a service worker for processing Web Push notifications: 2 | // 3 | // self.addEventListener("push", async (event) => { 4 | // const { title, options } = await event.data.json() 5 | // event.waitUntil(self.registration.showNotification(title, options)) 6 | // }) 7 | // 8 | // self.addEventListener("notificationclick", function(event) { 9 | // event.notification.close() 10 | // event.waitUntil( 11 | // clients.matchAll({ type: "window" }).then((clientList) => { 12 | // for (let i = 0; i < clientList.length; i++) { 13 | // let client = clientList[i] 14 | // let clientPath = (new URL(client.url)).pathname 15 | // 16 | // if (clientPath == event.notification.data.path && "focus" in client) { 17 | // return client.focus() 18 | // } 19 | // } 20 | // 21 | // if (clients.openWindow) { 22 | // return clients.openWindow(event.notification.data.path) 23 | // } 24 | // }) 25 | // ) 26 | // }) 27 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/bin/brakeman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | ARGV.unshift("--ensure-latest") 6 | 7 | load Gem.bin_path("brakeman", "brakeman") 8 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/bin/docker-entrypoint: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # Enable jemalloc for reduced memory usage and latency. 4 | if [ -z "${LD_PRELOAD+x}" ] && [ -f /usr/lib/*/libjemalloc.so.2 ]; then 5 | export LD_PRELOAD="$(echo /usr/lib/*/libjemalloc.so.2)" 6 | fi 7 | 8 | # If running the rails server then create or migrate existing database 9 | if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then 10 | ./bin/rails db:prepare 11 | fi 12 | 13 | exec "${@}" 14 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/bin/rubocop: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "rubygems" 3 | require "bundler/setup" 4 | 5 | # explicit rubocop config increases performance slightly while avoiding config confusion. 6 | ARGV.unshift("--config", File.expand_path("../.rubocop.yml", __dir__)) 7 | 8 | load Gem.bin_path("rubocop", "rubocop") 9 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | APP_ROOT = File.expand_path("..", __dir__) 5 | APP_NAME = "todo" 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | 34 | # puts "\n== Configuring puma-dev ==" 35 | # system "ln -nfs #{APP_ROOT} ~/.puma-dev/#{APP_NAME}" 36 | # system "curl -Is https://#{APP_NAME}.test/up | head -n 1" 37 | end 38 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Todo 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 7.2 13 | 14 | # Please, add to the `ignore` list any other `lib` subdirectories that do 15 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 16 | # Common ones are `templates`, `generators`, or `middleware`, for example. 17 | config.autoload_lib(ignore: %w[assets tasks]) 18 | 19 | # Configuration for the application, engines, and railties goes here. 20 | # 21 | # These settings can be overridden in specific environments using the files 22 | # in config/environments, which are processed later. 23 | # 24 | # config.time_zone = "Central Time (US & Canada)" 25 | # config.eager_load_paths << Rails.root.join("extras") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: test 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: todo_production 11 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | L5MABPObAssYsDkM1D6DAUFb7VuLwuaGvPBY0CSZ54DIMHt9yfR9S0bwJa8FW33FnH58/fAjswSH8PTI74CtKLjE7LU1f+ayyicMNBiQiBf8FJ+Sb91DjX8pICEGn15+RizGNGhkjXIIcNW8+00Ek9xgT2qSziKmHjGdquLZHyARYAUfxuECWQHCVrfPP4LFAJC5vESyo65kkUzbHnClYsxtIwmIITh4RdbzI3BU4DD/vTPF3WTytoYKe9ugPSUcNakKAkN010gF4Xlh4OzKOoHoNl3x1b6Y4BUuiyCpEfFGcJCLUFPiZ979ZeejKPoeBjdDrWBHZtF0FnVD6fjM/fgIKlIVct3BO4U9B8tAFsgp7zfDx5UMpgAFnyyma9sobnoUcCEczp94nm4XOTRg8JgtouSv--gOvoDY6XCCoo/yS8--0Gl2UGvbB+nFeO3v020nww== -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: storage/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: storage/test.sqlite3 22 | 23 | 24 | # SQLite3 write its data on the local filesystem, as such it requires 25 | # persistent disks. If you are deploying to a managed service, you should 26 | # make sure it provides disk persistence, as many don't. 27 | # 28 | # Similarly, if you deploy your application as a Docker container, you must 29 | # ensure the database is located in a persisted volume. 30 | production: 31 | <<: *default 32 | # database: path/to/persistent/storage/production.sqlite3 33 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin "application" 4 | pin "@hotwired/turbo-rails", to: "turbo.min.js" 5 | pin "@hotwired/stimulus", to: "stimulus.min.js" 6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js" 7 | pin_all_from "app/javascript/controllers", under: "controllers" 8 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w[ admin.js admin.css ] 13 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :email, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization and 2 | # are automatically loaded by Rails. If you want to use locales other than 3 | # English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more about the API, please read the Rails Internationalization guide 20 | # at https://guides.rubyonrails.org/i18n.html. 21 | # 22 | # Be aware that YAML interprets the following case-insensitive strings as 23 | # booleans: `true`, `false`, `on`, `off`, `yes`, `no`. Therefore, these strings 24 | # must be quoted to be interpreted as strings. For example: 25 | # 26 | # en: 27 | # "yes": yup 28 | # enabled: "ON" 29 | 30 | en: 31 | hello: "Hello world" 32 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 3 | 4 | # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. 5 | # Can be used by load balancers and uptime monitors to verify that the app is live. 6 | get "up" => "rails/health#show", as: :rails_health_check 7 | 8 | # Render dynamic PWA files from app/views/pwa/* 9 | get "service-worker" => "rails/pwa#service_worker", as: :pwa_service_worker 10 | get "manifest" => "rails/pwa#manifest", as: :pwa_manifest 11 | 12 | # Defines the root path route ("/") 13 | # root "posts#index" 14 | end 15 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should ensure the existence of records required to run the application in every environment (production, 2 | # development, test). The code here should be idempotent so that it can be executed at any point in every environment. 3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Example: 6 | # 7 | # ["Action", "Comedy", "Drama", "Horror"].each do |genre_name| 8 | # MovieGenre.find_or_create_by!(name: genre_name) 9 | # end 10 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/lib/assets/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/lib/tasks/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/log/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/public/icon.png -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/public/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/storage/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :headless_chrome, screen_size: [ 1400, 1400 ] 5 | end 6 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module ApplicationCable 4 | class ConnectionTest < ActionCable::Connection::TestCase 5 | # test "connects with cookies" do 6 | # cookies.signed[:user_id] = 42 7 | # 8 | # connect 9 | # 10 | # assert_equal connection.user_id, "42" 11 | # end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/test/controllers/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/test/fixtures/files/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/test/helpers/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/test/integration/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/test/mailers/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/test/models/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/test/system/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | module ActiveSupport 6 | class TestCase 7 | # Run tests in parallel with specified workers 8 | parallelize(workers: :number_of_processors) 9 | 10 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 11 | fixtures :all 12 | 13 | # Add more helper methods to be used by all tests here... 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/tmp/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/tmp/pids/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/tmp/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/tmp/storage/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/vendor/.keep -------------------------------------------------------------------------------- /rwf-ruby/tests/todo/vendor/javascript/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-ruby/tests/todo/vendor/javascript/.keep -------------------------------------------------------------------------------- /rwf-tests/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /rwf-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwf-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rwf = { path = "../rwf" } 10 | rwf-macros = { path = "../rwf-macros" } 11 | tokio = { version = "1", features = ["net", "fs", "rt-multi-thread"] } 12 | tracing-subscriber = "*" 13 | console-subscriber = "*" 14 | serde_json = "1" 15 | serde = "*" 16 | parking_lot = "*" 17 | -------------------------------------------------------------------------------- /rwf-tests/migrations/0_rum.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE rwf_jobs; 2 | -------------------------------------------------------------------------------- /rwf-tests/migrations/0_rum.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE rwf_jobs ( 2 | id BIGSERIAL PRIMARY KEY, 3 | name VARCHAR NOT NULL, 4 | args JSONB NOT NULL DEFAULT '{}'::jsonb, 5 | created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), 6 | start_after TIMESTAMPTZ NOT NULL DEFAULT NOW(), 7 | started_at TIMESTAMPTZ, 8 | attempts INT NOT NULL DEFAULT 0, 9 | retries BIGINT NOT NULL DEFAULT 25, 10 | completed_at TIMESTAMPTZ, 11 | error VARCHAR 12 | ); 13 | 14 | -- Pending jobs 15 | CREATE INDEX ON rwf_jobs USING btree(start_after, created_at) WHERE 16 | completed_at IS NULL 17 | AND started_at IS NULL 18 | AND attempts < retries; 19 | 20 | -- Running jobs 21 | CREATE INDEX ON rwf_jobs USING btree(start_after, created_at) WHERE 22 | completed_at IS NULL 23 | AND started_at IS NOT NULL 24 | AND attempts < retries; 25 | 26 | CREATE INDEX ON rwf_jobs USING btree(name, completed_at); 27 | -------------------------------------------------------------------------------- /rwf-tests/migrations/1_users.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE users; -------------------------------------------------------------------------------- /rwf-tests/migrations/1_users.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE users ( 2 | id BIGINT NOT NULL, 3 | name VARCHAR NOT NULL 4 | ); -------------------------------------------------------------------------------- /rwf-tests/migrations/2_orders.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE orders; -------------------------------------------------------------------------------- /rwf-tests/migrations/2_orders.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE orders ( 2 | id BIGSERIAL PRIMARY KEY, 3 | user_id BIGINT NOT NULL, 4 | name VARCHAR NOT NULL, 5 | optional VARCHAR 6 | ); -------------------------------------------------------------------------------- /rwf-tests/migrations/3_products.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE products; -------------------------------------------------------------------------------- /rwf-tests/migrations/3_products.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE products ( 2 | id BIGSERIAL PRIMARY KEY, 3 | name VARCHAR NOT NULL, 4 | avg_price DOUBLE PRECISION NOT NULL DEFAULT 5.0 5 | ); -------------------------------------------------------------------------------- /rwf-tests/migrations/4_order_items.down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE order_items; -------------------------------------------------------------------------------- /rwf-tests/migrations/4_order_items.up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE order_items ( 2 | id BIGSERIAL PRIMARY KEY, 3 | order_id BIGINT NOT NULL, 4 | product_id BIGINT NOT NULL, 5 | amount DOUBLE PRECISION NOT NULL DEFAULT 5.0 6 | ); -------------------------------------------------------------------------------- /rwf-tests/migrations/5_data.down.sql: -------------------------------------------------------------------------------- 1 | TRUNCATE TABLE orders; 2 | TRUNCATE TABLE products; 3 | TRUNCATE TABLE order_items; 4 | TRUNCATE TABLE users; -------------------------------------------------------------------------------- /rwf-tests/migrations/5_data.up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO orders (user_id, name, optional) VALUES (2, 'test', 'optional'); 2 | INSERT INTO order_items (order_id, product_id, amount) VALUES (1, 1, 5.0), (1, 2, 6.0); 3 | INSERT INTO products (name, avg_price) VALUES ('apples', 6.0), ('doodles', 7.0); 4 | INSERT INTO users (id, name) VALUES (2, 'test'); -------------------------------------------------------------------------------- /rwf-tests/src/components/login/controller.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-tests/src/components/login/controller.js -------------------------------------------------------------------------------- /rwf-tests/src/components/login/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-tests/src/components/login/index.html -------------------------------------------------------------------------------- /rwf-tests/src/components/login/mod.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-tests/src/components/login/mod.rs -------------------------------------------------------------------------------- /rwf-tests/src/components/login/show.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-tests/src/components/login/show.html -------------------------------------------------------------------------------- /rwf-tests/src/components/login/style.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/levkk/rwf/572f82c1bd7735be8ec6f7817d6096315e3177c8/rwf-tests/src/components/login/style.scss -------------------------------------------------------------------------------- /rwf-tests/src/components/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rwf-tests/src/controllers/login/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rwf-tests/src/controllers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod login; 2 | -------------------------------------------------------------------------------- /rwf-tests/src/models/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /rwf-tests/static/test.txt: -------------------------------------------------------------------------------- 1 | hello static file -------------------------------------------------------------------------------- /rwf-tests/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Rum 6 | <%- rwf_head() %> 7 | 13 | 19 | 25 | 26 | 27 |
    28 |
    29 |

    Hello

    30 |
    31 |
    32 | 37 |
    38 |
    39 | 40 | 41 | -------------------------------------------------------------------------------- /rwf-tests/templates/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 |

    <%= title.upcase %>

    8 |

    <%= description.to_uppercase %>

    9 | 10 | <%= product.name %> 11 | 12 |
      13 | <% for product in products %> 14 |
    • <%= product.name %>
    • 15 | <% end %> 16 |
    17 | 18 |
      19 | <% for var in vars.enumerate %> 20 |
    • <%= var.0 %>. <%= var.1 %>
    • 21 | <% end %> 22 |
    23 | 24 | 25 | <% for i in 5.times %> 26 | <%= i %> 27 | <% end %> 28 | 29 | 30 | <% if title.downcase == "hello" && 1 == 2 %> 31 |

    <%= title %>

    32 | <% else %> 33 |

    Not hello

    34 | <% end %> 35 | 36 | 37 | -------------------------------------------------------------------------------- /rwf/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /rwf/README.md: -------------------------------------------------------------------------------- 1 | ../README.md -------------------------------------------------------------------------------- /rwf/src/analytics/mod.rs: -------------------------------------------------------------------------------- 1 | //! Analytics around aplication usage. 2 | //! 3 | //! Work in progress, but currently handles HTTP request tracking. On the roadmap: 4 | //! 5 | //! * Experiments (A/B testing) 6 | 7 | pub mod requests; 8 | 9 | pub use requests::Request; 10 | -------------------------------------------------------------------------------- /rwf/src/controller/middleware/prelude.rs: -------------------------------------------------------------------------------- 1 | //! All necessary imports for writing your own midddleware. 2 | pub use crate::controller::{Error, Middleware, MiddlewareSet, Outcome}; 3 | pub use crate::http::{Request, Response}; 4 | pub use async_trait::async_trait; 5 | -------------------------------------------------------------------------------- /rwf/src/controller/turbo_stream.rs: -------------------------------------------------------------------------------- 1 | //! Implement a WebSocket controller to serve Turbo's ``. 2 | //! 3 | //! ### Example 4 | //! 5 | //! ```rust 6 | //! use rwf::prelude::*; 7 | //! use rwf::controller::TurboStream; 8 | //! use rwf::http::Server; 9 | //! 10 | //! Server::new(vec![ 11 | //! route!("/turbo-stream" => TurboStream), 12 | //! ]); 13 | //! ``` 14 | use super::WebsocketController; 15 | use crate::{http::Stream, prelude::*}; 16 | 17 | /// Turbo Stream WebSocket controller. 18 | #[derive(Default)] 19 | pub struct TurboStream; 20 | 21 | #[async_trait] 22 | impl Controller for TurboStream { 23 | async fn handle(&self, request: &Request) -> Result { 24 | WebsocketController::handle(self, request).await 25 | } 26 | 27 | async fn handle_stream(&self, request: &Request, stream: Stream<'_>) -> Result { 28 | WebsocketController::handle_stream(self, request, stream).await 29 | } 30 | } 31 | 32 | #[async_trait] 33 | impl WebsocketController for TurboStream {} 34 | -------------------------------------------------------------------------------- /rwf/src/http/form.rs: -------------------------------------------------------------------------------- 1 | //! Form parsing. 2 | use super::{Error, FormData}; 3 | 4 | /// HTTP form. 5 | pub struct Form { 6 | data: Box, 7 | } 8 | 9 | /// Handle conversion between HTML form data and a Rust struct. 10 | pub trait FromFormData { 11 | /// Convert form data to Rust struct. 12 | fn from_form_data(form_data: &FormData) -> Result 13 | where 14 | Self: Sized; 15 | } 16 | 17 | impl FromFormData for FormData { 18 | fn from_form_data(form_data: &FormData) -> Result { 19 | Ok(form_data.clone()) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /rwf/src/http/path/to_parameter.rs: -------------------------------------------------------------------------------- 1 | //! Conversion between URL parameters and Rust types. 2 | use super::Error; 3 | use std::fmt::Debug; 4 | 5 | /// Convert a URL parameter to a Rust type. 6 | pub trait ToParameter: Sync + Send + Debug { 7 | /// Perform the type conversion. 8 | fn to_parameter(s: &str) -> Result 9 | where 10 | Self: Sized; 11 | } 12 | 13 | impl ToParameter for i64 { 14 | fn to_parameter(s: &str) -> Result { 15 | match s.parse() { 16 | Ok(id) => Ok(id), 17 | Err(_) => Err(Error::MalformedRequest("i64")), 18 | } 19 | } 20 | } 21 | 22 | impl ToParameter for String { 23 | fn to_parameter(s: &str) -> Result { 24 | Ok(s.to_string()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /rwf/src/http/wsgi/mod.rs: -------------------------------------------------------------------------------- 1 | //! uWSGI server to integrate Python apps 2 | //! into Rwf. 3 | 4 | pub mod request; 5 | 6 | pub use request::{WsgiRequest, WsgiResponse}; 7 | -------------------------------------------------------------------------------- /rwf/src/http/wsgi/uwsgi_wrapper.py: -------------------------------------------------------------------------------- 1 | 2 | def wrapper(env, application): 3 | # List is passed by reference into the lambda function. 4 | results = [] 5 | 6 | # Start response for wsgi. 7 | def start_response(code, headers): 8 | results.append(code) 9 | results.append(headers) 10 | 11 | response = application(env, start_response) 12 | 13 | return (response, results[0], results[1]) 14 | -------------------------------------------------------------------------------- /rwf/src/job/mod.rs: -------------------------------------------------------------------------------- 1 | //! Asynchronous background job queue. 2 | //! 3 | //! Implemented using a Postgres table and a fast locking query (`FOR UPDATE SKIP LOCKED`). 4 | //! This implementation makes the job queue durable (doesn't lose jobs) and fast. 5 | pub mod clock; 6 | pub mod cron; 7 | pub mod error; 8 | pub mod model; 9 | pub mod worker; 10 | 11 | pub use clock::Clock; 12 | pub use cron::Cron; 13 | pub use error::Error; 14 | pub use model::{queue_async, queue_delay, Job, JobHandler, JobModel}; 15 | pub use worker::Worker; 16 | -------------------------------------------------------------------------------- /rwf/src/logging.rs: -------------------------------------------------------------------------------- 1 | //! Wrapper around `tracing_subscriber` for logging. 2 | //! 3 | //! Configures application-wide logging to go to stderr at the `INFO` level. 4 | //! If you prefer to use your own logging subscriber, don't initialize the `Logger`. 5 | //! 6 | //! ### Example 7 | //! 8 | //! ```rust 9 | //! use rwf::prelude::*; 10 | //! 11 | //! Logger::init(); 12 | //! ``` 13 | use crate::config::get_config; 14 | use once_cell::sync::OnceCell; 15 | use tracing_subscriber::{filter::LevelFilter, fmt, util::SubscriberInitExt, EnvFilter}; 16 | 17 | static INITIALIZED: OnceCell<()> = OnceCell::new(); 18 | 19 | pub struct Logger; 20 | 21 | impl Logger { 22 | /// Configure logging application-wide. 23 | /// 24 | /// Calling this multiple times is safe. Logger will be initialized only once. 25 | pub fn init() { 26 | INITIALIZED.get_or_init(|| { 27 | setup_logging(); 28 | get_config().log_info(); 29 | 30 | () 31 | }); 32 | } 33 | } 34 | 35 | fn setup_logging() { 36 | fmt() 37 | .with_env_filter( 38 | EnvFilter::builder() 39 | .with_default_directive(LevelFilter::INFO.into()) 40 | .from_env_lossy(), 41 | ) 42 | .with_ansi(get_config().general.tty) 43 | .with_file(false) 44 | .with_target(false) 45 | .finish() 46 | .init(); 47 | } 48 | -------------------------------------------------------------------------------- /rwf/src/model/callbacks.rs: -------------------------------------------------------------------------------- 1 | //! Implements automatically calling functions after model events, e.g. when a model is saved, created, etc. 2 | //! 3 | //! This is currently a work in progress. 4 | -------------------------------------------------------------------------------- /rwf/src/model/exists.rs: -------------------------------------------------------------------------------- 1 | //! Represents the result of `Query::exists`. 2 | use super::{Error, FromRow, Model, Value}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Exists { 6 | pub count: i64, 7 | } 8 | 9 | impl FromRow for Exists { 10 | fn from_row(row: tokio_postgres::Row) -> Result { 11 | Ok(Self { 12 | count: row.try_get("count")?, 13 | }) 14 | } 15 | } 16 | 17 | impl Model for Exists { 18 | fn table_name() -> &'static str { 19 | unimplemented!() 20 | } 21 | 22 | fn foreign_key() -> &'static str { 23 | unimplemented!() 24 | } 25 | 26 | fn column_names() -> &'static [&'static str] { 27 | &[] 28 | } 29 | 30 | fn values(&self) -> Vec { 31 | vec![] 32 | } 33 | 34 | fn id(&self) -> Value { 35 | Value::Null 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rwf/src/model/explain.rs: -------------------------------------------------------------------------------- 1 | //! Represents the result of executing `EXPLAIN ;` 2 | use super::{Error, FromRow, Model, Value}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Explain { 6 | plan: String, 7 | } 8 | 9 | impl Model for Explain { 10 | fn table_name() -> &'static str { 11 | unimplemented!() 12 | } 13 | 14 | fn foreign_key() -> &'static str { 15 | unimplemented!() 16 | } 17 | 18 | fn column_names() -> &'static [&'static str] { 19 | &[] 20 | } 21 | 22 | fn values(&self) -> Vec { 23 | vec![] 24 | } 25 | 26 | fn id(&self) -> Value { 27 | Value::Null 28 | } 29 | } 30 | 31 | impl std::fmt::Display for Explain { 32 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 33 | write!(f, "{}", self.plan.trim()) 34 | } 35 | } 36 | 37 | impl FromRow for Explain { 38 | fn from_row(row: tokio_postgres::Row) -> Result { 39 | let plan = row.try_get(0)?; 40 | Ok(Self { plan }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /rwf/src/model/limit.rs: -------------------------------------------------------------------------------- 1 | //! Implements the `LIMIT` and `OFFSET` SQL entities. 2 | use super::ToSql; 3 | 4 | #[derive(Debug, Default, Clone)] 5 | pub struct Limit { 6 | limit: Option, 7 | offset: Option, 8 | } 9 | 10 | impl ToSql for Limit { 11 | fn to_sql(&self) -> String { 12 | let mut limit = String::new(); 13 | if let Some(ref rows) = self.limit { 14 | limit += format!(" LIMIT {}", rows).as_str(); 15 | } 16 | 17 | if let Some(ref offset) = self.offset { 18 | limit += format!(" OFFSET {}", offset).as_str(); 19 | } 20 | 21 | limit 22 | } 23 | } 24 | 25 | impl Limit { 26 | pub fn new(n: i64) -> Self { 27 | Self { 28 | limit: Some(n), 29 | offset: None, 30 | } 31 | } 32 | 33 | pub fn offset(mut self, offset: i64) -> Self { 34 | self.offset = Some(offset); 35 | self 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rwf/src/model/lock.rs: -------------------------------------------------------------------------------- 1 | //! Implements `FOR UPDATE` SQL locking primitive. 2 | use super::ToSql; 3 | 4 | #[derive(Debug, Default, Clone)] 5 | pub struct Lock { 6 | lock: bool, 7 | skip_locked: bool, 8 | } 9 | 10 | impl Lock { 11 | pub fn new() -> Self { 12 | Self { 13 | lock: true, 14 | skip_locked: false, 15 | } 16 | } 17 | 18 | pub fn skip_locked(mut self) -> Self { 19 | self.skip_locked = true; 20 | self 21 | } 22 | } 23 | 24 | impl ToSql for Lock { 25 | fn to_sql(&self) -> String { 26 | let lock = if self.lock { " FOR UPDATE" } else { "" }; 27 | let skip_locked = if self.skip_locked { " SKIP LOCKED" } else { "" }; 28 | format!("{}{}", lock, skip_locked) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /rwf/src/model/picked.rs: -------------------------------------------------------------------------------- 1 | //! Select only a few columns. 2 | #![allow(dead_code, unused_variables)] 3 | 4 | use std::collections::HashMap; 5 | 6 | use super::*; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct Picked { 10 | pub select: Select, 11 | columns: HashMap, 12 | } 13 | 14 | impl Picked { 15 | pub fn group(mut self, group: &[impl ToColumn]) -> Self { 16 | self.select = self.select.group(group); 17 | self 18 | } 19 | 20 | fn from_row(&self, row: tokio_postgres::Row) -> Result { 21 | todo!() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /rwf/src/model/placeholders.rs: -------------------------------------------------------------------------------- 1 | //! `$1`, `$2`, etc. query placeholders used by prepared statements. 2 | use super::Value; 3 | 4 | #[derive(Debug, Clone, Default)] 5 | pub struct Placeholders { 6 | values: Vec, 7 | } 8 | 9 | impl Placeholders { 10 | pub fn new() -> Self { 11 | Self { values: vec![] } 12 | } 13 | 14 | pub fn add(&mut self, value: &Value) -> Value { 15 | let id = self.values.len() + 1; 16 | self.values.push(value.clone()); 17 | Value::Placeholder(id as i32) 18 | } 19 | 20 | pub fn get(&self, index: i32) -> Option<&Value> { 21 | self.values.get(index as usize - 1) 22 | } 23 | 24 | pub fn values(&self) -> Vec<&(dyn tokio_postgres::types::ToSql + Sync)> { 25 | self.values 26 | .iter() 27 | .map(|v| v as &(dyn tokio_postgres::types::ToSql + Sync)) 28 | .collect() 29 | } 30 | 31 | pub fn id(&self) -> i32 { 32 | self.values().len() as i32 + 1 33 | } 34 | } 35 | 36 | impl From> for Placeholders { 37 | fn from(values: Vec) -> Self { 38 | Placeholders { values } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /rwf/src/model/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Include all types in here to use the ORM ergonomically. 2 | //! 3 | //! These types are covered by [`crate::prelude`]. 4 | pub use super::{Error, Model, Pool, Scope, ToValue, Value}; 5 | -------------------------------------------------------------------------------- /rwf/src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! A collection of types, methods and macros 2 | //! which when imported make Rwf development ergonomic and easy. 3 | //! 4 | //! We recommend you import these whenever you work with Rwf primitives: 5 | //! 6 | //! ``` 7 | //! use rwf::prelude::*; 8 | //! ``` 9 | pub use crate::comms::Comms; 10 | pub use crate::config::Config; 11 | pub use crate::controller::{auth::SessionAuth, AuthHandler}; 12 | pub use crate::controller::{ 13 | Authentication, Controller, Error, ModelController, PageController, RestController, SessionId, 14 | }; 15 | pub use crate::http::{Cookie, CookieBuilder, Message, Method, Request, Response, ToMessage}; 16 | pub use crate::job::{queue_async, queue_delay, Job}; 17 | pub use crate::logging::Logger; 18 | pub use crate::model::{pool::ToConnectionRequest, Migrations, Model, Pool, Scope, ToSql, ToValue}; 19 | pub use crate::view::{Template, ToTemplateValue, TurboStream}; 20 | 21 | /// A macro to easily implement async traits methods. 22 | pub use async_trait::async_trait; 23 | 24 | pub use time::{Duration, OffsetDateTime}; 25 | pub use tokio; 26 | 27 | pub use macros::{ 28 | context, controller, crud, engine, render, render_include, rest, route, turbo_stream, 29 | }; 30 | pub use rwf_macros as macros; 31 | pub use serde::{Deserialize, Serialize}; 32 | pub use uuid::Uuid; 33 | -------------------------------------------------------------------------------- /rwf/src/view/mod.rs: -------------------------------------------------------------------------------- 1 | //! Dynamic templates and views, the **V** in MVC. 2 | //! 3 | //! Rwf templates are inspired from multiple other languages like ERB and Jinja2. The templates are dynamic, 4 | //! meaning they are interpreted at runtime. This allows for smoother local development experience, while the 5 | //! template cache in production makes this performant as well. 6 | //! 7 | //! # Example 8 | //! 9 | //! ``` 10 | //! # use rwf::view::*; 11 | //! let template = Template::from_str("

    <%= title %>

    ").unwrap(); 12 | //! let mut context = Context::new(); 13 | //! 14 | //! context.set("title", "Hello from Rwf!").unwrap(); 15 | //! 16 | //! let rendered = template.render(&context).unwrap(); 17 | //! 18 | //! assert_eq!(rendered, "

    Hello from Rwf!

    "); 19 | //! ``` 20 | //! 21 | //! # User guides 22 | //! 23 | //! See [documentation](https://levkk.github.io/rwf/views/) on how to use templates. 24 | pub mod cache; 25 | pub mod prelude; 26 | pub mod template; 27 | pub mod turbo; 28 | 29 | pub use cache::Templates; 30 | pub use template::Context; 31 | pub use template::Error; 32 | pub use template::Template; 33 | pub use turbo::TurboStream; 34 | 35 | pub use template::{ToTemplateValue, Value}; 36 | -------------------------------------------------------------------------------- /rwf/src/view/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Types useful when working with templates. 2 | pub use super::template::{ToTemplateValue, Value}; 3 | pub use super::Template; 4 | pub use super::Templates; 5 | -------------------------------------------------------------------------------- /rwf/src/view/template/head.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 24 | -------------------------------------------------------------------------------- /rwf/src/view/template/language.txt: -------------------------------------------------------------------------------- 1 | program ::= [statement] 2 | statement ::= print expression 3 | | if expression then [statement] end 4 | | if expression then [statement] else statement end 5 | | if expression then [statement] elsif expression [statement] [..elseif] else [statement] end 6 | | for variable in list do [statement] end 7 | expression ::= 8 | "(" expression binary_op expression ")" 9 | | unary_op expression 10 | | term 11 | | term.function 12 | term ::= constant | variable | function | [expression] 13 | constant ::= integer | float | string | [constant] 14 | binary_op ::= "+" | "-" | "*" | "/" | "%" | "==" | "!=" | "<" | "<=" | ">" | ">=" | "&&" | "||" 15 | unary_op ::= "-" | "!" | "+" 16 | -------------------------------------------------------------------------------- /rwf/src/view/template/language/mod.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the template language. 2 | //! 3 | //! Includes the parser and runtime. 4 | pub mod expression; 5 | pub mod op; 6 | pub mod program; 7 | pub mod statement; 8 | pub mod term; 9 | 10 | pub use expression::Expression; 11 | pub use op::Op; 12 | pub use program::Program; 13 | pub use statement::Statement; 14 | pub use term::Term; 15 | -------------------------------------------------------------------------------- /rwf/src/view/template/turbo-stream.html: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /rwf/src/view/turbo/stream.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /scripts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1-bullseye AS builder 2 | COPY . /build 3 | WORKDIR /build 4 | RUN cargo build --release 5 | 6 | FROM debian:bullseye 7 | COPY --from=builder /build/target/release /app/app 8 | COPY templates /app/templates 9 | COPY migrations /app/migrations 10 | COPY static /app/static 11 | WORKDIR /app 12 | CMD ["app"] 13 | -------------------------------------------------------------------------------- /scripts/build_cli.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "$0" )" && pwd )" 3 | pushd "$DIR/.." 4 | cargo build --bin rwf-cli 5 | cp target/debug/rwf-cli ~/.cargo/bin/rwf-cli 6 | -------------------------------------------------------------------------------- /scripts/check_publish.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Checks that the current Cargo.toml package version 4 | # matches the git tag. If the versions match, publish the crate 5 | # to crates.io. 6 | # 7 | set -ex 8 | 9 | CRATE="$(cargo read-manifest | jq '.name' -r)" 10 | VERSION="$(cargo read-manifest | jq '.version' -r)" 11 | 12 | # Test crate isn't published already 13 | if test $(cargo search "$CRATE" | grep "$CRATE =" | awk '{print $3}') = "\"$VERSION\""; then 14 | echo "Crate has been published already, aborting" 15 | exit 1 16 | else 17 | echo "Crate has not been published" 18 | fi 19 | 20 | if ! test "v$VERSION" = $(git describe --exact-match --tags); then 21 | echo "Latest git tag does not match crate version, skipping" 22 | exit 0 23 | else 24 | echo "All checks passed, publishing crate" 25 | fi 26 | 27 | if [[ ! -z "${CRATES_IO_TOKEN}" ]]; then 28 | cargo login "${CRATES_IO_TOKEN}" 29 | fi 30 | 31 | cargo publish 32 | -------------------------------------------------------------------------------- /scripts/docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR="$( cd "$( dirname "$0" )" && pwd )" 3 | cd "$DIR" 4 | cd ../docs 5 | 6 | source venv/bin/activate 7 | mkdocs serve 8 | --------------------------------------------------------------------------------