example: loco-todo-list (#2092)
* example: loco-todo-list * fmt * Cargo.lock * Disabled integration test for GitHub CI * fmt * Update Cargo.toml
This commit is contained in:
parent
76f73e778d
commit
7f25da3e2b
1
.github/workflows/rust.yml
vendored
1
.github/workflows/rust.yml
vendored
@ -239,6 +239,7 @@ jobs:
|
|||||||
examples/basic,
|
examples/basic,
|
||||||
examples/graphql_example,
|
examples/graphql_example,
|
||||||
examples/jsonrpsee_example,
|
examples/jsonrpsee_example,
|
||||||
|
examples/loco_example,
|
||||||
examples/poem_example,
|
examples/poem_example,
|
||||||
examples/proxy_gluesql_example,
|
examples/proxy_gluesql_example,
|
||||||
examples/rocket_example,
|
examples/rocket_example,
|
||||||
|
3
examples/loco_example/.cargo/config.toml
Normal file
3
examples/loco_example/.cargo/config.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[alias]
|
||||||
|
loco = "run --"
|
||||||
|
playground = "run --example playground"
|
5
examples/loco_example/.dockerignore
Normal file
5
examples/loco_example/.dockerignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
target
|
||||||
|
dockerfile
|
||||||
|
.dockerignore
|
||||||
|
.git
|
||||||
|
.gitignore
|
17
examples/loco_example/.gitignore
vendored
Normal file
17
examples/loco_example/.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
**/config/local.yaml
|
||||||
|
**/config/*.local.yaml
|
||||||
|
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
!Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
|
*.pdb
|
7
examples/loco_example/.rustfmt.toml
Normal file
7
examples/loco_example/.rustfmt.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
max_width = 100
|
||||||
|
comment_width = 80
|
||||||
|
wrap_comments = true
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
use_small_heuristics = "Default"
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
format_strings = true
|
4994
examples/loco_example/Cargo.lock
generated
Normal file
4994
examples/loco_example/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
examples/loco_example/Cargo.toml
Normal file
47
examples/loco_example/Cargo.toml
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
[workspace]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "todolist"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
loco-rs = { version = "0.1.7" }
|
||||||
|
migration = { path = "migration" }
|
||||||
|
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
eyre = "0.6"
|
||||||
|
tokio = { version = "1.33.0", default-features = false }
|
||||||
|
async-trait = "0.1.74"
|
||||||
|
tracing = "0.1.40"
|
||||||
|
chrono = "0.4"
|
||||||
|
validator = { version = "0.16" }
|
||||||
|
axum = "0.7.1"
|
||||||
|
include_dir = "0.7"
|
||||||
|
uuid = { version = "1.6.0", features = ["v4"] }
|
||||||
|
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] }
|
||||||
|
|
||||||
|
[dependencies.sea-orm]
|
||||||
|
# path = "../../" # remove this line in your own project
|
||||||
|
version = "0.12.4" # sea-orm version
|
||||||
|
features = [
|
||||||
|
"sqlx-sqlite",
|
||||||
|
"sqlx-postgres",
|
||||||
|
"runtime-tokio-rustls",
|
||||||
|
"macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "todolist-cli"
|
||||||
|
path = "src/bin/main.rs"
|
||||||
|
required-features = []
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serial_test = "2.0.0"
|
||||||
|
rstest = "0.18.2"
|
||||||
|
loco-rs = { version = "0.1.7", features = ["testing"] }
|
||||||
|
insta = { version = "1.34.0", features = ["redactions", "yaml", "filters"] }
|
95
examples/loco_example/README.md
Normal file
95
examples/loco_example/README.md
Normal file
@ -0,0 +1,95 @@
|
|||||||
|

|
||||||
|
|
||||||
|
> Adapted from https://github.com/loco-rs/todo-list
|
||||||
|
|
||||||
|
# Loco with SeaORM example todo list
|
||||||
|
|
||||||
|
Build your own todo list website using Loco. Follow the step-by-step guide [here](<(https://loco.rs/blog/frontend-website/)>) to create it effortlessly.
|
||||||
|
|
||||||
|
## Build Client
|
||||||
|
|
||||||
|
Navigate to the `frontend` directory and build the client:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd frontend && yarn install && yarn build
|
||||||
|
|
||||||
|
vite v5.0.8 building for production...
|
||||||
|
✓ 120 modules transformed.
|
||||||
|
dist/index.html 0.46 kB │ gzip: 0.30 kB
|
||||||
|
dist/assets/index-AbTMZIjW.css 1.26 kB │ gzip: 0.65 kB
|
||||||
|
dist/assets/index-MJFpQvzE.js 235.64 kB │ gzip: 75.58 kB
|
||||||
|
✓ built in 2.01s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Run Locally
|
||||||
|
|
||||||
|
You need:
|
||||||
|
|
||||||
|
* A local postgres instance
|
||||||
|
|
||||||
|
Check out your development [configuration](config/development.yaml).
|
||||||
|
|
||||||
|
> To configure a database , please run a local postgres database with <code>loco:loco</code> and a db named <code>[app name]_development.</code>:
|
||||||
|
<code>docker run -d -p 5432:5432 -e POSTGRES_USER=loco -e POSTGRES_DB=[app name]_development -e POSTGRES_PASSWORD="loco" postgres:15.3-alpine</code>
|
||||||
|
|
||||||
|
Execute the following command to run your todo list website locally, serving static assets from `frontend/dist`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cargo loco start
|
||||||
|
|
||||||
|
Finished dev [unoptimized + debuginfo] target(s) in 0.53s
|
||||||
|
Running `target/debug/todolist-cli start`
|
||||||
|
2024-02-01T08:49:41.070430Z INFO loco_rs::db: auto migrating
|
||||||
|
2024-02-01T08:49:41.073698Z INFO sea_orm_migration::migrator: Applying all pending migrations
|
||||||
|
2024-02-01T08:49:41.078191Z INFO sea_orm_migration::migrator: No pending migrations
|
||||||
|
2024-02-01T08:49:41.100557Z INFO loco_rs::controller::app_routes: [GET] /api/_ping
|
||||||
|
2024-02-01T08:49:41.100617Z INFO loco_rs::controller::app_routes: [GET] /api/_health
|
||||||
|
2024-02-01T08:49:41.100667Z INFO loco_rs::controller::app_routes: [GET] /api/notes
|
||||||
|
2024-02-01T08:49:41.100702Z INFO loco_rs::controller::app_routes: [POST] /api/notes
|
||||||
|
2024-02-01T08:49:41.100738Z INFO loco_rs::controller::app_routes: [GET] /api/notes/:id
|
||||||
|
2024-02-01T08:49:41.100791Z INFO loco_rs::controller::app_routes: [DELETE] /api/notes/:id
|
||||||
|
2024-02-01T08:49:41.100817Z INFO loco_rs::controller::app_routes: [POST] /api/notes/:id
|
||||||
|
2024-02-01T08:49:41.100934Z INFO loco_rs::controller::app_routes: [Middleware] Adding limit payload data="5mb"
|
||||||
|
2024-02-01T08:49:41.101017Z INFO loco_rs::controller::app_routes: [Middleware] Adding log trace id
|
||||||
|
2024-02-01T08:49:41.101057Z INFO loco_rs::controller::app_routes: [Middleware] Adding timeout layer
|
||||||
|
2024-02-01T08:49:41.101192Z INFO loco_rs::controller::app_routes: [Middleware] Adding cors
|
||||||
|
2024-02-01T08:49:41.101241Z INFO loco_rs::controller::app_routes: [Middleware] Adding static
|
||||||
|
|
||||||
|
▄ ▀
|
||||||
|
▀ ▄
|
||||||
|
▄ ▀ ▄ ▄ ▄▀
|
||||||
|
▄ ▀▄▄
|
||||||
|
▄ ▀ ▀ ▀▄▀█▄
|
||||||
|
▀█▄
|
||||||
|
▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄ ▀▀█
|
||||||
|
██████ █████ ███ █████ ███ █████ ███ ▀█
|
||||||
|
██████ █████ ███ █████ ▀▀▀ █████ ███ ▄█▄
|
||||||
|
██████ █████ ███ █████ █████ ███ ████▄
|
||||||
|
██████ █████ ███ █████ ▄▄▄ █████ ███ █████
|
||||||
|
██████ █████ ███ ████ ███ █████ ███ ████▀
|
||||||
|
▀▀▀██▄ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀▀▀ ██▀
|
||||||
|
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
|
||||||
|
https://loco.rs
|
||||||
|
|
||||||
|
environment: development
|
||||||
|
database: automigrate
|
||||||
|
logger: debug
|
||||||
|
compilation: debug
|
||||||
|
modes: server
|
||||||
|
|
||||||
|
listening on port 3000
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To develop the UI, run the following commands:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cd frontend && yarn install && yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ cargo loco start
|
||||||
|
```
|
BIN
examples/loco_example/Screenshot.png
Normal file
BIN
examples/loco_example/Screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 633 KiB |
72
examples/loco_example/config/development.yaml
Normal file
72
examples/loco_example/config/development.yaml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# Loco configuration file documentation
|
||||||
|
|
||||||
|
# Application logging configuration
|
||||||
|
logger:
|
||||||
|
# Enable or disable logging.
|
||||||
|
enable: true
|
||||||
|
# Log level, options: trace, debug, info, warn or error.
|
||||||
|
level: debug
|
||||||
|
# Define the logging format. options: compact, pretty or Json
|
||||||
|
format: compact
|
||||||
|
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||||
|
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
|
||||||
|
# override_filter: trace
|
||||||
|
|
||||||
|
# Web server configuration
|
||||||
|
server:
|
||||||
|
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||||
|
port: 3000
|
||||||
|
# The UI hostname or IP address that mailers will point to.
|
||||||
|
host: http://localhost
|
||||||
|
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||||
|
middlewares:
|
||||||
|
# Allows to limit the payload size request. payload that bigger than this file will blocked the request.
|
||||||
|
limit_payload:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# the limit size. can be b,kb,kib,mb,mib,gb,gib
|
||||||
|
body_limit: 5mb
|
||||||
|
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||||
|
logger:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# when your code is panicked, the request still returns 500 status code.
|
||||||
|
catch_panic:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||||
|
timeout_request:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# Duration time in milliseconds.
|
||||||
|
timeout: 5000
|
||||||
|
cors:
|
||||||
|
enable: true
|
||||||
|
static:
|
||||||
|
enable: true
|
||||||
|
must_exist: true
|
||||||
|
folder:
|
||||||
|
uri: "/"
|
||||||
|
path: "frontend/dist"
|
||||||
|
fallback: "frontend/dist/index.html"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
database:
|
||||||
|
# Database connection URI
|
||||||
|
uri: postgres://loco:loco@localhost:5432/loco_app
|
||||||
|
# When enabled, the sql query will be logged.
|
||||||
|
enable_logging: false
|
||||||
|
# Set the timeout duration when acquiring a connection.
|
||||||
|
connect_timeout: 500
|
||||||
|
# Set the idle duration before closing a connection.
|
||||||
|
idle_timeout: 500
|
||||||
|
# Minimum number of connections for a pool.
|
||||||
|
min_connections: 1
|
||||||
|
# Maximum number of connections for a pool.
|
||||||
|
max_connections: 1
|
||||||
|
# Run migration up when application loaded
|
||||||
|
auto_migrate: true
|
||||||
|
# Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||||
|
dangerously_truncate: false
|
||||||
|
# Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||||
|
dangerously_recreate: false
|
72
examples/loco_example/config/production.yaml
Normal file
72
examples/loco_example/config/production.yaml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# Loco configuration file documentation
|
||||||
|
|
||||||
|
# Application logging configuration
|
||||||
|
logger:
|
||||||
|
# Enable or disable logging.
|
||||||
|
enable: true
|
||||||
|
# Log level, options: trace, debug, info, warn or error.
|
||||||
|
level: debug
|
||||||
|
# Define the logging format. options: compact, pretty or Json
|
||||||
|
format: compact
|
||||||
|
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||||
|
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
|
||||||
|
# override_filter: trace
|
||||||
|
|
||||||
|
# Web server configuration
|
||||||
|
server:
|
||||||
|
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||||
|
port: 3000
|
||||||
|
# The UI hostname or IP address that mailers will point to.
|
||||||
|
host: http://localhost
|
||||||
|
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||||
|
middlewares:
|
||||||
|
# Allows to limit the payload size request. payload that bigger than this file will blocked the request.
|
||||||
|
limit_payload:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# the limit size. can be b,kb,kib,mb,mib,gb,gib
|
||||||
|
body_limit: 5mb
|
||||||
|
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||||
|
logger:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# when your code is panicked, the request still returns 500 status code.
|
||||||
|
catch_panic:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||||
|
timeout_request:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# Duration time in milliseconds.
|
||||||
|
timeout: 5000
|
||||||
|
cors:
|
||||||
|
enable: true
|
||||||
|
static:
|
||||||
|
enable: true
|
||||||
|
must_exist: true
|
||||||
|
folder:
|
||||||
|
uri: "/"
|
||||||
|
path: "frontend/dist"
|
||||||
|
fallback: "frontend/dist/index.html"
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
database:
|
||||||
|
# Database connection URI
|
||||||
|
uri: sqlite://db.sqlite?mode=rwc
|
||||||
|
# When enabled, the sql query will be logged.
|
||||||
|
enable_logging: false
|
||||||
|
# Set the timeout duration when acquiring a connection.
|
||||||
|
connect_timeout: 500
|
||||||
|
# Set the idle duration before closing a connection.
|
||||||
|
idle_timeout: 500
|
||||||
|
# Minimum number of connections for a pool.
|
||||||
|
min_connections: 1
|
||||||
|
# Maximum number of connections for a pool.
|
||||||
|
max_connections: 1
|
||||||
|
# Run migration up when application loaded
|
||||||
|
auto_migrate: true
|
||||||
|
# Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||||
|
dangerously_truncate: false
|
||||||
|
# Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||||
|
dangerously_recreate: true
|
76
examples/loco_example/config/test.yaml
Normal file
76
examples/loco_example/config/test.yaml
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# Loco configuration file documentation
|
||||||
|
|
||||||
|
# Application logging configuration
|
||||||
|
logger:
|
||||||
|
# Enable or disable logging.
|
||||||
|
enable: false
|
||||||
|
# Log level, options: trace, debug, info, warn or error.
|
||||||
|
level: debug
|
||||||
|
# Define the logging format. options: compact, pretty or Json
|
||||||
|
format: compact
|
||||||
|
# By default the logger has filtering only logs that came from your code or logs that came from `loco` framework. to see all third party libraries
|
||||||
|
# Uncomment the line below to override to see all third party libraries you can enable this config and override the logger filters.
|
||||||
|
# override_filter: trace
|
||||||
|
|
||||||
|
# Web server configuration
|
||||||
|
server:
|
||||||
|
# Port on which the server will listen. the server binding is 0.0.0.0:{PORT}
|
||||||
|
port: 3000
|
||||||
|
# The UI hostname or IP address that mailers will point to.
|
||||||
|
host: http://localhost
|
||||||
|
# Out of the box middleware configuration. to disable middleware you can changed the `enable` field to `false` of comment the middleware block
|
||||||
|
middlewares:
|
||||||
|
# Allows to limit the payload size request. payload that bigger than this file will blocked the request.
|
||||||
|
limit_payload:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# the limit size. can be b,kb,kib,mb,mib,gb,gib
|
||||||
|
body_limit: 5mb
|
||||||
|
# Generating a unique request ID and enhancing logging with additional information such as the start and completion of request processing, latency, status code, and other request details.
|
||||||
|
logger:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# when your code is panicked, the request still returns 500 status code.
|
||||||
|
catch_panic:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# Timeout for incoming requests middleware. requests that take more time from the configuration will cute and 408 status code will returned.
|
||||||
|
timeout_request:
|
||||||
|
# Enable/Disable the middleware.
|
||||||
|
enable: true
|
||||||
|
# Duration time in milliseconds.
|
||||||
|
timeout: 5000
|
||||||
|
cors:
|
||||||
|
enable: true
|
||||||
|
# Set the value of the [`Access-Control-Allow-Origin`][mdn] header
|
||||||
|
# allow_origins:
|
||||||
|
# - https://loco.rs
|
||||||
|
# Set the value of the [`Access-Control-Allow-Headers`][mdn] header
|
||||||
|
# allow_headers:
|
||||||
|
# - Content-Type
|
||||||
|
# Set the value of the [`Access-Control-Allow-Methods`][mdn] header
|
||||||
|
# allow_methods:
|
||||||
|
# - POST
|
||||||
|
# Set the value of the [`Access-Control-Max-Age`][mdn] header in seconds
|
||||||
|
# max_age: 3600
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
database:
|
||||||
|
# Database connection URI
|
||||||
|
uri: postgres://loco:loco@localhost:5432/loco_app
|
||||||
|
# When enabled, the sql query will be logged.
|
||||||
|
enable_logging: false
|
||||||
|
# Set the timeout duration when acquiring a connection.
|
||||||
|
connect_timeout: 500
|
||||||
|
# Set the idle duration before closing a connection.
|
||||||
|
idle_timeout: 500
|
||||||
|
# Minimum number of connections for a pool.
|
||||||
|
min_connections: 1
|
||||||
|
# Maximum number of connections for a pool.
|
||||||
|
max_connections: 1
|
||||||
|
# Run migration up when application loaded
|
||||||
|
auto_migrate: true
|
||||||
|
# Truncate database when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||||
|
dangerously_truncate: true
|
||||||
|
# Recreating schema when application loaded. This is a dangerous operation, make sure that you using this flag only on dev environments or test mode
|
||||||
|
dangerously_recreate: false
|
18
examples/loco_example/dockerfile
Normal file
18
examples/loco_example/dockerfile
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
FROM rust:1.74-slim as builder
|
||||||
|
|
||||||
|
WORKDIR /usr/src/
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
WORKDIR /usr/app
|
||||||
|
|
||||||
|
COPY --from=builder /usr/src/frontend/dist /usr/app/frontend/dist
|
||||||
|
COPY --from=builder /usr/src/frontend/dist/index.html /usr/app/frontend/dist/index.html
|
||||||
|
COPY --from=builder /usr/src/config /usr/app/config
|
||||||
|
COPY --from=builder /usr/src/target/release/todolist-cli /usr/app/todolist-cli
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/app/todolist-cli"]
|
20
examples/loco_example/frontend/.eslintrc.cjs
Normal file
20
examples/loco_example/frontend/.eslintrc.cjs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
env: { browser: true, es2020: true },
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:react/recommended',
|
||||||
|
'plugin:react/jsx-runtime',
|
||||||
|
'plugin:react-hooks/recommended',
|
||||||
|
],
|
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'],
|
||||||
|
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
|
||||||
|
settings: { react: { version: '18.2' } },
|
||||||
|
plugins: ['react-refresh'],
|
||||||
|
rules: {
|
||||||
|
'react-refresh/only-export-components': [
|
||||||
|
'warn',
|
||||||
|
{ allowConstantExport: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
27
examples/loco_example/frontend/.gitignore
vendored
Normal file
27
examples/loco_example/frontend/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
.yarn
|
||||||
|
.yarnrc.yml
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
13
examples/loco_example/frontend/index.html
Normal file
13
examples/loco_example/frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite + React</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
29
examples/loco_example/frontend/package.json
Normal file
29
examples/loco_example/frontend/package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "frontent",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.2",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"react-query": "3.39.3",
|
||||||
|
"react-router-dom": "6.15.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "18.2.43",
|
||||||
|
"@types/react-dom": "18.2.17",
|
||||||
|
"@vitejs/plugin-react": "4.2.1",
|
||||||
|
"eslint": "8.55.0",
|
||||||
|
"eslint-plugin-react": "7.33.2",
|
||||||
|
"eslint-plugin-react-hooks": "4.6.0",
|
||||||
|
"eslint-plugin-react-refresh": "0.4.5",
|
||||||
|
"vite": "5.0.8"
|
||||||
|
}
|
||||||
|
}
|
2898
examples/loco_example/frontend/pnpm-lock.yaml
generated
Normal file
2898
examples/loco_example/frontend/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
37
examples/loco_example/frontend/src/App.css
Normal file
37
examples/loco_example/frontend/src/App.css
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#root {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
height: 10em;
|
||||||
|
padding: 1.5em;
|
||||||
|
will-change: filter;
|
||||||
|
transition: filter 300ms;
|
||||||
|
}
|
||||||
|
.logo:hover {
|
||||||
|
filter: drop-shadow(0 0 2em #ff1111aa);
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-list .todo{
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-add {
|
||||||
|
margin-top: 20px;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.todo-add input{
|
||||||
|
width: 70%;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.todo-add button{
|
||||||
|
float: right;
|
||||||
|
}
|
163
examples/loco_example/frontend/src/App.jsx
Normal file
163
examples/loco_example/frontend/src/App.jsx
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import {
|
||||||
|
useQuery,
|
||||||
|
useMutation,
|
||||||
|
useQueryClient,
|
||||||
|
} from 'react-query'
|
||||||
|
import axios from 'axios';
|
||||||
|
import './App.css'
|
||||||
|
import { Routes, Route, Outlet, Link } from "react-router-dom";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Loco Todo List</h1>
|
||||||
|
<Routes >
|
||||||
|
<Route path="/" element={<Layout />}>
|
||||||
|
<Route index element={<TodoList />} />
|
||||||
|
<Route path="*" element={<NoMatch />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Layout() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
<a href="https://loco.rs" target="_blank" rel="noreferrer">
|
||||||
|
<img src="https://raw.githubusercontent.com/loco-rs/todo-list-example/4b8ade3ddfb5a2e076e5188cdc8f6cd404f3fdd1/frontend/src/assets/loco.svg" className="logo" alt="Loco logo" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TodoList() {
|
||||||
|
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const fetchTodos = async () => {
|
||||||
|
const { data } = await axios.get(`api/notes`)
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { isLoading, isError, data = [] } = useQuery(["todos"], fetchTodos); // a hook provided by react-query, it takes a key(name) and function that returns a promise
|
||||||
|
|
||||||
|
|
||||||
|
const remove = async (id) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(`api/notes/${id}`);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting todo:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const mutation = useMutation(remove, {
|
||||||
|
onSuccess: () => {
|
||||||
|
queryClient.invalidateQueries(["todos"]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(data)
|
||||||
|
if (isLoading)
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<p>isLoading...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isError)
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<p>Could not get todo list from the server</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<AddTodo />
|
||||||
|
<div className="todo-list">
|
||||||
|
{data.map((todo) => (
|
||||||
|
<div key={todo.id} className="todo" >
|
||||||
|
<div>
|
||||||
|
<div> <button onClick={() => {
|
||||||
|
mutation.mutate(todo.id);
|
||||||
|
}}>x</button> {todo.title}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function AddTodo() {
|
||||||
|
const [todo, setTodo] = useState("");
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const add = async (newTodo) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`api/notes`, {
|
||||||
|
title: newTodo,
|
||||||
|
content: newTodo,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error posting todo:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const mutation = useMutation(add, {
|
||||||
|
onSuccess: () => {
|
||||||
|
setTodo("")
|
||||||
|
queryClient.invalidateQueries(["todos"]);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='todo-add'>
|
||||||
|
<input
|
||||||
|
value={todo}
|
||||||
|
onChange={(event) => {
|
||||||
|
setTodo(event.target.value);
|
||||||
|
}}
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (todo !== "") {
|
||||||
|
mutation.mutate(todo);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NoMatch() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2>Sorry, this page not found</h2>
|
||||||
|
<p>
|
||||||
|
<Link to="/">Go to the todo list page</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
5116
examples/loco_example/frontend/src/assets/loco.svg
Normal file
5116
examples/loco_example/frontend/src/assets/loco.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 2.2 MiB |
68
examples/loco_example/frontend/src/index.css
Normal file
68
examples/loco_example/frontend/src/index.css
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
:root {
|
||||||
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-weight: 400;
|
||||||
|
|
||||||
|
color-scheme: light dark;
|
||||||
|
color: rgba(255, 255, 255, 0.87);
|
||||||
|
background-color: #242424;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #646cff;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #535bf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
place-items: center;
|
||||||
|
min-width: 320px;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3.2em;
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
padding: 0.6em 1.2em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: 500;
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: #1a1a1a;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.25s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
border-color: #646cff;
|
||||||
|
}
|
||||||
|
button:focus,
|
||||||
|
button:focus-visible {
|
||||||
|
outline: 4px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: light) {
|
||||||
|
:root {
|
||||||
|
color: #213547;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #747bff;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
}
|
21
examples/loco_example/frontend/src/main.jsx
Normal file
21
examples/loco_example/frontend/src/main.jsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import ReactDOM from 'react-dom/client'
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import App from './App.jsx'
|
||||||
|
import './index.css'
|
||||||
|
import {
|
||||||
|
QueryClient,
|
||||||
|
QueryClientProvider,
|
||||||
|
} from 'react-query'
|
||||||
|
|
||||||
|
const queryClient = new QueryClient();
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App />
|
||||||
|
</BrowserRouter>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</React.StrictMode>
|
||||||
|
)
|
16
examples/loco_example/frontend/vite.config.js
Normal file
16
examples/loco_example/frontend/vite.config.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
server: {
|
||||||
|
proxy: {
|
||||||
|
"/api": {
|
||||||
|
target: "http://127.0.0.1:3000",
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
3979
examples/loco_example/frontend/yarn.lock
Normal file
3979
examples/loco_example/frontend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
23
examples/loco_example/migration/Cargo.toml
Normal file
23
examples/loco_example/migration/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "migration"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "migration"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-std = { version = "1", features = ["attributes", "tokio1"] }
|
||||||
|
loco-rs = { version = "0.1.6" }
|
||||||
|
|
||||||
|
[dependencies.sea-orm-migration]
|
||||||
|
# path = "../../../sea-orm-migration" # remove this line in your own project
|
||||||
|
version = "0.12.4" # sea-orm-migration version
|
||||||
|
features = [
|
||||||
|
# Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
|
||||||
|
# View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
|
||||||
|
# e.g.
|
||||||
|
"runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
|
||||||
|
]
|
41
examples/loco_example/migration/README.md
Normal file
41
examples/loco_example/migration/README.md
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# Running Migrator CLI
|
||||||
|
|
||||||
|
- Generate a new migration file
|
||||||
|
```sh
|
||||||
|
cargo run -- migrate generate MIGRATION_NAME
|
||||||
|
```
|
||||||
|
- Apply all pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
```sh
|
||||||
|
cargo run -- up
|
||||||
|
```
|
||||||
|
- Apply first 10 pending migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- up -n 10
|
||||||
|
```
|
||||||
|
- Rollback last applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down
|
||||||
|
```
|
||||||
|
- Rollback last 10 applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- down -n 10
|
||||||
|
```
|
||||||
|
- Drop all tables from the database, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- fresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations, then reapply all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- refresh
|
||||||
|
```
|
||||||
|
- Rollback all applied migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- reset
|
||||||
|
```
|
||||||
|
- Check the status of all migrations
|
||||||
|
```sh
|
||||||
|
cargo run -- status
|
||||||
|
```
|
14
examples/loco_example/migration/src/lib.rs
Normal file
14
examples/loco_example/migration/src/lib.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#![allow(elided_lifetimes_in_paths)]
|
||||||
|
#![allow(clippy::wildcard_imports)]
|
||||||
|
pub use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
mod m20231103_114510_notes;
|
||||||
|
|
||||||
|
pub struct Migrator;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigratorTrait for Migrator {
|
||||||
|
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||||
|
vec![Box::new(m20231103_114510_notes::Migration)]
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
use std::borrow::BorrowMut;
|
||||||
|
|
||||||
|
use loco_rs::schema::*;
|
||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[derive(DeriveMigrationName)]
|
||||||
|
pub struct Migration;
|
||||||
|
|
||||||
|
#[async_trait::async_trait]
|
||||||
|
impl MigrationTrait for Migration {
|
||||||
|
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.create_table(
|
||||||
|
table_auto(Notes::Table)
|
||||||
|
.col(pk_auto(Notes::Id).borrow_mut())
|
||||||
|
.col(string_null(Notes::Title).borrow_mut())
|
||||||
|
.col(string_null(Notes::Content).borrow_mut())
|
||||||
|
.to_owned(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||||
|
manager
|
||||||
|
.drop_table(Table::drop().table(Notes::Table).to_owned())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(DeriveIden)]
|
||||||
|
enum Notes {
|
||||||
|
Table,
|
||||||
|
Id,
|
||||||
|
Title,
|
||||||
|
Content,
|
||||||
|
}
|
6
examples/loco_example/migration/src/main.rs
Normal file
6
examples/loco_example/migration/src/main.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
use sea_orm_migration::prelude::*;
|
||||||
|
|
||||||
|
#[async_std::main]
|
||||||
|
async fn main() {
|
||||||
|
cli::run_cli(migration::Migrator).await;
|
||||||
|
}
|
42
examples/loco_example/src/app.rs
Normal file
42
examples/loco_example/src/app.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use loco_rs::{
|
||||||
|
app::{AppContext, Hooks},
|
||||||
|
controller::AppRoutes,
|
||||||
|
db::{self, truncate_table},
|
||||||
|
task::Tasks,
|
||||||
|
worker::Processor,
|
||||||
|
Result,
|
||||||
|
};
|
||||||
|
use sea_orm::DatabaseConnection;
|
||||||
|
|
||||||
|
use crate::{controllers, models::_entities::notes};
|
||||||
|
|
||||||
|
pub struct App;
|
||||||
|
#[async_trait]
|
||||||
|
impl Hooks for App {
|
||||||
|
fn app_name() -> &'static str {
|
||||||
|
env!("CARGO_CRATE_NAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn routes() -> AppRoutes {
|
||||||
|
AppRoutes::with_default_routes()
|
||||||
|
.prefix("/api")
|
||||||
|
.add_route(controllers::notes::routes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn connect_workers<'a>(_p: &'a mut Processor, _ctx: &'a AppContext) {}
|
||||||
|
|
||||||
|
fn register_tasks(_tasks: &mut Tasks) {}
|
||||||
|
|
||||||
|
async fn truncate(db: &DatabaseConnection) -> Result<()> {
|
||||||
|
truncate_table(db, notes::Entity).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn seed(db: &DatabaseConnection, base: &Path) -> Result<()> {
|
||||||
|
db::seed::<notes::ActiveModel>(db, &base.join("notes.yaml").display().to_string()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
8
examples/loco_example/src/bin/main.rs
Normal file
8
examples/loco_example/src/bin/main.rs
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
use loco_rs::cli;
|
||||||
|
use migration::Migrator;
|
||||||
|
use todolist::app::App;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> eyre::Result<()> {
|
||||||
|
cli::main::<App, Migrator>().await
|
||||||
|
}
|
1
examples/loco_example/src/controllers/mod.rs
Normal file
1
examples/loco_example/src/controllers/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod notes;
|
69
examples/loco_example/src/controllers/notes.rs
Normal file
69
examples/loco_example/src/controllers/notes.rs
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::unnecessary_struct_initialization)]
|
||||||
|
#![allow(clippy::unused_async)]
|
||||||
|
use loco_rs::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::models::_entities::notes::{ActiveModel, Entity, Model};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct Params {
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub content: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Params {
|
||||||
|
fn update(&self, item: &mut ActiveModel) {
|
||||||
|
item.title = Set(self.title.clone());
|
||||||
|
item.content = Set(self.content.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn load_item(ctx: &AppContext, id: i32) -> Result<Model> {
|
||||||
|
let item = Entity::find_by_id(id).one(&ctx.db).await?;
|
||||||
|
item.ok_or_else(|| Error::NotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list(State(ctx): State<AppContext>) -> Result<Json<Vec<Model>>> {
|
||||||
|
format::json(Entity::find().all(&ctx.db).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add(State(ctx): State<AppContext>, Json(params): Json<Params>) -> Result<Json<Model>> {
|
||||||
|
let mut item = ActiveModel {
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
params.update(&mut item);
|
||||||
|
let item = item.insert(&ctx.db).await?;
|
||||||
|
format::json(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(
|
||||||
|
Path(id): Path<i32>,
|
||||||
|
State(ctx): State<AppContext>,
|
||||||
|
Json(params): Json<Params>,
|
||||||
|
) -> Result<Json<Model>> {
|
||||||
|
let item = load_item(&ctx, id).await?;
|
||||||
|
let mut item = item.into_active_model();
|
||||||
|
params.update(&mut item);
|
||||||
|
let item = item.update(&ctx.db).await?;
|
||||||
|
format::json(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Result<()> {
|
||||||
|
load_item(&ctx, id).await?.delete(&ctx.db).await?;
|
||||||
|
format::empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_one(Path(id): Path<i32>, State(ctx): State<AppContext>) -> Result<Json<Model>> {
|
||||||
|
format::json(load_item(&ctx, id).await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn routes() -> Routes {
|
||||||
|
Routes::new()
|
||||||
|
.prefix("notes")
|
||||||
|
.add("/", get(list))
|
||||||
|
.add("/", post(add))
|
||||||
|
.add("/:id", get(get_one))
|
||||||
|
.add("/:id", delete(remove))
|
||||||
|
.add("/:id", post(update))
|
||||||
|
}
|
11
examples/loco_example/src/fixtures/notes.yaml
Normal file
11
examples/loco_example/src/fixtures/notes.yaml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
- id: 1
|
||||||
|
title: Loco note 1
|
||||||
|
content: Loco note 1 content
|
||||||
|
created_at: "2023-11-12T12:34:56.789"
|
||||||
|
updated_at: "2023-11-12T12:34:56.789"
|
||||||
|
- id: 2
|
||||||
|
title: Loco note 1
|
||||||
|
content: Loco note 1 content
|
||||||
|
created_at: "2023-11-12T12:34:56.789"
|
||||||
|
updated_at: "2023-11-12T12:34:56.789"
|
3
examples/loco_example/src/lib.rs
Normal file
3
examples/loco_example/src/lib.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod app;
|
||||||
|
pub mod controllers;
|
||||||
|
pub mod models;
|
5
examples/loco_example/src/models/_entities/mod.rs
Normal file
5
examples/loco_example/src/models/_entities/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||||
|
|
||||||
|
pub mod prelude;
|
||||||
|
|
||||||
|
pub mod notes;
|
18
examples/loco_example/src/models/_entities/notes.rs
Normal file
18
examples/loco_example/src/models/_entities/notes.rs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||||
|
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||||
|
#[sea_orm(table_name = "notes")]
|
||||||
|
pub struct Model {
|
||||||
|
pub created_at: DateTime,
|
||||||
|
pub updated_at: DateTime,
|
||||||
|
#[sea_orm(primary_key)]
|
||||||
|
pub id: i32,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub content: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||||
|
pub enum Relation {}
|
3
examples/loco_example/src/models/_entities/prelude.rs
Normal file
3
examples/loco_example/src/models/_entities/prelude.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.4
|
||||||
|
|
||||||
|
pub use super::notes::Entity as Notes;
|
2
examples/loco_example/src/models/mod.rs
Normal file
2
examples/loco_example/src/models/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pub mod _entities;
|
||||||
|
pub mod notes;
|
7
examples/loco_example/src/models/notes.rs
Normal file
7
examples/loco_example/src/models/notes.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
|
||||||
|
use super::_entities::notes::ActiveModel;
|
||||||
|
|
||||||
|
impl ActiveModelBehavior for ActiveModel {
|
||||||
|
// extend activemodel below (keep comment for generators)
|
||||||
|
}
|
3
examples/loco_example/tests/mod.rs
Normal file
3
examples/loco_example/tests/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod models;
|
||||||
|
mod requests;
|
||||||
|
mod tasks;
|
1
examples/loco_example/tests/models/mod.rs
Normal file
1
examples/loco_example/tests/models/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
1
examples/loco_example/tests/requests/mod.rs
Normal file
1
examples/loco_example/tests/requests/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
mod notes;
|
127
examples/loco_example/tests/requests/notes.rs
Normal file
127
examples/loco_example/tests/requests/notes.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use insta::{assert_debug_snapshot, with_settings};
|
||||||
|
use loco_rs::testing;
|
||||||
|
use migration::Migrator;
|
||||||
|
use sea_orm::entity::prelude::*;
|
||||||
|
use serial_test::serial;
|
||||||
|
use todolist::{app::App, models::_entities::notes::Entity};
|
||||||
|
|
||||||
|
// TODO: see how to dedup / extract this to app-local test utils
|
||||||
|
// not to framework, because that would require a runtime dep on insta
|
||||||
|
macro_rules! configure_insta {
|
||||||
|
($($expr:expr),*) => {
|
||||||
|
let mut settings = insta::Settings::clone_current();
|
||||||
|
settings.set_prepend_module_to_snapshot(false);
|
||||||
|
settings.set_snapshot_suffix("notes_request");
|
||||||
|
let _guard = settings.bind_to_scope();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled integration test for GitHub CI
|
||||||
|
/*
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_get_notes() {
|
||||||
|
configure_insta!();
|
||||||
|
|
||||||
|
testing::request::<App, Migrator, _, _>(|request, ctx| async move {
|
||||||
|
testing::seed::<App>(&ctx.db).await.unwrap();
|
||||||
|
|
||||||
|
let notes = request.get("/api/notes").await;
|
||||||
|
|
||||||
|
with_settings!({
|
||||||
|
filters => {
|
||||||
|
let mut combined_filters = testing::CLEANUP_DATE.to_vec();
|
||||||
|
combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]);
|
||||||
|
combined_filters
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
(notes.status_code(), notes.text())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_add_note() {
|
||||||
|
configure_insta!();
|
||||||
|
|
||||||
|
testing::request::<App, Migrator, _, _>(|request, _ctx| async move {
|
||||||
|
let payload = serde_json::json!({
|
||||||
|
"title": "loco",
|
||||||
|
"content": "loco note test",
|
||||||
|
});
|
||||||
|
|
||||||
|
let add_note_request = request.post("/api/notes").json(&payload).await;
|
||||||
|
|
||||||
|
with_settings!({
|
||||||
|
filters => {
|
||||||
|
let mut combined_filters = testing::CLEANUP_DATE.to_vec();
|
||||||
|
combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]);
|
||||||
|
combined_filters
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
(add_note_request.status_code(), add_note_request.text())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_get_note() {
|
||||||
|
configure_insta!();
|
||||||
|
|
||||||
|
testing::request::<App, Migrator, _, _>(|request, ctx| async move {
|
||||||
|
testing::seed::<App>(&ctx.db).await.unwrap();
|
||||||
|
|
||||||
|
let add_note_request = request.get("/api/notes/1").await;
|
||||||
|
|
||||||
|
with_settings!({
|
||||||
|
filters => {
|
||||||
|
let mut combined_filters = testing::CLEANUP_DATE.to_vec();
|
||||||
|
combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]);
|
||||||
|
combined_filters
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
(add_note_request.status_code(), add_note_request.text())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn can_delete_note() {
|
||||||
|
configure_insta!();
|
||||||
|
|
||||||
|
testing::request::<App, Migrator, _, _>(|request, ctx| async move {
|
||||||
|
testing::seed::<App>(&ctx.db).await.unwrap();
|
||||||
|
|
||||||
|
let count_before_delete = Entity::find().all(&ctx.db).await.unwrap().len();
|
||||||
|
let delete_note_request = request.delete("/api/notes/1").await;
|
||||||
|
|
||||||
|
with_settings!({
|
||||||
|
filters => {
|
||||||
|
let mut combined_filters = testing::CLEANUP_DATE.to_vec();
|
||||||
|
combined_filters.extend(vec![(r#"\"id\\":\d+"#, r#""id\":ID"#)]);
|
||||||
|
combined_filters
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
assert_debug_snapshot!(
|
||||||
|
(delete_note_request.status_code(), delete_note_request.text())
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
let count_after_delete = Entity::find().all(&ctx.db).await.unwrap().len();
|
||||||
|
assert_eq!(count_after_delete, count_before_delete - 1);
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
*/
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: tests/requests/notes.rs
|
||||||
|
expression: "(add_note_request.status_code(), add_note_request.text())"
|
||||||
|
---
|
||||||
|
(
|
||||||
|
200,
|
||||||
|
"{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"loco\",\"content\":\"loco note test\"}",
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: tests/requests/notes.rs
|
||||||
|
expression: "(delete_note_request.status_code(), delete_note_request.text())"
|
||||||
|
---
|
||||||
|
(
|
||||||
|
200,
|
||||||
|
"",
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: tests/requests/notes.rs
|
||||||
|
expression: "(add_note_request.status_code(), add_note_request.text())"
|
||||||
|
---
|
||||||
|
(
|
||||||
|
200,
|
||||||
|
"{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"}",
|
||||||
|
)
|
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
source: tests/requests/notes.rs
|
||||||
|
expression: "(notes.status_code(), notes.text())"
|
||||||
|
---
|
||||||
|
(
|
||||||
|
200,
|
||||||
|
"[{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"},{\"created_at\":\"DATE\",\"updated_at\":\"DATE\",\"id\":ID,\"title\":\"Loco note 1\",\"content\":\"Loco note 1 content\"}]",
|
||||||
|
)
|
1
examples/loco_example/tests/tasks/mod.rs
Normal file
1
examples/loco_example/tests/tasks/mod.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub mod seed;
|
42
examples/loco_example/tests/tasks/seed.rs
Normal file
42
examples/loco_example/tests/tasks/seed.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
//! This task implements data seeding functionality for initializing new
|
||||||
|
//! development/demo environments.
|
||||||
|
//!
|
||||||
|
//! # Example
|
||||||
|
//!
|
||||||
|
//! Run the task with the following command:
|
||||||
|
//! ```sh
|
||||||
|
//! cargo run task
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! To override existing data and reset the data structure, use the following
|
||||||
|
//! command with the `refresh:true` argument:
|
||||||
|
//! ```sh
|
||||||
|
//! cargo run task seed_data refresh:true
|
||||||
|
//! ```
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
use loco_rs::{db, prelude::*};
|
||||||
|
use migration::Migrator;
|
||||||
|
use todolist::app::App;
|
||||||
|
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
|
pub struct SeedData;
|
||||||
|
#[async_trait]
|
||||||
|
impl Task for SeedData {
|
||||||
|
fn task(&self) -> TaskInfo {
|
||||||
|
TaskInfo {
|
||||||
|
name: "seed_data".to_string(),
|
||||||
|
detail: "Task for seeding data".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn run(&self, app_context: &AppContext, vars: &BTreeMap<String, String>) -> Result<()> {
|
||||||
|
let refresh = vars.get("refresh").is_some_and(|refresh| refresh == "true");
|
||||||
|
|
||||||
|
if refresh {
|
||||||
|
db::reset::<Migrator>(&app_context.db).await?;
|
||||||
|
}
|
||||||
|
let path = std::path::Path::new("src/fixtures");
|
||||||
|
db::run_app_seed::<App>(&app_context.db, path).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user