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/graphql_example,
|
||||
examples/jsonrpsee_example,
|
||||
examples/loco_example,
|
||||
examples/poem_example,
|
||||
examples/proxy_gluesql_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