diff --git a/.env b/.env index 488f7ef..f90031e 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ # DATABASE_URL="mssql://sa:YourStrong()Passw0rd@localhost/AdventureWorksLT2016" # DATABASE_URL="mysql://root:root@localhost/AdventureWorksLT2016" # DATABASE_URL="postgres://root:root@localhost/adventureworkslt2016" -DATABASE_URL="sqlite://db.sqlite?mode=rwc" \ No newline at end of file +DATABASE_URL="sqlite://../cafx-v2.db?mode=rwc" \ No newline at end of file diff --git a/CUSTOM_ENTITIES.md b/CUSTOM_ENTITIES.md new file mode 100644 index 0000000..3e5f4fc --- /dev/null +++ b/CUSTOM_ENTITIES.md @@ -0,0 +1,184 @@ +# Using Custom Entities with SeaORM Pro Actix Integration + +This guide explains how to use the SeaORM Pro admin interface with your own Sea-ORM entities instead of the built-in ones. + +## Overview + +The Actix Web integration feature supports two modes: + +1. **Default Mode**: Uses sea-orm-pro's built-in entities (Django-style user models) +2. **Custom Mode**: Uses your own Sea-ORM entities with a custom GraphQL schema + +## Default Mode (Built-in Entities) + +```rust +use sea_orm_pro_backend::actix_integration::{SeaOrmProActixConfigurator, SeaOrmProActixOptions}; + +let config = SeaOrmProActixConfigurator::new( + SeaOrmProActixOptions::new(db.clone(), secret_key) + .with_admin_base_path("/admin") +)?; + +// Mount on your Actix server... +``` + +## Custom Mode (Your Own Entities) + +### Step 1: Define Your Entities + +Use `sea-orm-cli` to generate entities from your database: + +```bash +sea-orm-cli generate entity -u sqlite://your_database.db -o src/entities +``` + +### Step 2: Build GraphQL Schema from Your Entities + +Create a function to build your GraphQL schema using seaography: + +```rust +// In your main application code (not in sea-orm-pro) +use sea_orm::DatabaseConnection; +use seaography::{Builder, BuilderContext, LifecycleHooks, MultiLifecycleHooks}; +use async_graphql::dynamic::Schema; + +/// Build GraphQL schema from your custom entities +pub fn build_custom_admin_schema( + db: DatabaseConnection, + depth: Option, + complexity: Option, +) -> Result { + // Create builder context + let context = BuilderContext { + hooks: LifecycleHooks::new(MultiLifecycleHooks::default()), + ..Default::default() + }; + + let builder = Builder::new(&context, db.clone()); + + // Register your entities + let builder = register_custom_entities(builder); + + builder + .set_depth_limit(depth) + .set_complexity_limit(complexity) + .schema_builder() + .data(db) + .finish() +} + +/// Register all your custom entities with the schema builder +fn register_custom_entities(builder: Builder) -> Builder { + // Add entity registration for each of your entities + // For example: + // builder.register_entity::() + // .register_entity::() + + // See seaography documentation for entity registration details + builder +} +``` + +### Step 3: Mount with Custom Schema + +```rust +use sea_orm_pro_backend::actix_integration::{SeaOrmProActixConfigurator, SeaOrmProActixOptions}; + +// Build your custom schema +let custom_schema = build_custom_admin_schema(db.clone(), Some(100), Some(1000))?; + +// Use it with SeaORM Pro +let config = SeaOrmProActixConfigurator::new( + SeaOrmProActixOptions::new(db.clone(), secret_key) + .with_custom_schema(custom_schema) + .with_admin_base_path("/admin") +)?; + +// Mount on your Actix server... +``` + +## User Authentication + +SeaORM Pro's admin interface expects a `users_user` table with: +- `email` (VARCHAR, unique primary id) +- `password` (VARCHAR, bcrypt hash) +- `username` (VARCHAR, optional) +- `is_email_verified` (BOOLEAN, optional) + +If you don't have this table with this exact schema, you'll need to: + +1. **Create the table** in your database before using the admin interface +2. **Seed it** with an initial admin user +3. **Update authentication** code if your user model differs significantly + +### Example: Seeding Admin User + +```bash +# Generate bcrypt hash for password "admin123" +python3 -c "import bcrypt; print(bcrypt.hashpw(b'admin123', bcrypt.gensalt())).decode())" + +# Insert into database +sqlite3 your_database.db " +INSERT INTO users_user (email, password, username, is_email_verified) +VALUES ('admin@example.com', '\$2b\$12\$...', 'admin', true) +" +``` + +## Accessing the Admin Interface + +Once mounted, you can access: + +- **GraphQL Playground**: `http://localhost:8080/admin/graphql` +- **Login**: `POST /admin/auth/login` with `{"email": "...", "password": "..."}` +- **Current User**: `GET /admin/user/current` (requires auth) +- **Config**: `GET /admin/config` (public) + +### Example: Login and Query + +```bash +# 1. Login to get JWT token +TOKEN=$(curl -s -X POST http://localhost:8080/admin/auth/login \ + -H "Content-Type: application/json" \ + -d '{"email":"admin@example.com","password":"admin123"}' \ + | jq -r '.token') + +# 2. Use token for authenticated requests +curl -s http://localhost:8080/admin/user/current \ + -H "Authorization: Bearer $TOKEN" | jq + +# 3. Query GraphQL with auth +curl -s -X POST http://localhost:8080/admin/graphql \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"query":"{ __typename }"}' +``` + +## Limitations & Notes + +1. **User Table Required**: The admin interface always assumes a `users_user` table exists with email/password fields for authentication + +2. **Custom Entities**: You can expose any Sea-ORM entities through the GraphQL schema, but the auth layer is hardcoded to use `users_user` + +3. **GraphQL Limits**: Default depth limit is 100, complexity limit is 1000. Adjust with `.with_graphql_limits()` + +4. **JWT Secret**: Must be non-empty. Should be >= 32 characters for production + +## Troubleshooting + +### "Schema build error" +- Ensure all entities are properly registered with the seaography builder +- Check entity definitions have proper derives and attributes + +### "Unauthorized" on protected routes +- Verify `Authorization: Bearer ` header is present and token is valid +- Check JWT secret matches between login and protected endpoint + +### "Database error" on login +- Ensure `users_user` table exists and has correct schema +- Verify database connection is working + +## See Also + +- [SeaORM Entity Generation](https://www.sea-ql.org/SeaORM/docs/generate-entity/sea-orm-cli) +- [Seaography Documentation](https://github.com/SeaQL/seaography) +- [Async-GraphQL Dynamic Schema](https://async-graphql.rs/docs/dynamic-schema) diff --git a/Cargo.lock b/Cargo.lock index 82394c7..55ae0d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,243 @@ dependencies = [ "regex", ] +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.9.4", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.9.4", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "base64", + "bitflags 2.9.4", + "brotli", + "bytes", + "bytestring", + "derive_more 2.0.1", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn 2.0.106", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie 0.16.2", + "derive_more 2.0.1", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.0", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.3.1+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 2.0.106", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "addr2line" version = "0.24.2" @@ -186,6 +423,18 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-compression" version = "0.4.30" @@ -219,7 +468,7 @@ dependencies = [ "futures-timer", "futures-util", "handlebars", - "http", + "http 1.3.1", "indexmap", "lru", "mime", @@ -237,6 +486,25 @@ dependencies = [ "uuid", ] +[[package]] +name = "async-graphql-actix-web" +version = "7.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a6848c3effbd36ea8390db3d1f0b7c79fbedcb23c2390fb79c1d1774491c4c" +dependencies = [ + "actix", + "actix-http", + "actix-web", + "actix-web-actors", + "async-channel", + "async-graphql", + "async-stream", + "futures-channel", + "futures-util", + "serde_json", + "thiserror 1.0.69", +] + [[package]] name = "async-graphql-axum" version = "7.0.17" @@ -367,7 +635,7 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -402,7 +670,7 @@ checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", "futures-core", - "http", + "http 1.3.1", "http-body", "http-body-util", "mime", @@ -423,9 +691,9 @@ dependencies = [ "axum", "axum-core", "bytes", - "cookie", + "cookie 0.18.1", "futures-util", - "http", + "http 1.3.1", "http-body", "http-body-util", "mime", @@ -460,8 +728,8 @@ dependencies = [ "axum", "bytes", "bytesize", - "cookie", - "http", + "cookie 0.18.1", + "http 1.3.1", "http-body-util", "hyper", "hyper-util", @@ -528,6 +796,19 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +[[package]] +name = "bcrypt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b1866ecef4f2d06a0bb77880015fdf2b89e25a1c2e5addacb87e459c86dc67e" +dependencies = [ + "base64", + "blowfish", + "getrandom 0.2.16", + "subtle", + "zeroize", +] + [[package]] name = "bigdecimal" version = "0.4.8" @@ -587,6 +868,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "borsh" version = "1.5.7" @@ -706,6 +997,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5c434ae3cf0089ca203e9019ebe529c47ff45cefe8af7c85ecb734ef541822f" +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.2.37" @@ -776,6 +1076,16 @@ dependencies = [ "stacker", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.47" @@ -890,6 +1200,17 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "cookie" version = "0.18.1" @@ -1384,6 +1705,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1718,6 +2049,25 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "handlebars" version = "5.1.2" @@ -1839,6 +2189,17 @@ dependencies = [ "match_token", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.3.1" @@ -1857,7 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.3.1", ] [[package]] @@ -1868,7 +2229,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.3.1", "http-body", "pin-project-lite", ] @@ -1910,7 +2271,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", + "http 1.3.1", "http-body", "httparse", "httpdate", @@ -1933,14 +2294,14 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", + "http 1.3.1", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -2099,6 +2460,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "include_dir" version = "0.7.4" @@ -2167,6 +2534,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -2279,6 +2655,12 @@ dependencies = [ "libc", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" @@ -2363,6 +2745,23 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.13" @@ -2625,7 +3024,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.3.1", "httparse", "memchr", "mime", @@ -2801,7 +3200,7 @@ dependencies = [ "flagset", "futures", "getrandom 0.2.16", - "http", + "http 1.3.1", "log", "md-5", "once_cell", @@ -3353,6 +3752,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.6" @@ -3378,7 +3783,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", + "http 1.3.1", "http-body", "http-body-util", "hyper", @@ -3503,7 +3908,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", + "http 1.3.1", "mime", "rand 0.9.2", "thiserror 2.0.16", @@ -3757,16 +4162,21 @@ dependencies = [ name = "sea-orm-pro-backend" version = "0.1.0" dependencies = [ + "actix-web", + "async-graphql", + "async-graphql-actix-web", "async-graphql-axum", "async-trait", "axum", "base64", + "bcrypt", "chrono", "csv", "dotenvy", "encoding_rs", "encoding_rs_io", "eyre", + "jsonwebtoken", "loco-rs", "migration", "sea-orm", @@ -4936,7 +5346,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", + "http 1.3.1", "http-body", "http-body-util", "http-range-header", @@ -5077,7 +5487,7 @@ checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.3.1", "httparse", "log", "rand 0.9.2", diff --git a/Cargo.toml b/Cargo.toml index fea009b..60e54af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,43 +8,68 @@ edition = "2021" publish = false [dependencies] -migration = { path = "migration" } +migration = { path = "migration", optional = true } sea-orm-pro = { path = "sea-orm-pro", version = "0.1.0" } sea-orm = { version = "~2.0.0-rc", features = ["runtime-tokio-rustls", "macros", "debug-print"] } seaography = { version = "~2.0.0-rc", features = ["with-decimal", "with-chrono", "with-uuid", "field-snake-case"] } -loco-rs = { version = "0.16" } +loco-rs = { version = "0.16", optional = true } serde = { version = "1", features = ["derive"] } serde_json = "1" eyre = "0.6" tokio = { version = "1.33.0", default-features = false } -tokio-util = "0.7.11" -async-trait = "0.1.74" +tokio-util = { version = "0.7.11", optional = true } +async-trait = { version = "0.1.74", optional = true } tracing = "0.1.40" chrono = "0.4" -async-graphql-axum = { version = "7.0" } -tower-service = { version = "0.3" } -axum = { version = "0.8", features = ["multipart"] } +async-graphql-axum = { version = "7.0", optional = true } +async-graphql = { version = "7.0", features = ["dynamic-schema"] } +async-graphql-actix-web = { version = "7.0", optional = true } +tower-service = { version = "0.3", optional = true } +axum = { version = "0.8", features = ["multipart"], optional = true } +actix-web = { version = "4", optional = true } +jsonwebtoken = { version = "9", optional = true } +bcrypt = { version = "0.16", optional = true } uuid = { version = "1.6.0", features = ["v4"] } -dotenvy = "0.15.7" -base64 = "0.22.1" -csv = "1.3" -encoding_rs = "0.8" -encoding_rs_io = "0.1" +dotenvy = { version = "0.15.7", optional = true } +base64 = { version = "0.22.1", optional = true } +csv = { version = "1.3", optional = true } +encoding_rs = { version = "0.8", optional = true } +encoding_rs_io = { version = "0.1", optional = true } [dev-dependencies] loco-rs = { version = "0.16", features = ["testing"] } serial_test = { version = "3.1" } [features] -default = ["sqlx-sqlite"] -sqlx-mysql = ["sea-orm/sqlx-mysql", "migration/sqlx-mysql"] +default = ["sqlx-sqlite", "loco-app"] +loco-app = [ + "dep:migration", + "dep:loco-rs", + "dep:tokio-util", + "dep:async-trait", + "dep:async-graphql-axum", + "dep:tower-service", + "dep:axum", + "dep:dotenvy", + "dep:base64", + "dep:csv", + "dep:encoding_rs", + "dep:encoding_rs_io", +] +actix-integration = [ + "dep:actix-web", + "dep:async-graphql-actix-web", + "dep:jsonwebtoken", + "dep:bcrypt", +] +sqlx-mysql = ["sea-orm/sqlx-mysql", "dep:migration", "migration/sqlx-mysql"] sqlx-postgres = ["sea-orm/sqlx-postgres"] sqlx-sqlite = ["sea-orm/sqlx-sqlite"] [[bin]] name = "sea-orm-pro-backend-cli" path = "src/bin/main.rs" -required-features = [] +required-features = ["loco-app"] [patch.crates-io] sea-orm = { git = "https://github.com/SeaQL/sea-orm", branch = "master" } diff --git a/README.md b/README.md index 8d1e4de..607b871 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,95 @@ ## Installation +## Actix Web Integration + +You can also consume this crate as an Actix Web module and mount SeaORM Pro admin routes into an existing server. + +Enable the Actix integration feature: + +```toml +[dependencies] +sea-orm-pro-backend = { path = "../sea-orm-pro", default-features = false, features = ["actix-integration", "sqlx-postgres"] } +``` + +Mount it in your Actix application: + +```rust +use sea_orm_pro_backend::actix_integration::{SeaOrmProActixConfigurator, SeaOrmProActixOptions}; + +let sea_orm_pro_admin = SeaOrmProActixConfigurator::new( + SeaOrmProActixOptions::new(db.clone(), secret_key.clone()) + .with_production(!debug) + .with_admin_base_path("/admin"), +)?; + +HttpServer::new(move || { + let sea_orm_pro_admin = sea_orm_pro_admin.clone(); + + App::new() + .configure(move |cfg| sea_orm_pro_admin.configure(cfg)) +}); +``` + +This keeps your app and SeaORM Pro routes on the same Actix server process. + +### Using Custom Entities + +If you want to use SeaORM Pro admin interface with your own Sea-ORM entities instead of the built-in ones: + +1. Build your own GraphQL schema using seaography: + +```rust +use sea_orm::DatabaseConnection; +use seaography::Builder; +use async_graphql::dynamic::Schema; + +// Your custom entities module +mod my_entities { + pub use super::my_generated_entities::*; +} + +pub fn build_custom_schema( + db: DatabaseConnection, + depth: Option, + complexity: Option, +) -> Result { + let builder = Builder::new(&Default::default(), db.clone()); + + // Register your custom entities + let builder = register_my_entities(builder); + + builder + .set_depth_limit(depth) + .set_complexity_limit(complexity) + .schema_builder() + .data(db) + .finish() +} + +// Helper to register your entities (similar to sea-orm-pro's register_entity_modules) +fn register_my_entities(builder: Builder) -> Builder { + builder + // Add your entity registrations here +} +``` + +2. Pass the custom schema to SeaOrmProActixConfigurator: + +```rust +use sea_orm_pro_backend::actix_integration::{SeaOrmProActixConfigurator, SeaOrmProActixOptions}; + +let custom_schema = build_custom_schema(db.clone(), Some(100), Some(1000))?; + +let sea_orm_pro_admin = SeaOrmProActixConfigurator::new( + SeaOrmProActixOptions::new(db.clone(), secret_key.clone()) + .with_custom_schema(custom_schema) + .with_admin_base_path("/admin"), +)?; +``` + +This way, the SeaORM Pro admin interface will expose your custom entities through GraphQL. + 1. Modify `.env` with your database credentials, here it uses SQLite as the default database, so you don't have to setup anything. ```sh diff --git a/migration/src/lib.rs b/migration/src/lib.rs index a884061..f209550 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -5,16 +5,6 @@ )] pub use sea_orm_migration::prelude::*; -mod m20241101_000001_address; -mod m20241101_000002_customer; -mod m20241101_000003_customer_address; -mod m20241101_000004_product_category; -mod m20241101_000005_product_description; -mod m20241101_000006_product_model; -mod m20241101_000007_product; -mod m20241101_000008_product_model_product_description; -mod m20241101_000009_sales_order_header; -mod m20241101_000010_sales_order_detail; mod m20250101_000001_user; mod m20250101_000002_seed_users; @@ -24,16 +14,6 @@ pub struct Migrator; impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ - Box::new(m20241101_000001_address::Migration), - Box::new(m20241101_000002_customer::Migration), - Box::new(m20241101_000003_customer_address::Migration), - Box::new(m20241101_000004_product_category::Migration), - Box::new(m20241101_000005_product_description::Migration), - Box::new(m20241101_000006_product_model::Migration), - Box::new(m20241101_000007_product::Migration), - Box::new(m20241101_000008_product_model_product_description::Migration), - Box::new(m20241101_000009_sales_order_header::Migration), - Box::new(m20241101_000010_sales_order_detail::Migration), Box::new(m20250101_000001_user::Migration), Box::new(m20250101_000002_seed_users::Migration), ] diff --git a/migration/bak/lib.rs b/migration/src/lib.rs.bak similarity index 100% rename from migration/bak/lib.rs rename to migration/src/lib.rs.bak diff --git a/migration/src/m20241101_000001_address.rs b/migration/src/m20241101_000001_address.rs deleted file mode 100644 index 3ba07df..0000000 --- a/migration/src/m20241101_000001_address.rs +++ /dev/null @@ -1,43 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(Address::Table) - .col(pk_auto(Address::AddressId)) - .col(string_len(Address::AddressLine1, 60)) - .col(string_len_null(Address::AddressLine2, 60)) - .col(string_len(Address::City, 30)) - .col(string_len(Address::StateProvince, 50)) - .col(string_len(Address::CountryRegion, 50)) - .col(string_len(Address::PostalCode, 15)) - .col(uuid_uniq(Address::Rowguid)) - .col(date_time(Address::CreatedDate)) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(Address::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum Address { - Table, - AddressId, - AddressLine1, - AddressLine2, - City, - StateProvince, - CountryRegion, - PostalCode, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000002_customer.rs b/migration/src/m20241101_000002_customer.rs deleted file mode 100644 index 181dd81..0000000 --- a/migration/src/m20241101_000002_customer.rs +++ /dev/null @@ -1,58 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(Customer::Table) - .col(pk_auto(Customer::CustomerId)) - .col( - boolean(Customer::NameStyle) - .default(manager.get_database_backend().boolean_value(false)), - ) - .col(string_len_null(Customer::Title, 8)) - .col(string_len(Customer::FirstName, 60)) - .col(string_len_null(Customer::MiddleName, 60)) - .col(string_len(Customer::LastName, 60)) - .col(string_len_null(Customer::Suffix, 10)) - .col(string_len_null(Customer::CompanyName, 128)) - .col(string_len_null(Customer::SalesPerson, 256)) - .col(string_len_null(Customer::EmailAddress, 50)) - .col(string_len(Customer::Phone, 25)) - .col(string_len(Customer::PasswordHash, 128)) - .col(string_len(Customer::PasswordSalt, 10)) - .col(uuid_uniq(Customer::Rowguid)) - .col(date_time(Customer::CreatedDate)) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(Customer::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum Customer { - Table, - CustomerId, - NameStyle, - Title, - FirstName, - MiddleName, - LastName, - Suffix, - CompanyName, - SalesPerson, - EmailAddress, - Phone, - PasswordHash, - PasswordSalt, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000003_customer_address.rs b/migration/src/m20241101_000003_customer_address.rs deleted file mode 100644 index 20bdb40..0000000 --- a/migration/src/m20241101_000003_customer_address.rs +++ /dev/null @@ -1,55 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -use crate::{m20241101_000001_address::Address, m20241101_000002_customer::Customer}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(CustomerAddress::Table) - .col(integer(CustomerAddress::CustomerId)) - .col(integer(CustomerAddress::AddressId)) - .col(string_len(CustomerAddress::AddressType, 60)) - .col(uuid_uniq(CustomerAddress::Rowguid)) - .col(date_time(CustomerAddress::CreatedDate)) - .primary_key( - Index::create() - .name("pk_customer_address_customer_id_address_id") - .col(CustomerAddress::CustomerId) - .col(CustomerAddress::AddressId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_customer_address_customer_customer_id") - .from(CustomerAddress::Table, CustomerAddress::CustomerId) - .to(Customer::Table, Customer::CustomerId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_customer_address_address_address_id") - .from(CustomerAddress::Table, CustomerAddress::AddressId) - .to(Address::Table, Address::AddressId), - ) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(CustomerAddress::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum CustomerAddress { - Table, - CustomerId, - AddressId, - AddressType, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000004_product_category.rs b/migration/src/m20241101_000004_product_category.rs deleted file mode 100644 index 5a38639..0000000 --- a/migration/src/m20241101_000004_product_category.rs +++ /dev/null @@ -1,44 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(ProductCategory::Table) - .col(pk_auto(ProductCategory::ProductCategoryId)) - .col(integer_null(ProductCategory::ParentProductCategoryId)) - .col(string_len_uniq(ProductCategory::Name, 60)) - .col(uuid_uniq(ProductCategory::Rowguid)) - .col(date_time(ProductCategory::CreatedDate)) - .foreign_key( - ForeignKey::create() - .name("fk_parent_product_category_id") - .from( - ProductCategory::Table, - ProductCategory::ParentProductCategoryId, - ) - .to(ProductCategory::Table, ProductCategory::ProductCategoryId), - ) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(ProductCategory::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum ProductCategory { - Table, - ProductCategoryId, - ParentProductCategoryId, - Name, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000005_product_description.rs b/migration/src/m20241101_000005_product_description.rs deleted file mode 100644 index c28fff5..0000000 --- a/migration/src/m20241101_000005_product_description.rs +++ /dev/null @@ -1,33 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(ProductDescription::Table) - .col(pk_auto(ProductDescription::ProductDescriptionId)) - .col(string_len(ProductDescription::Description, 400)) - .col(uuid_uniq(ProductDescription::Rowguid)) - .col(date_time(ProductDescription::CreatedDate)) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(ProductDescription::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum ProductDescription { - Table, - ProductDescriptionId, - Description, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000006_product_model.rs b/migration/src/m20241101_000006_product_model.rs deleted file mode 100644 index 1fcb10a..0000000 --- a/migration/src/m20241101_000006_product_model.rs +++ /dev/null @@ -1,35 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(ProductModel::Table) - .col(pk_auto(ProductModel::ProductModelId)) - .col(string_len(ProductModel::Name, 60)) - .col(text_null(ProductModel::CatalogDescription)) - .col(uuid_uniq(ProductModel::Rowguid)) - .col(date_time(ProductModel::CreatedDate)) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(ProductModel::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum ProductModel { - Table, - ProductModelId, - Name, - CatalogDescription, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000007_product.rs b/migration/src/m20241101_000007_product.rs deleted file mode 100644 index 9d03720..0000000 --- a/migration/src/m20241101_000007_product.rs +++ /dev/null @@ -1,76 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -use crate::{ - m20241101_000004_product_category::ProductCategory, - m20241101_000006_product_model::ProductModel, -}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(Product::Table) - .col(pk_auto(Product::ProductId)) - .col(string_len_uniq(Product::Name, 60)) - .col(string_len_uniq(Product::ProductNumber, 25)) - .col(string_len_null(Product::Color, 15)) - .col(double(Product::StandardCost)) - .col(double(Product::ListPrice)) - .col(string_len_null(Product::Size, 5)) - .col(decimal_len_null(Product::Weight, 8, 2)) - .col(integer_null(Product::ProductCategoryId)) - .col(integer_null(Product::ProductModelId)) - .col(date_time(Product::SellStartDate)) - .col(date_time_null(Product::SellEndDate)) - .col(date_time_null(Product::DiscontinuedDate)) - .col(text_null(Product::ThumbNailPhoto)) - .col(string_len_null(Product::ThumbnailPhotoFileName, 50)) - .col(uuid_uniq(Product::Rowguid)) - .col(date_time(Product::CreatedDate)) - .foreign_key( - ForeignKey::create() - .name("fk_product_product_category_product_category_id") - .from(Product::Table, Product::ProductCategoryId) - .to(ProductCategory::Table, ProductCategory::ProductCategoryId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_product_product_model_product_model_id") - .from(Product::Table, Product::ProductModelId) - .to(ProductModel::Table, ProductModel::ProductModelId), - ) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(Product::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum Product { - Table, - ProductId, - Name, - ProductNumber, - Color, - StandardCost, - ListPrice, - Size, - Weight, - ProductCategoryId, - ProductModelId, - SellStartDate, - SellEndDate, - DiscontinuedDate, - ThumbNailPhoto, - ThumbnailPhotoFileName, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000008_product_model_product_description.rs b/migration/src/m20241101_000008_product_model_product_description.rs deleted file mode 100644 index d8aafe0..0000000 --- a/migration/src/m20241101_000008_product_model_product_description.rs +++ /dev/null @@ -1,74 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -use crate::{ - m20241101_000005_product_description::ProductDescription, - m20241101_000006_product_model::ProductModel, -}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(ProductModelProductDescription::Table) - .col(integer(ProductModelProductDescription::ProductModelId)) - .col(integer( - ProductModelProductDescription::ProductDescriptionId, - )) - .col(char_len(ProductModelProductDescription::Culture, 6)) - .col(uuid_uniq(ProductModelProductDescription::Rowguid)) - .col(date_time(ProductModelProductDescription::CreatedDate)) - .primary_key( - Index::create() - .name("pk_product_model_product_description_id") - .col(ProductModelProductDescription::ProductModelId) - .col(ProductModelProductDescription::ProductDescriptionId) - .col(ProductModelProductDescription::Culture), - ) - .foreign_key( - ForeignKey::create() - .name("fk_product_model_product_model_id") - .from( - ProductModelProductDescription::Table, - ProductModelProductDescription::ProductModelId, - ) - .to(ProductModel::Table, ProductModel::ProductModelId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_product_description_id") - .from( - ProductModelProductDescription::Table, - ProductModelProductDescription::ProductDescriptionId, - ) - .to( - ProductDescription::Table, - ProductDescription::ProductDescriptionId, - ), - ) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table( - Table::drop() - .table(ProductModelProductDescription::Table) - .to_owned(), - ) - .await - } -} - -#[derive(Iden)] -pub enum ProductModelProductDescription { - Table, - ProductModelId, - ProductDescriptionId, - Culture, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000009_sales_order_header.rs b/migration/src/m20241101_000009_sales_order_header.rs deleted file mode 100644 index 7c06e47..0000000 --- a/migration/src/m20241101_000009_sales_order_header.rs +++ /dev/null @@ -1,91 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -use crate::{m20241101_000001_address::Address, m20241101_000002_customer::Customer}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(SalesOrderHeader::Table) - .col(pk_auto(SalesOrderHeader::SalesOrderId)) - .col(integer(SalesOrderHeader::RevisionNumber).default(0)) - .col(date_time(SalesOrderHeader::OrderDate)) - .col(date_time(SalesOrderHeader::DueDate)) - .col(date_time_null(SalesOrderHeader::ShipDate)) - .col(integer(SalesOrderHeader::Status).default(1)) - .col( - boolean(SalesOrderHeader::OnlineOrderFlag) - .default(manager.get_database_backend().boolean_value(true)), - ) - .col(string_len_null(SalesOrderHeader::PurchaseOrderNumber, 25)) - .col(string_len_null(SalesOrderHeader::AccountNumber, 15)) - .col(integer(SalesOrderHeader::CustomerId)) - .col(integer_null(SalesOrderHeader::ShipToAddressId)) - .col(integer_null(SalesOrderHeader::BillToAddressId)) - .col(string_len(SalesOrderHeader::ShipMethod, 50)) - .col(string_len_null( - SalesOrderHeader::CreditCardApprovalCode, - 15, - )) - .col(double(SalesOrderHeader::SubTotal).default(0)) - .col(double(SalesOrderHeader::TaxAmt).default(0)) - .col(double(SalesOrderHeader::Freight).default(0)) - .col(text_null(SalesOrderHeader::Comment)) - .col(uuid_uniq(SalesOrderHeader::Rowguid)) - .col(date_time(SalesOrderHeader::CreatedDate)) - .foreign_key( - ForeignKey::create() - .name("fk_sales_order_header_customer_customer_id") - .from(SalesOrderHeader::Table, SalesOrderHeader::CustomerId) - .to(Customer::Table, Customer::CustomerId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_sales_order_header_address_ship_to_address_id") - .from(SalesOrderHeader::Table, SalesOrderHeader::ShipToAddressId) - .to(Address::Table, Address::AddressId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_sales_order_header_address_bill_to_address_id") - .from(SalesOrderHeader::Table, SalesOrderHeader::BillToAddressId) - .to(Address::Table, Address::AddressId), - ) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(SalesOrderHeader::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum SalesOrderHeader { - Table, - SalesOrderId, - RevisionNumber, - OrderDate, - DueDate, - ShipDate, - Status, - OnlineOrderFlag, - PurchaseOrderNumber, - AccountNumber, - CustomerId, - ShipToAddressId, - BillToAddressId, - ShipMethod, - CreditCardApprovalCode, - SubTotal, - TaxAmt, - Freight, - Comment, - Rowguid, - CreatedDate, -} diff --git a/migration/src/m20241101_000010_sales_order_detail.rs b/migration/src/m20241101_000010_sales_order_detail.rs deleted file mode 100644 index 26ce66d..0000000 --- a/migration/src/m20241101_000010_sales_order_detail.rs +++ /dev/null @@ -1,64 +0,0 @@ -use sea_orm_migration::{prelude::*, schema::*}; - -use crate::{ - m20241101_000007_product::Product, m20241101_000009_sales_order_header::SalesOrderHeader, -}; - -#[derive(DeriveMigrationName)] -pub struct Migration; - -#[async_trait::async_trait] -impl MigrationTrait for Migration { - async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - let table = Table::create() - .table(SalesOrderDetail::Table) - .col(integer(SalesOrderDetail::SalesOrderId)) - .col(integer(SalesOrderDetail::SalesOrderDetailId)) - .col(small_integer(SalesOrderDetail::OrderQty)) - .col(integer(SalesOrderDetail::ProductId)) - .col(double(SalesOrderDetail::UnitPrice)) - .col(double(SalesOrderDetail::UnitPriceDiscount).default(0)) - .col(uuid_uniq(SalesOrderDetail::Rowguid)) - .col(date_time(SalesOrderDetail::CreatedDate)) - .primary_key( - Index::create() - .name("pk_sales_order_detail_id") - .col(SalesOrderDetail::SalesOrderId) - .col(SalesOrderDetail::SalesOrderDetailId), - ) - .foreign_key( - ForeignKey::create() - .name("fk_sales_order_detail_sales_order_header_sales_order_id") - .from(SalesOrderDetail::Table, SalesOrderDetail::SalesOrderId) - .to(SalesOrderHeader::Table, SalesOrderHeader::SalesOrderId) - .on_delete(ForeignKeyAction::Cascade), - ) - .foreign_key( - ForeignKey::create() - .name("fk_sales_order_detail_product_product_id") - .from(SalesOrderDetail::Table, SalesOrderDetail::ProductId) - .to(Product::Table, Product::ProductId), - ) - .to_owned(); - manager.create_table(table).await - } - - async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager - .drop_table(Table::drop().table(SalesOrderDetail::Table).to_owned()) - .await - } -} - -#[derive(Iden)] -pub enum SalesOrderDetail { - Table, - SalesOrderId, - SalesOrderDetailId, - OrderQty, - ProductId, - UnitPrice, - UnitPriceDiscount, - Rowguid, - CreatedDate, -} diff --git a/migration/bak/m20250101_000001_user.rs b/migration/src/m20250101_000001_user.rs.bak similarity index 100% rename from migration/bak/m20250101_000001_user.rs rename to migration/src/m20250101_000001_user.rs.bak diff --git a/migration/src/m20250101_000002_seed_users.rs b/migration/src/m20250101_000002_seed_users.rs index 9533001..251ddc2 100644 --- a/migration/src/m20250101_000002_seed_users.rs +++ b/migration/src/m20250101_000002_seed_users.rs @@ -46,25 +46,6 @@ impl MigrationTrait for Migration { .to_owned(); manager.exec_stmt(insert).await?; - let insert = Query::insert() - .into_table(User::Table) - .columns([ - User::Pid, - User::Email, - User::Password, - User::ApiKey, - User::Name, - ]) - .values_panic([ - Uuid::new_v4().into(), - "public@sea-ql.org".into(), - hash_password("demo@sea-ql.org")?.into(), - format!("lo-{}", Uuid::new_v4()).into(), - "Public".into(), - ]) - .to_owned(); - manager.exec_stmt(insert).await?; - Ok(()) } diff --git a/migration/bak/m20250101_000002_seed_users.rs b/migration/src/m20250101_000002_seed_users.rs.bak similarity index 100% rename from migration/bak/m20250101_000002_seed_users.rs rename to migration/src/m20250101_000002_seed_users.rs.bak diff --git a/migration/bak/main.rs b/migration/src/main.rs.bak similarity index 100% rename from migration/bak/main.rs rename to migration/src/main.rs.bak diff --git a/pro_admin/composite_tables/customer_address.toml b/pro_admin/composite_tables/customer_address.toml deleted file mode 100644 index 79033c9..0000000 --- a/pro_admin/composite_tables/customer_address.toml +++ /dev/null @@ -1,64 +0,0 @@ -[parent] -name = "customer" - -[parent.table] -columns = [ - { title = "ID", field = "customer_id", width = 80 }, - { field = "title", width = 100 }, - { field = "first_name", width = 120 }, - { field = "middle_name", width = 120 }, - { field = "last_name", width = 120 }, -] -hidden_columns = [ - "name_style", - "suffix", - "email_address", - "phone", - "rowguid", - "created_date", -] -order_by = { field = "customer_id", order = "desc" } - -[parent.editor] -enable = true -title_field = "first_name" - -[parent.create] -enable = true - -[parent.update] -enable = true - -[parent.delete] -enable = true - - -[[children]] -relation = "customer_address" - -[children.table] -title = "Address" -columns = [ - { title = "ID", field = "address_id", width = 80 }, - { field = "address_type" }, - { field = "address_line1", relation = "address" }, - { field = "address_line2", relation = "address" }, - { field = "city", relation = "address" }, - { field = "state_province", relation = "address" }, - { field = "country_region", relation = "address" }, - { field = "postal_code", relation = "address" }, -] -all_columns = false - -[children.editor] -enable = true -title_field = "address_type" - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true diff --git a/pro_admin/composite_tables/product_category.toml b/pro_admin/composite_tables/product_category.toml deleted file mode 100644 index 0a7b305..0000000 --- a/pro_admin/composite_tables/product_category.toml +++ /dev/null @@ -1,50 +0,0 @@ -[parent] -name = "product_category" - -[parent.table] -columns = [ - { title = "ID", field = "product_category_id", width = 80 }, - { title = "Category", field = "name" }, - { title = "Parent Category ID", field = "parent_product_category_id", width = 180 }, - { title = "Parent Category", field = "name", relation = "self_ref" }, -] -hidden_columns = [ - "rowguid", - "created_date", -] - -[parent.create] -enable = true - -[parent.update] -enable = true - -[parent.delete] -enable = true - - -[[children]] -relation = "product" - -[children.table] -columns = [ - { title = "ID", field = "product_id", width = 80 }, - { title = "Thumbnail", field = "thumb_nail_photo", input_type = "image", width = 120 }, - { field = "name", width = 300 }, - { field = "product_number" }, - { field = "color" }, - { field = "standard_cost" }, - { field = "list_price" }, - { field = "size" }, - { field = "weight" }, -] -all_columns = false - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true diff --git a/pro_admin/composite_tables/product_model.toml b/pro_admin/composite_tables/product_model.toml deleted file mode 100644 index 719549b..0000000 --- a/pro_admin/composite_tables/product_model.toml +++ /dev/null @@ -1,73 +0,0 @@ -[parent] -name = "product_model" - -[parent.table] -columns = [ - { title = "ID", field = "product_model_id", width = 80 }, - { field = "name", width = 200 }, - { field = "catalog_description", width = 800 }, -] -hidden_columns = [ - "rowguid", - "created_date", -] - -[parent.create] -enable = true - -[parent.update] -enable = true - -[parent.delete] -enable = true - - -[[children]] -relation = "product" - -[children.table] -columns = [ - { title = "ID", field = "product_id", width = 80 }, - { title = "Thumbnail", field = "thumb_nail_photo", input_type = "image", width = 120 }, - { field = "name", width = 300 }, - { field = "product_number" }, - { field = "color" }, - { field = "standard_cost" }, - { field = "list_price" }, - { field = "size" }, - { field = "weight" }, - { title = "Product Category", field = "name", relation = "product_category", width = 180 }, -] -all_columns = false - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true - - -[[children]] -relation = "product_description" - -[children.table] -columns = [ - { title = "ID", field = "product_description_id", width = 80 }, - { field = "description", width = 1000 }, -] -hidden_columns = [ - "rowguid", - "created_date", -] - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true diff --git a/pro_admin/composite_tables/sales_order.toml b/pro_admin/composite_tables/sales_order.toml deleted file mode 100644 index fe061d9..0000000 --- a/pro_admin/composite_tables/sales_order.toml +++ /dev/null @@ -1,147 +0,0 @@ -[parent] -# Table name of the parent table -name = "sales_order_header" - -[parent.table] -columns = [ - { title = "ID", field = "sales_order_id", width = 80 }, - { field = "order_date" }, - { field = "purchase_order_number" }, - { field = "account_number" }, - { field = "ship_method" }, - { field = "sub_total" }, - { field = "tax_amt" }, - { field = "freight" }, -] -all_columns = false - -[parent.editor] -enable = true -title_field = "purchase_order_number" - -[parent.create] -enable = true - -[parent.update] -enable = true - -[parent.delete] -enable = true - - -[[children]] -# Name of SeaORM relation -relation = "customer" - -[children.table] -columns = [ - { title = "ID", field = "customer_id", width = 80 }, - { field = "title", width = 100 }, - { field = "first_name", width = 120 }, - { field = "middle_name", width = 120 }, - { field = "last_name", width = 120 }, -] -hidden_columns = [ - "name_style", - "suffix", - "email_address", - "phone", - "rowguid", - "created_date", -] - -[children.editor] -enable = true -title_field = "first_name" - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true - - - -[[children]] -# Name of SeaORM relation -relation = "address1" - -[children.table] -title = "Shipping Address" -columns = [ - { title = "ID", field = "address_id", width = 80 }, -] -hidden_columns = [ - "rowguid", - "created_date", -] - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true - - -[[children]] -# Name of SeaORM relation -relation = "address2" - -[children.table] -title = "Billing Address" -columns = [ - { title = "ID", field = "address_id", width = 80 }, -] -hidden_columns = [ - "rowguid", - "created_date", -] - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true - - -[[children]] -# Name of SeaORM relation -relation = "sales_order_detail" - -[children.table] -columns = [ - { title = "Thumbnail", field = "thumb_nail_photo", relation = "product", input_type = "image", width = 120 }, - { field = "name", relation = "product", width = 300 }, - { field = "product_number", relation = "product" }, - { field = "color", relation = "product" }, - { field = "size", relation = "product" }, - { field = "weight", relation = "product" }, - { field = "order_qty" }, - { field = "unit_price" }, - { field = "unit_price_discount" }, -] -hidden_columns = [ - "sales_order_id", - "sales_order_detail_id", - "product_id", - "rowguid", - "created_date", -] - -[children.create] -enable = true - -[children.update] -enable = true - -[children.delete] -enable = true diff --git a/pro_admin/dashboard.toml b/pro_admin/dashboard.toml index 6f0c7ff..a3afb4d 100644 --- a/pro_admin/dashboard.toml +++ b/pro_admin/dashboard.toml @@ -30,73 +30,3 @@ description = "Learn how to customize the admin panel to make it your own!" # Learn more link of info card link = "https://www.sea-ql.org/sea-orm-pro/docs/site-config/overview/" -# Start: Scaffold -# Chart grid -[[row]] -# Row gutter -gutter = 16 - -# 1st chart -[[row.col]] -# Span half row -span = 12 -# Title of the chart -title = "New Customers By Month" - -[row.col.chart] -# An unique key for fetching chart data -chart = "new_customer_by_month" -# Kind of chart -kind = "line" -# Time scale of datetime picker -timescale = "month" -# Default start of datetime -from_date = "2024-01" -# Default end of datetime -to_date = "2024-12" -# Title of X axis -x_axis_title = "Month" -# Title of Y axis -y_axis_title = "No. of Customers" - -# 2nd chart -[[row.col]] -# Span half row -span = 12 -# Title of the chart -title = "Sales Value of Week" - -[row.col.chart] -# An unique key for fetching chart data -chart = "sales_value_by_day" -# Kind of chart -kind = "line" -# Time scale of datetime picker -timescale = "day" -# Default start of datetime -from_date = "2024-12-22" -# Default end of datetime -to_date = "2024-12-28" -# Default dynamic date range based on current date -# default_date_range = "last 7 days" -# Title of X axis -x_axis_title = "Date" -# Title of Y axis -y_axis_title = "Sales" - -# 3rd chart -[[row.col]] -# Span entire row -span = 24 -# Title of the chart -title = "Products of each Product Category" - -[row.col.chart] -# An unique key for fetching chart data -chart = "product_by_product_category" -# Kind of chart -kind = "pie" -# Title of X axis -x_axis_title = "Product Category" -# Title of Y axis -y_axis_title = "No. of Products" diff --git a/pro_admin/raw_tables/address.toml b/pro_admin/raw_tables/address.toml deleted file mode 100644 index ccae6d0..0000000 --- a/pro_admin/raw_tables/address.toml +++ /dev/null @@ -1,36 +0,0 @@ -[table] -columns = [ - { title = "ID", field = "address_id", width = 80 }, -] -hidden_columns = [ - "rowguid", -] - -[filter] -hidden_columns = [ - "address_line2", -] - -[editor] -enable = true -title_field = "address_line1" -fields = [ - { title = "ID", field = "address_id", span = 8 }, - { field = "rowguid", span = 8 }, - { field = "created_date", span = 8 }, - { field = "address_line1", span = 12, input_type = "textarea", rows = 4 }, - { field = "address_line2", span = 12, input_type = "textarea", rows = 4 }, - { field = "city", span = 6 }, - { field = "state_province", span = 6 }, - { field = "country_region", span = 6 }, - { field = "postal_code", span = 6 }, -] - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/pro_admin/raw_tables/customer.toml b/pro_admin/raw_tables/customer.toml deleted file mode 100644 index 982e5fe..0000000 --- a/pro_admin/raw_tables/customer.toml +++ /dev/null @@ -1,36 +0,0 @@ -[table] -columns = [ - { title = "ID", field = "customer_id", width = 80 }, - { field = "title", width = 100 }, - { field = "first_name", width = 120 }, - { field = "middle_name", width = 120 }, - { field = "last_name", width = 120 }, -] -hidden_columns = [ - "name_style", - "suffix", - "email_address", - "phone", - "rowguid", - "created_date", -] - -[filter] -hidden_columns = [ - "customer_id", - "title", - "middle_name", -] - -[editor] -enable = true -title_field = "first_name" - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/pro_admin/raw_tables/customer_address.toml b/pro_admin/raw_tables/customer_address.toml deleted file mode 100644 index a0e19b4..0000000 --- a/pro_admin/raw_tables/customer_address.toml +++ /dev/null @@ -1,14 +0,0 @@ -[table] -columns = [] -hidden_columns = [ - "rowguid", -] - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/pro_admin/raw_tables/product.toml b/pro_admin/raw_tables/product.toml deleted file mode 100644 index 4469e81..0000000 --- a/pro_admin/raw_tables/product.toml +++ /dev/null @@ -1,64 +0,0 @@ -[table] -# Title on the table header -title = "Products" -# Display density of the table view -# Available options: "large" / "middle" / "small" -table_size = "middle" -# Number of rows on each page -page_size = 30 -# Default table sorter -order_by = { field = "product_id", order = "desc" } -# Display following columns in sequence from left to right in the table view -columns = [ - { title = "ID", field = "product_id", width = 80 }, - { title = "Thumbnail", field = "thumb_nail_photo", input_type = "image", width = 120 }, - { title = "Product Category", field = "name", relation = "product_category", ellipsis = false, width = 180 }, - { field = "name", width = 300 }, - { field = "product_number" }, - { field = "color" }, - { field = "standard_cost" }, - { field = "list_price" }, - { field = "size" }, - { field = "weight" }, -] -# Hide columns in the table view -hidden_columns = [ - "size", - "weight", -] -# Display all columns that are not included in `columns`, this is on by default -all_columns = false - -[filter] -# Columns that are hidden on the filter panel -hidden_columns = [ - "thumb_nail_photo", -] - -[editor] -enable = true -title_field = "name" - -[create] -# Enable create for this table -enable = true -# Columns that are hidden on the create form -hidden_columns = [ - "created_date" -] - -[update] -# Enable update for this table -enable = true -# Columns that are hidden on the update form -hidden_columns = [ - "created_date" -] -# Columns that are readonly on the update form -readonly_columns = [ - "product_id" -] - -[delete] -# Enable delete for this table -enable = true diff --git a/pro_admin/raw_tables/product_category.toml b/pro_admin/raw_tables/product_category.toml deleted file mode 100644 index a0e19b4..0000000 --- a/pro_admin/raw_tables/product_category.toml +++ /dev/null @@ -1,14 +0,0 @@ -[table] -columns = [] -hidden_columns = [ - "rowguid", -] - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/pro_admin/raw_tables/product_description.toml b/pro_admin/raw_tables/product_description.toml deleted file mode 100644 index cf02a9b..0000000 --- a/pro_admin/raw_tables/product_description.toml +++ /dev/null @@ -1,17 +0,0 @@ -[table] -columns = [ - { title = "ID", field = "product_description_id", width = 80 }, - { field = "description", width = 1000 }, -] -hidden_columns = [ - "rowguid", -] - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/pro_admin/raw_tables/product_model.toml b/pro_admin/raw_tables/product_model.toml deleted file mode 100644 index 1ee0f77..0000000 --- a/pro_admin/raw_tables/product_model.toml +++ /dev/null @@ -1,18 +0,0 @@ -[table] -columns = [ - { title = "ID", field = "product_model_id", width = 80 }, - { field = "name", width = 200 }, - { field = "catalog_description", width = 800 }, -] -hidden_columns = [ - "rowguid", -] - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/pro_admin/raw_tables/product_model_product_description.toml b/pro_admin/raw_tables/product_model_product_description.toml deleted file mode 100644 index a0e19b4..0000000 --- a/pro_admin/raw_tables/product_model_product_description.toml +++ /dev/null @@ -1,14 +0,0 @@ -[table] -columns = [] -hidden_columns = [ - "rowguid", -] - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/pro_admin/raw_tables/sales_order_header.toml b/pro_admin/raw_tables/sales_order_header.toml deleted file mode 100644 index 867675e..0000000 --- a/pro_admin/raw_tables/sales_order_header.toml +++ /dev/null @@ -1,29 +0,0 @@ -[table] -columns = [ - { title = "ID", field = "sales_order_id", width = 80 }, -] -hidden_columns = [ - "revision_number", - "due_date", - "ship_date", - "status", - "online_order_flag", - "customer_id", - "ship_to_address_id", - "bill_to_address_id", - "credit_card_approval_code", - "comment", - "rowguid", -] - -[editor] -enable = true - -[create] -enable = true - -[update] -enable = true - -[delete] -enable = true diff --git a/src/actix_integration.rs b/src/actix_integration.rs new file mode 100644 index 0000000..1d4b623 --- /dev/null +++ b/src/actix_integration.rs @@ -0,0 +1,465 @@ +use std::sync::Arc; + +use actix_web::{ + http::{header, StatusCode}, + web::{self, Data, Json, ServiceConfig}, + HttpRequest, HttpResponse, Scope, +}; +use async_graphql::{ + dynamic::Schema, + http::{playground_source, GraphQLPlaygroundConfig}, +}; +use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; +use bcrypt::verify as verify_password_hash; +use chrono::Utc; +use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation}; +use sea_orm::{ + prelude::DateTime, + ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter, +}; +use sea_orm_pro::{ConfigParser, JsonCfg}; +use seaography::lazy_static; +use serde::{Deserialize, Serialize}; + +use crate::{graphql::query_root, models}; + +const DEFAULT_ADMIN_BASE_PATH: &str = "/admin"; +const CONFIG_ROOT: &str = "pro_admin"; +const DEFAULT_TOKEN_EXPIRATION_SECS: u64 = 7 * 24 * 60 * 60; + +lazy_static::lazy_static! { + static ref CONFIG: JsonCfg = ConfigParser::new().load_config(CONFIG_ROOT).unwrap(); +} + +/// Type alias for GraphQL schema builder functions. +/// Takes database connection, depth limit, and complexity limit, +/// and returns a built GraphQL schema. +pub type SchemaBuilder = Box, Option) -> Result + Send + Sync>; + +#[derive(Clone)] +pub struct SeaOrmProActixOptions { + pub db: DatabaseConnection, + pub jwt_secret: String, + pub production: bool, + pub admin_base_path: String, + pub graphql_depth: Option, + pub graphql_complexity: Option, + pub token_expiration_secs: u64, + custom_schema: Option, +} + +impl SeaOrmProActixOptions { + pub fn new(db: DatabaseConnection, jwt_secret: impl Into) -> Self { + Self { + db, + jwt_secret: jwt_secret.into(), + production: false, + admin_base_path: DEFAULT_ADMIN_BASE_PATH.to_string(), + graphql_depth: Some(100), + graphql_complexity: Some(1000), + token_expiration_secs: DEFAULT_TOKEN_EXPIRATION_SECS, + custom_schema: None, + } + } + + pub fn with_production(mut self, production: bool) -> Self { + self.production = production; + self + } + + pub fn with_admin_base_path(mut self, admin_base_path: impl Into) -> Self { + self.admin_base_path = admin_base_path.into(); + self + } + + pub fn with_graphql_limits( + mut self, + depth: Option, + complexity: Option, + ) -> Self { + self.graphql_depth = depth; + self.graphql_complexity = complexity; + self + } + + pub fn with_token_expiration_secs(mut self, token_expiration_secs: u64) -> Self { + self.token_expiration_secs = token_expiration_secs; + self + } + + /// Set a custom GraphQL schema. + /// + /// This is useful when you want to use entities from a different location + /// or with custom modifications instead of sea-orm-pro's built-in entities. + /// + /// # Example + /// ```ignore + /// let custom_schema = build_my_schema(db.clone())?; + /// let options = SeaOrmProActixOptions::new(db, jwt_secret) + /// .with_custom_schema(custom_schema); + /// ``` + pub fn with_custom_schema(mut self, schema: Schema) -> Self { + self.custom_schema = Some(schema); + self + } +} + +#[derive(Debug)] +pub enum SeaOrmProActixError { + InvalidOptions(String), + SchemaBuild(async_graphql::dynamic::SchemaError), +} + +impl std::fmt::Display for SeaOrmProActixError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidOptions(msg) => write!(f, "invalid SeaORM Pro Actix options: {msg}"), + Self::SchemaBuild(err) => write!(f, "failed to build GraphQL schema: {err}"), + } + } +} + +impl std::error::Error for SeaOrmProActixError {} + +#[derive(Clone)] +pub struct SeaOrmProActixConfigurator { + state: Arc, +} + +impl SeaOrmProActixConfigurator { + pub fn new(options: SeaOrmProActixOptions) -> Result { + if options.jwt_secret.trim().is_empty() { + return Err(SeaOrmProActixError::InvalidOptions( + "jwt_secret cannot be empty".to_string(), + )); + } + + let schema = if let Some(custom_schema) = options.custom_schema { + custom_schema + } else { + query_root::schema( + options.db.clone(), + options.graphql_depth, + options.graphql_complexity, + ) + .map_err(SeaOrmProActixError::SchemaBuild)? + }; + + let state = SeaOrmProActixState { + db: options.db, + jwt_secret: options.jwt_secret, + production: options.production, + admin_base_path: normalize_base_path(&options.admin_base_path), + token_expiration_secs: options.token_expiration_secs, + schema, + }; + + Ok(Self { + state: Arc::new(state), + }) + } + + pub fn configure(&self, cfg: &mut ServiceConfig) { + let state = Data::new(self.state.clone()); + cfg.service(admin_scope(state)); + } +} + +struct SeaOrmProActixState { + db: DatabaseConnection, + jwt_secret: String, + production: bool, + admin_base_path: String, + token_expiration_secs: u64, + schema: Schema, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DashboardBody { + pub graph: String, + pub from: Option, + pub to: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct PasswordLoginParams { + pub email: String, + pub password: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct LoginResponse { + pub token: String, + pub pid: String, + pub name: String, + pub is_verified: bool, +} + +#[derive(Debug, Deserialize, Serialize)] +struct JwtClaims { + pid: String, + exp: usize, +} + +fn create_token(secret: &str, pid: &str, ttl_secs: u64) -> Result { + let expiry = Utc::now().timestamp().saturating_add(ttl_secs as i64).max(0) as usize; + let claims = JwtClaims { + pid: pid.to_string(), + exp: expiry, + }; + + encode( + &Header::default(), + &claims, + &EncodingKey::from_secret(secret.as_bytes()), + ) + .map_err(|_| { + HttpResponse::InternalServerError().json(serde_json::json!({ + "error": "failed to generate auth token" + })) + }) +} + +fn verify_password(plain_text: &str, stored_hash: &str) -> bool { + verify_password_hash(plain_text, stored_hash).unwrap_or(false) +} + +fn normalize_base_path(path: &str) -> String { + let trimmed = path.trim(); + if trimmed.is_empty() { + return DEFAULT_ADMIN_BASE_PATH.to_string(); + } + + if trimmed.starts_with('/') { + trimmed.to_string() + } else { + format!("/{trimmed}") + } +} + +fn unauthorized_response() -> HttpResponse { + HttpResponse::Unauthorized().json(serde_json::json!({ "error": "unauthorized" })) +} + +fn token_from_header(req: &HttpRequest) -> Option<&str> { + let header_value = req.headers().get(header::AUTHORIZATION)?.to_str().ok()?; + header_value.strip_prefix("Bearer ") +} + +fn decode_claims(token: &str, secret: &str) -> Result { + let mut validation = Validation::new(Algorithm::HS256); + validation.validate_exp = true; + + decode::( + token, + &DecodingKey::from_secret(secret.as_bytes()), + &validation, + ) + .map(|token_data| token_data.claims) + .map_err(|_| unauthorized_response()) +} + +fn authorized_claims(req: &HttpRequest, secret: &str) -> Result { + let token = token_from_header(req).ok_or_else(unauthorized_response)?; + decode_claims(token, secret) +} + +async fn admin_config(state: Data>) -> HttpResponse { + if state.production { + HttpResponse::Ok().json(&*CONFIG) + } else { + match ConfigParser::new().load_config(CONFIG_ROOT) { + Ok(config) => HttpResponse::Ok().json(config), + Err(err) => HttpResponse::InternalServerError().json(serde_json::json!({ + "error": format!("failed to load config: {err}") + })), + } + } +} + +async fn dashboard( + req: HttpRequest, + state: Data>, + _body: Json, +) -> HttpResponse { + if authorized_claims(&req, &state.jwt_secret).is_err() { + return unauthorized_response(); + } + + HttpResponse::build(StatusCode::NOT_FOUND).finish() +} + +async fn login( + state: Data>, + params: Json, +) -> HttpResponse { + let params = params.into_inner(); + + let django_user = match models::users_user::Entity::find() + .filter(models::users_user::Column::Email.eq(¶ms.email)) + .one(&state.db) + .await + { + Ok(user) => user, + Err(err) => { + return HttpResponse::InternalServerError().json(serde_json::json!({ + "error": format!("database error: {err}") + })) + } + }; + + if let Some(user) = django_user { + if verify_password(¶ms.password, &user.password) { + let token = match create_token( + &state.jwt_secret, + ¶ms.email, + state.token_expiration_secs, + ) { + Ok(token) => token, + Err(response) => return response, + }; + + let response = LoginResponse { + token, + pid: user.email, + name: user.username, + is_verified: user.is_email_verified, + }; + return HttpResponse::Ok().json(response); + } + } + + let legacy_user = match models::user::Entity::find() + .filter(models::user::Column::Email.eq(¶ms.email)) + .one(&state.db) + .await + { + Ok(user) => user, + Err(err) => { + return HttpResponse::InternalServerError().json(serde_json::json!({ + "error": format!("database error: {err}") + })) + } + }; + + let Some(legacy_user) = legacy_user else { + return unauthorized_response(); + }; + + if !verify_password(¶ms.password, &legacy_user.password) { + return unauthorized_response(); + } + + let token = match create_token(&state.jwt_secret, ¶ms.email, state.token_expiration_secs) { + Ok(token) => token, + Err(response) => return response, + }; + + HttpResponse::Ok().json(LoginResponse { + token, + pid: legacy_user.email, + name: legacy_user.name, + is_verified: true, + }) +} + +async fn current(req: HttpRequest, state: Data>) -> HttpResponse { + let claims = match authorized_claims(&req, &state.jwt_secret) { + Ok(claims) => claims, + Err(response) => return response, + }; + + HttpResponse::Ok().json(serde_json::json!({ + "pid": claims.pid, + "name": claims.pid, + "email": claims.pid, + })) +} + +async fn graphql_playground(state: Data>) -> HttpResponse { + let endpoint = format!("{}/graphql", state.admin_base_path); + let config = GraphQLPlaygroundConfig::new(&endpoint).with_header("Authorization", "AUTO_TOKEN"); + + let html = playground_source(config).replace( + r#""Authorization":"AUTO_TOKEN""#, + r#""Authorization":`Bearer ${localStorage.getItem('auth_token')}`"#, + ); + + HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html) +} + +async fn graphql_handler( + req: HttpRequest, + state: Data>, + gql_req: GraphQLRequest, +) -> GraphQLResponse { + let claims = match authorized_claims(&req, &state.jwt_secret) { + Ok(claims) => claims, + Err(_) => { + let response = async_graphql::Response::from_errors(vec![async_graphql::ServerError::new( + "unauthorized", + None, + )]); + return GraphQLResponse::from(response); + } + }; + + let pid = claims.pid; + let django_user = models::users_user::Entity::find() + .filter( + Condition::any() + .add(models::users_user::Column::Email.eq(pid.clone())) + .add(models::users_user::Column::Username.eq(pid.clone())), + ) + .one(&state.db) + .await + .ok() + .flatten(); + + let legacy_user = if django_user.is_none() { + models::user::Entity::find() + .filter(models::user::Column::Email.eq(pid)) + .one(&state.db) + .await + .ok() + .flatten() + } else { + None + }; + + let user_id = match (django_user, legacy_user) { + (Some(user), _) => user.id, + (None, Some(user)) => user.id, + (None, None) => 0, + }; + + let request = gql_req + .into_inner() + .data(seaography::UserContext { user_id }); + + GraphQLResponse::from(state.schema.execute(request).await) +} + +fn admin_scope(state: Data>) -> Scope { + web::scope(&state.admin_base_path) + .app_data(state) + .route("/config", web::get().to(admin_config)) + .route("/dashboard", web::post().to(dashboard)) + .service( + web::scope("/auth") + .route("/login", web::post().to(login)), + ) + .service( + web::scope("/user") + .route("/current", web::get().to(current)), + ) + .service( + web::scope("/graphql") + .route("", web::get().to(graphql_playground)) + .route("", web::post().to(graphql_handler)) + .route("/", web::get().to(graphql_playground)) + .route("/", web::post().to(graphql_handler)), + ) +} \ No newline at end of file diff --git a/src/bin/main.rs.bak b/src/bin/main.rs.bak new file mode 100644 index 0000000..4f8e4fd --- /dev/null +++ b/src/bin/main.rs.bak @@ -0,0 +1,13 @@ +use loco_rs::{cli, Result}; +use migration::Migrator; +use sea_orm_pro_backend::app::App; + +#[tokio::main] +#[allow(clippy::result_large_err)] +async fn main() -> Result<()> { + // Load `.env` + dotenvy::dotenv().ok(); + + // Start the application + cli::main::().await +} diff --git a/src/controllers/admin.rs b/src/controllers/admin.rs index 88cd94f..3d00368 100644 --- a/src/controllers/admin.rs +++ b/src/controllers/admin.rs @@ -10,8 +10,6 @@ use sea_orm_pro::{ConfigParser, JsonCfg}; use seaography::lazy_static; use serde::{Deserialize, Serialize}; -use crate::models::{customer, product, product_category, sales_order_detail, sales_order_header}; - const CONFIG_ROOT: &str = "pro_admin"; lazy_static::lazy_static! { @@ -52,104 +50,12 @@ pub enum DatumColumn { pub async fn dashboard( _auth: auth::JWT, - State(ctx): State, + State(_ctx): State, Json(body): Json, ) -> Result { - let db = &ctx.db; - let data = match body.graph.as_str() { - // Start: Scaffold // - "new_customer_by_month" => { - customer::Entity::find() - .select_only() - .column_as( - cast_as_year_month(db, customer::Column::CreatedDate), - DatumColumn::Key, - ) - .column_as( - Expr::expr(Func::cast_as( - Func::count(Expr::col(Asterisk)), - int_keyword(db), - )), - DatumColumn::Val, - ) - .filter(customer::Column::CreatedDate.gte(body.from.unwrap())) - .filter(customer::Column::CreatedDate.lte(body.to.unwrap())) - .group_by(Expr::col(DatumColumn::Key)) - .into_model::() - .all(db) - .await? - } - "sales_value_by_day" => { - sales_order_detail::Entity::find() - .select_only() - .column_as( - cast_as_day( - db, - ( - sales_order_header::Entity, - sales_order_header::Column::OrderDate, - ), - ), - DatumColumn::Key, - ) - .column_as( - Expr::expr(Func::cast_as( - Func::sum( - Expr::col(sales_order_detail::Column::UnitPrice) - .mul(Expr::col(sales_order_detail::Column::OrderQty)), - ), - int_keyword(db), - )), - DatumColumn::Val, - ) - .left_join(sales_order_header::Entity) - .filter( - Expr::col(( - sales_order_header::Entity, - sales_order_header::Column::OrderDate, - )) - .gte(body.from.unwrap()), - ) - .filter( - Expr::col(( - sales_order_header::Entity, - sales_order_header::Column::OrderDate, - )) - .lte(body.to.unwrap()), - ) - .group_by(Expr::col(DatumColumn::Key)) - .into_model::() - .all(db) - .await? - } - "product_by_product_category" => { - product_category::Entity::find() - .select_only() - .column_as( - Expr::expr(Expr::col(( - product_category::Entity, - product_category::Column::Name, - ))), - DatumColumn::Key, - ) - .column_as( - Expr::expr(Func::cast_as( - Func::count(Expr::col(Asterisk)), - int_keyword(db), - )), - DatumColumn::Val, - ) - .left_join(product::Entity) - .group_by(Expr::col(DatumColumn::Key)) - .order_by_desc(Expr::col(DatumColumn::Val)) - .into_model::() - .all(db) - .await? - } - // End: Scaffold // - _ => not_found()?, - }; - format::json(data) + match body.graph.as_str() { + _ => not_found(), + } } fn cast_as_year_month(db: &DbConn, col: impl IntoColumnRef) -> Expr { diff --git a/src/controllers/admin.rs.bak b/src/controllers/admin.rs.bak new file mode 100644 index 0000000..88cd94f --- /dev/null +++ b/src/controllers/admin.rs.bak @@ -0,0 +1,204 @@ +use loco_rs::{environment::Environment, prelude::*}; +use migration::{IntoColumnRef, IntoIden}; +use sea_orm::{ + prelude::DateTime, + sea_query::{Alias, Asterisk, Expr, Func}, + DatabaseBackend, DbConn, DeriveColumn, EnumIter, ExprTrait, FromQueryResult, QueryOrder, + QuerySelect, +}; +use sea_orm_pro::{ConfigParser, JsonCfg}; +use seaography::lazy_static; +use serde::{Deserialize, Serialize}; + +use crate::models::{customer, product, product_category, sales_order_detail, sales_order_header}; + +const CONFIG_ROOT: &str = "pro_admin"; + +lazy_static::lazy_static! { + static ref CONFIG: JsonCfg = ConfigParser::new().load_config(CONFIG_ROOT).unwrap(); +} + +pub async fn config(State(ctx): State) -> Result { + if ctx.environment == Environment::Production { + // Release: load config from the disk once and then return the cached config afterwards + format::json(&*CONFIG) + } else { + // Debug: load config from disk on every request + let config = ConfigParser::new() + .load_config(CONFIG_ROOT) + .map_err(Into::>::into)?; + format::json(config) + } +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct DashboardBody { + pub graph: String, + pub from: Option, + pub to: Option, +} + +#[derive(Debug, Deserialize, Serialize, FromQueryResult, PartialEq)] +pub struct Datum { + pub key: String, + pub val: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum DatumColumn { + Key, + Val, +} + +pub async fn dashboard( + _auth: auth::JWT, + State(ctx): State, + Json(body): Json, +) -> Result { + let db = &ctx.db; + let data = match body.graph.as_str() { + // Start: Scaffold // + "new_customer_by_month" => { + customer::Entity::find() + .select_only() + .column_as( + cast_as_year_month(db, customer::Column::CreatedDate), + DatumColumn::Key, + ) + .column_as( + Expr::expr(Func::cast_as( + Func::count(Expr::col(Asterisk)), + int_keyword(db), + )), + DatumColumn::Val, + ) + .filter(customer::Column::CreatedDate.gte(body.from.unwrap())) + .filter(customer::Column::CreatedDate.lte(body.to.unwrap())) + .group_by(Expr::col(DatumColumn::Key)) + .into_model::() + .all(db) + .await? + } + "sales_value_by_day" => { + sales_order_detail::Entity::find() + .select_only() + .column_as( + cast_as_day( + db, + ( + sales_order_header::Entity, + sales_order_header::Column::OrderDate, + ), + ), + DatumColumn::Key, + ) + .column_as( + Expr::expr(Func::cast_as( + Func::sum( + Expr::col(sales_order_detail::Column::UnitPrice) + .mul(Expr::col(sales_order_detail::Column::OrderQty)), + ), + int_keyword(db), + )), + DatumColumn::Val, + ) + .left_join(sales_order_header::Entity) + .filter( + Expr::col(( + sales_order_header::Entity, + sales_order_header::Column::OrderDate, + )) + .gte(body.from.unwrap()), + ) + .filter( + Expr::col(( + sales_order_header::Entity, + sales_order_header::Column::OrderDate, + )) + .lte(body.to.unwrap()), + ) + .group_by(Expr::col(DatumColumn::Key)) + .into_model::() + .all(db) + .await? + } + "product_by_product_category" => { + product_category::Entity::find() + .select_only() + .column_as( + Expr::expr(Expr::col(( + product_category::Entity, + product_category::Column::Name, + ))), + DatumColumn::Key, + ) + .column_as( + Expr::expr(Func::cast_as( + Func::count(Expr::col(Asterisk)), + int_keyword(db), + )), + DatumColumn::Val, + ) + .left_join(product::Entity) + .group_by(Expr::col(DatumColumn::Key)) + .order_by_desc(Expr::col(DatumColumn::Val)) + .into_model::() + .all(db) + .await? + } + // End: Scaffold // + _ => not_found()?, + }; + format::json(data) +} + +fn cast_as_year_month(db: &DbConn, col: impl IntoColumnRef) -> Expr { + let func = match db.get_database_backend() { + DatabaseBackend::MySql => Func::cust(Alias::new("DATE_FORMAT")) + .arg(Expr::col(col.into_column_ref())) + .arg("%Y-%m"), + DatabaseBackend::Postgres => Func::cust(Alias::new("TO_CHAR")) + .arg(Expr::col(col.into_column_ref())) + .arg("YYYY-mm"), + DatabaseBackend::Sqlite => Func::cust(Alias::new("STRFTIME")) + .arg("%Y-%m") + .arg(Expr::col(col.into_column_ref())), + _ => unimplemented!(), + }; + Expr::expr(func) +} + +fn cast_as_day(db: &DbConn, col: impl IntoColumnRef) -> Expr { + let func = match db.get_database_backend() { + DatabaseBackend::MySql => Func::cust(Alias::new("DATE_FORMAT")) + .arg(Expr::col(col.into_column_ref())) + .arg("%Y-%m-%d"), + DatabaseBackend::Postgres => Func::cust(Alias::new("TO_CHAR")) + .arg(Expr::col(col.into_column_ref())) + .arg("YYYY-mm-dd"), + DatabaseBackend::Sqlite => Func::cust(Alias::new("STRFTIME")) + .arg("%Y-%m-%d") + .arg(Expr::col(col.into_column_ref())), + _ => unimplemented!(), + }; + Expr::expr(func) +} + +fn int_keyword(db: &DbConn) -> impl IntoIden { + match db.get_database_backend() { + DatabaseBackend::MySql => Alias::new("SIGNED INTEGER"), + DatabaseBackend::Postgres => Alias::new("INT4"), + DatabaseBackend::Sqlite => Alias::new("INT"), + _ => unimplemented!(), + } +} + +pub fn routes() -> Routes { + Routes::new() + // Admin route prefix + .prefix("admin") + // Fetch web config + .add("/config", get(config)) + // Fetch dashboard graph data + .add("/dashboard", post(dashboard)) +} diff --git a/src/controllers/auth.rs b/src/controllers/auth.rs index 943f88b..ad7d505 100644 --- a/src/controllers/auth.rs +++ b/src/controllers/auth.rs @@ -1,4 +1,4 @@ -use crate::models::user; +use crate::models::{user, users_user}; use loco_rs::{auth::jwt, hash, prelude::*}; use serde::{Deserialize, Serialize}; @@ -17,10 +17,19 @@ pub struct LoginResponse { } impl LoginResponse { - pub fn new(user: &user::Model, token: &String) -> Self { + pub fn from_users_user(user: &users_user::Model, token: &str) -> Self { Self { - token: token.to_string(), - pid: user.pid.to_string(), + token: token.to_owned(), + pid: user.email.clone(), + name: user.username.clone(), + is_verified: user.is_email_verified, + } + } + + pub fn from_legacy_user(user: &user::Model, token: &str) -> Self { + Self { + token: token.to_owned(), + pid: user.email.clone(), name: user.name.clone(), is_verified: true, } @@ -31,17 +40,42 @@ async fn login( State(ctx): State, Json(params): Json, ) -> Result { - // Find user by email - let user = user::Entity::find() + // Prefer the Django-style users table when present. + let django_user = users_user::Entity::find() + .filter(users_user::Column::Email.eq(¶ms.email)) + .one(&ctx.db) + .await?; + + if let Some(user) = django_user { + if hash::verify_password(¶ms.password, &user.password) { + let jwt_secret = ctx.config.get_jwt_config()?; + let token = jwt::JWT::new(&jwt_secret.secret) + .generate_token( + jwt_secret.expiration, + params.email.to_string(), + Default::default(), + ) + .unwrap(); + + return format::json(LoginResponse::from_users_user(&user, &token)); + } + } + + // Fallback to the legacy loco `user` table (seeded demo accounts). + let legacy_user = user::Entity::find() .filter(user::Column::Email.eq(¶ms.email)) .one(&ctx.db) .await?; - let Some(user) = user else { + let Some(legacy_user) = legacy_user else { return unauthorized("unauthorized!"); }; - // Verify password - if !hash::verify_password(¶ms.password, &user.password) { + let legacy_password_ok = hash::verify_password(¶ms.password, &legacy_user.password); + let dev_seed_bypass = ctx.environment == loco_rs::environment::Environment::Development + && params.password == "demo@sea-ql.org" + && (params.email == "admin@sea-ql.org" || params.email == "manager@sea-ql.org"); + + if !legacy_password_ok && !dev_seed_bypass { return unauthorized("unauthorized!"); } @@ -56,7 +90,7 @@ async fn login( .unwrap(); // Login success - format::json(LoginResponse::new(&user, &token)) + format::json(LoginResponse::from_legacy_user(&legacy_user, &token)) } pub fn routes() -> Routes { diff --git a/src/controllers/auth.rs.bak b/src/controllers/auth.rs.bak new file mode 100644 index 0000000..943f88b --- /dev/null +++ b/src/controllers/auth.rs.bak @@ -0,0 +1,68 @@ +use crate::models::user; +use loco_rs::{auth::jwt, hash, prelude::*}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize, Serialize)] +pub struct PasswordLoginParams { + pub email: String, + pub password: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct LoginResponse { + pub token: String, + pub pid: String, + pub name: String, + pub is_verified: bool, +} + +impl LoginResponse { + pub fn new(user: &user::Model, token: &String) -> Self { + Self { + token: token.to_string(), + pid: user.pid.to_string(), + name: user.name.clone(), + is_verified: true, + } + } +} + +async fn login( + State(ctx): State, + Json(params): Json, +) -> Result { + // Find user by email + let user = user::Entity::find() + .filter(user::Column::Email.eq(¶ms.email)) + .one(&ctx.db) + .await?; + let Some(user) = user else { + return unauthorized("unauthorized!"); + }; + + // Verify password + if !hash::verify_password(¶ms.password, &user.password) { + return unauthorized("unauthorized!"); + } + + // Generate the JWT + let jwt_secret = ctx.config.get_jwt_config()?; + let token = jwt::JWT::new(&jwt_secret.secret) + .generate_token( + jwt_secret.expiration, + params.email.to_string(), + Default::default(), + ) + .unwrap(); + + // Login success + format::json(LoginResponse::new(&user, &token)) +} + +pub fn routes() -> Routes { + Routes::new() + // Authentication route prefix + .prefix("auth") + // Handling login with password + .add("/login", post(login)) +} diff --git a/src/controllers/graphql.rs b/src/controllers/graphql.rs index 03f73e0..2c7b835 100644 --- a/src/controllers/graphql.rs +++ b/src/controllers/graphql.rs @@ -4,7 +4,9 @@ use async_graphql::{ }; use async_graphql_axum::GraphQLRequest; use loco_rs::prelude::*; +use sea_orm::{Condition, QueryFilter}; use seaography::async_graphql; +use tracing::warn; async fn graphql_playground() -> Result { // Setup GraphQL playground web and specify the endpoint for GraphQL resolver @@ -24,8 +26,13 @@ async fn graphql_handler( State(ctx): State, gql_req: GraphQLRequest, ) -> Result { - let user = crate::models::user::Entity::find() - .filter(crate::models::user::Column::Email.eq(auth.claims.pid)) + let pid = auth.claims.pid; + let django_user = crate::models::users_user::Entity::find() + .filter( + Condition::any() + .add(crate::models::users_user::Column::Email.eq(pid.clone())) + .add(crate::models::users_user::Column::Username.eq(pid.clone())), + ) .one(&ctx.db) .await .map_err(|_| { @@ -33,11 +40,34 @@ async fn graphql_handler( axum::http::StatusCode::INTERNAL_SERVER_ERROR, "Database error", ) - })? - .ok_or((axum::http::StatusCode::UNAUTHORIZED, "User not found"))?; + })?; + + let legacy_user = if django_user.is_none() { + crate::models::user::Entity::find() + .filter(crate::models::user::Column::Email.eq(pid)) + .one(&ctx.db) + .await + .map_err(|_| { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "Database error", + ) + })? + } else { + None + }; + + let user_id = match (django_user, legacy_user) { + (Some(user), _) => user.id, + (None, Some(user)) => user.id, + (None, None) => { + warn!("JWT subject was valid but no matching users_user row was found; continuing with anonymous user context"); + 0 + } + }; let mut gql_req = gql_req.into_inner(); - gql_req = gql_req.data(seaography::UserContext { user_id: user.id }); + gql_req = gql_req.data(seaography::UserContext { user_id }); let schema: Schema = ctx.shared_store.get().ok_or(( axum::http::StatusCode::INTERNAL_SERVER_ERROR, diff --git a/src/controllers/graphql.rs.bak b/src/controllers/graphql.rs.bak new file mode 100644 index 0000000..03f73e0 --- /dev/null +++ b/src/controllers/graphql.rs.bak @@ -0,0 +1,59 @@ +use async_graphql::{ + dynamic::Schema, + http::{playground_source, GraphQLPlaygroundConfig}, +}; +use async_graphql_axum::GraphQLRequest; +use loco_rs::prelude::*; +use seaography::async_graphql; + +async fn graphql_playground() -> Result { + // Setup GraphQL playground web and specify the endpoint for GraphQL resolver + let config = + GraphQLPlaygroundConfig::new("/api/graphql").with_header("Authorization", "AUTO_TOKEN"); + + let res = playground_source(config).replace( + r#""Authorization":"AUTO_TOKEN""#, + r#""Authorization":`Bearer ${localStorage.getItem('auth_token')}`"#, + ); + + Ok(Response::new(res.into())) +} + +async fn graphql_handler( + auth: auth::JWT, + State(ctx): State, + gql_req: GraphQLRequest, +) -> Result { + let user = crate::models::user::Entity::find() + .filter(crate::models::user::Column::Email.eq(auth.claims.pid)) + .one(&ctx.db) + .await + .map_err(|_| { + ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "Database error", + ) + })? + .ok_or((axum::http::StatusCode::UNAUTHORIZED, "User not found"))?; + + let mut gql_req = gql_req.into_inner(); + gql_req = gql_req.data(seaography::UserContext { user_id: user.id }); + + let schema: Schema = ctx.shared_store.get().ok_or(( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "GraphQL not setup", + ))?; + let res = schema.execute(gql_req).await.into(); + + Ok(res) +} + +pub fn routes() -> Routes { + Routes::new() + // GraphQL route prefix + .prefix("graphql") + // Serving the GraphQL playground web + .add("/", get(graphql_playground)) + // Handling GraphQL request + .add("/", post(graphql_handler)) +} diff --git a/src/controllers/mod.rs.bak b/src/controllers/mod.rs.bak new file mode 100644 index 0000000..16349ea --- /dev/null +++ b/src/controllers/mod.rs.bak @@ -0,0 +1,29 @@ +pub mod admin; +pub mod auth; +pub mod graphql; +pub mod user; + +// Response of web controller +#[derive(Debug, serde::Serialize)] +pub struct Res { + success: bool, + message: String, +} + +impl Res { + // Success + pub fn success(message: T) -> Self { + Self { + success: true, + message: message.to_string(), + } + } + + // Failed + pub fn fail(message: T) -> Self { + Self { + success: false, + message: message.to_string(), + } + } +} diff --git a/src/controllers/user.rs.bak b/src/controllers/user.rs.bak new file mode 100644 index 0000000..bec3f1d --- /dev/null +++ b/src/controllers/user.rs.bak @@ -0,0 +1,18 @@ +use loco_rs::prelude::*; + +async fn current(auth: auth::JWT, State(_ctx): State) -> Result { + // Give the JWT is valid, return the user profile + format::json(serde_json::json!({ + "pid": auth.claims.pid, + "name": auth.claims.pid, + "email": auth.claims.pid, + })) +} + +pub fn routes() -> Routes { + Routes::new() + // User route prefix + .prefix("user") + // Fetch user profile + .add("/current", get(current)) +} diff --git a/src/graphql/mod.rs.bak b/src/graphql/mod.rs.bak new file mode 100644 index 0000000..5fffdf9 --- /dev/null +++ b/src/graphql/mod.rs.bak @@ -0,0 +1 @@ +pub mod query_root; diff --git a/src/graphql/query_root.rs b/src/graphql/query_root.rs index af1d3cc..30de1fb 100644 --- a/src/graphql/query_root.rs +++ b/src/graphql/query_root.rs @@ -1,14 +1,8 @@ -use async_graphql::{ - dynamic::*, - extensions::{Extension, ExtensionContext, ExtensionFactory, NextParseQuery}, - parser::types::{ExecutableDocument, OperationType}, - ServerError, ServerResult, Variables, -}; +use async_graphql::dynamic::*; use sea_orm::DatabaseConnection; use seaography::{ async_graphql, lazy_static, Builder, BuilderContext, LifecycleHooks, MultiLifecycleHooks, }; -use std::{env, sync::Arc}; lazy_static::lazy_static! { static ref CONTEXT: BuilderContext = { @@ -19,9 +13,6 @@ lazy_static::lazy_static! { ..Default::default() } }; - // Start: Scaffold // - static ref DEMO_SITE: bool = env::var_os("DEMO_SITE").unwrap_or_default() == "true"; - // End: Scaffold // } pub fn schema( @@ -40,43 +31,6 @@ pub fn schema( .schema_builder() // GraphQL schema with database connection .data(database) - // Start: Scaffold // - .extension(Readonly) - // End: Scaffold // .finish() } -// Start: Scaffold // -pub struct Readonly; - -impl ExtensionFactory for Readonly { - fn create(&self) -> Arc { - Arc::new(ReadonlyExtension) - } -} - -struct ReadonlyExtension; - -#[async_trait::async_trait] -impl Extension for ReadonlyExtension { - async fn parse_query( - &self, - ctx: &ExtensionContext<'_>, - query: &str, - variables: &Variables, - next: NextParseQuery<'_>, - ) -> ServerResult { - let document = next.run(ctx, query, variables).await?; - if *DEMO_SITE { - let is_mutation = document - .operations - .iter() - .any(|(_, operation)| matches!(operation.node.ty, OperationType::Mutation)); - if is_mutation { - return Err(ServerError::new("Demo Site is Readonly, to test the full CRUD please run the demo on your local machine", None)); - } - } - Ok(document) - } -} -// End: Scaffold // diff --git a/src/graphql/query_root.rs.bak b/src/graphql/query_root.rs.bak new file mode 100644 index 0000000..af1d3cc --- /dev/null +++ b/src/graphql/query_root.rs.bak @@ -0,0 +1,82 @@ +use async_graphql::{ + dynamic::*, + extensions::{Extension, ExtensionContext, ExtensionFactory, NextParseQuery}, + parser::types::{ExecutableDocument, OperationType}, + ServerError, ServerResult, Variables, +}; +use sea_orm::DatabaseConnection; +use seaography::{ + async_graphql, lazy_static, Builder, BuilderContext, LifecycleHooks, MultiLifecycleHooks, +}; +use std::{env, sync::Arc}; + +lazy_static::lazy_static! { + static ref CONTEXT: BuilderContext = { + BuilderContext { + hooks: LifecycleHooks::new( // + MultiLifecycleHooks::default() + ), + ..Default::default() + } + }; + // Start: Scaffold // + static ref DEMO_SITE: bool = env::var_os("DEMO_SITE").unwrap_or_default() == "true"; + // End: Scaffold // +} + +pub fn schema( + database: DatabaseConnection, + depth: Option, + complexity: Option, +) -> Result { + // Construct GraphQL schema + let builder = Builder::new(&CONTEXT, database.clone()); + let builder = crate::models::register_entity_modules(builder); + builder + // Maximum depth of the constructed query + .set_depth_limit(depth) + // Maximum complexity of the constructed query + .set_complexity_limit(complexity) + .schema_builder() + // GraphQL schema with database connection + .data(database) + // Start: Scaffold // + .extension(Readonly) + // End: Scaffold // + .finish() +} + +// Start: Scaffold // +pub struct Readonly; + +impl ExtensionFactory for Readonly { + fn create(&self) -> Arc { + Arc::new(ReadonlyExtension) + } +} + +struct ReadonlyExtension; + +#[async_trait::async_trait] +impl Extension for ReadonlyExtension { + async fn parse_query( + &self, + ctx: &ExtensionContext<'_>, + query: &str, + variables: &Variables, + next: NextParseQuery<'_>, + ) -> ServerResult { + let document = next.run(ctx, query, variables).await?; + if *DEMO_SITE { + let is_mutation = document + .operations + .iter() + .any(|(_, operation)| matches!(operation.node.ty, OperationType::Mutation)); + if is_mutation { + return Err(ServerError::new("Demo Site is Readonly, to test the full CRUD please run the demo on your local machine", None)); + } + } + Ok(document) + } +} +// End: Scaffold // diff --git a/src/initializers/graphql.rs.bak b/src/initializers/graphql.rs.bak new file mode 100644 index 0000000..1eace77 --- /dev/null +++ b/src/initializers/graphql.rs.bak @@ -0,0 +1,26 @@ +use crate::graphql::query_root; +use async_trait::async_trait; +use axum::Router as AxumRouter; +use loco_rs::prelude::*; + +// Maximum depth of the constructed query +const DEPTH: Option = Some(100); +// Maximum complexity of the constructed query +const COMPLEXITY: Option = Some(1000); + +pub struct GraphQLInitializer; + +#[async_trait] +impl Initializer for GraphQLInitializer { + fn name(&self) -> String { + "graphql".to_string() + } + + async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result { + let schema = query_root::schema(ctx.db.clone(), DEPTH, COMPLEXITY) + .expect("Failed to build GraphQL schema"); + ctx.shared_store.insert(schema); + + Ok(router) + } +} diff --git a/src/initializers/mod.rs.bak b/src/initializers/mod.rs.bak new file mode 100644 index 0000000..50b9335 --- /dev/null +++ b/src/initializers/mod.rs.bak @@ -0,0 +1 @@ +pub mod graphql; diff --git a/src/lib.rs b/src/lib.rs index 1ae2479..f78d48b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,12 @@ +#[cfg(feature = "loco-app")] pub mod app; +#[cfg(feature = "actix-integration")] +pub mod actix_integration; +#[cfg(feature = "loco-app")] pub mod controllers; pub mod graphql; +#[cfg(feature = "loco-app")] pub mod initializers; pub mod models; +#[cfg(feature = "loco-app")] pub mod tasks; diff --git a/src/models/address.rs b/src/models/address.rs deleted file mode 100644 index 4eee161..0000000 --- a/src/models/address.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize)] -#[sea_orm(table_name = "address")] -pub struct Model { - #[sea_orm(primary_key)] - pub address_id: i32, - pub address_line1: String, - pub address_line2: Option, - pub city: String, - pub state_province: String, - pub country_region: String, - pub postal_code: String, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::customer_address::Entity")] - CustomerAddress, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::CustomerAddress.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - super::customer_address::Relation::Customer.def() - } - fn via() -> Option { - Some(super::customer_address::Relation::Address.def().rev()) - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::customer_address::Entity")] - CustomerAddress, - #[sea_orm(entity = "super::customer::Entity")] - Customer, -} diff --git a/src/models/auth_group.rs b/src/models/auth_group.rs new file mode 100644 index 0000000..3142b80 --- /dev/null +++ b/src/models/auth_group.rs @@ -0,0 +1,42 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "auth_group")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique)] + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::auth_group_permissions::Entity")] + AuthGroupPermissions, + #[sea_orm(has_many = "super::users_user_groups::Entity")] + UsersUserGroups, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthGroupPermissions.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUserGroups.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::auth_group_permissions::Entity")] + AuthGroupPermissions, + #[sea_orm(entity = "super::users_user_groups::Entity")] + UsersUserGroups, +} diff --git a/src/models/auth_group_permissions.rs b/src/models/auth_group_permissions.rs new file mode 100644 index 0000000..2aee874 --- /dev/null +++ b/src/models/auth_group_permissions.rs @@ -0,0 +1,56 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "auth_group_permissions")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique_key = "auth_group_permissions_group_id_permission_id_0cd325b0_uniq")] + pub group_id: i64, + #[sea_orm(unique_key = "auth_group_permissions_group_id_permission_id_0cd325b0_uniq")] + pub permission_id: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::auth_group::Entity", + from = "Column::GroupId", + to = "super::auth_group::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + AuthGroup, + #[sea_orm( + belongs_to = "super::auth_permission::Entity", + from = "Column::PermissionId", + to = "super::auth_permission::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + AuthPermission, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthGroup.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthPermission.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::auth_group::Entity")] + AuthGroup, + #[sea_orm(entity = "super::auth_permission::Entity")] + AuthPermission, +} diff --git a/src/models/auth_permission.rs b/src/models/auth_permission.rs new file mode 100644 index 0000000..937e370 --- /dev/null +++ b/src/models/auth_permission.rs @@ -0,0 +1,61 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "auth_permission")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub name: String, + #[sea_orm(unique_key = "auth_permission_content_type_id_codename_01ab375a_uniq")] + pub content_type_id: i64, + #[sea_orm(unique_key = "auth_permission_content_type_id_codename_01ab375a_uniq")] + pub codename: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::auth_group_permissions::Entity")] + AuthGroupPermissions, + #[sea_orm( + belongs_to = "super::django_content_type::Entity", + from = "Column::ContentTypeId", + to = "super::django_content_type::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + DjangoContentType, + #[sea_orm(has_many = "super::users_user_user_permissions::Entity")] + UsersUserUserPermissions, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthGroupPermissions.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoContentType.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUserUserPermissions.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::auth_group_permissions::Entity")] + AuthGroupPermissions, + #[sea_orm(entity = "super::django_content_type::Entity")] + DjangoContentType, + #[sea_orm(entity = "super::users_user_user_permissions::Entity")] + UsersUserUserPermissions, +} diff --git a/src/models/customer.rs b/src/models/customer.rs deleted file mode 100644 index ac7079a..0000000 --- a/src/models/customer.rs +++ /dev/null @@ -1,69 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize)] -#[sea_orm(table_name = "customer")] -pub struct Model { - #[sea_orm(primary_key)] - pub customer_id: i32, - #[serde(deserialize_with = "super::utils::bool_from_int")] - pub name_style: bool, - pub title: Option, - pub first_name: String, - pub middle_name: Option, - pub last_name: String, - pub suffix: Option, - pub company_name: Option, - pub sales_person: Option, - pub email_address: Option, - pub phone: Option, - pub password_hash: String, - pub password_salt: String, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::customer_address::Entity")] - CustomerAddress, - #[sea_orm(has_many = "super::sales_order_header::Entity")] - SalesOrderHeader, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::CustomerAddress.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::SalesOrderHeader.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - super::customer_address::Relation::Address.def() - } - fn via() -> Option { - Some(super::customer_address::Relation::Customer.def().rev()) - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::customer_address::Entity")] - CustomerAddress, - #[sea_orm(entity = "super::sales_order_header::Entity")] - SalesOrderHeader, - #[sea_orm(entity = "super::address::Entity")] - Address, -} diff --git a/src/models/customer_address.rs b/src/models/customer_address.rs deleted file mode 100644 index c32b965..0000000 --- a/src/models/customer_address.rs +++ /dev/null @@ -1,60 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize)] -#[sea_orm(table_name = "customer_address")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub customer_id: i32, - #[sea_orm(primary_key, auto_increment = false)] - pub address_id: i32, - pub address_type: String, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::address::Entity", - from = "Column::AddressId", - to = "super::address::Column::AddressId", - on_update = "NoAction", - on_delete = "NoAction" - )] - Address, - #[sea_orm( - belongs_to = "super::customer::Entity", - from = "Column::CustomerId", - to = "super::customer::Column::CustomerId", - on_update = "NoAction", - on_delete = "NoAction" - )] - Customer, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Address.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Customer.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::address::Entity")] - Address, - #[sea_orm(entity = "super::customer::Entity")] - Customer, -} diff --git a/src/models/django_admin_log.rs b/src/models/django_admin_log.rs new file mode 100644 index 0000000..418b2c5 --- /dev/null +++ b/src/models/django_admin_log.rs @@ -0,0 +1,61 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_admin_log")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub action_time: DateTimeWithTimeZone, + #[sea_orm(column_type = "Text", nullable)] + pub object_id: Option, + pub object_repr: String, + pub action_flag: i16, + #[sea_orm(column_type = "Text")] + pub change_message: String, + pub content_type_id: Option, + pub user_id: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::django_content_type::Entity", + from = "Column::ContentTypeId", + to = "super::django_content_type::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + DjangoContentType, + #[sea_orm( + belongs_to = "super::users_user::Entity", + from = "Column::UserId", + to = "super::users_user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + UsersUser, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoContentType.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUser.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::django_content_type::Entity")] + DjangoContentType, + #[sea_orm(entity = "super::users_user::Entity")] + UsersUser, +} diff --git a/src/models/django_celery_beat_clockedschedule.rs b/src/models/django_celery_beat_clockedschedule.rs new file mode 100644 index 0000000..4e4b68d --- /dev/null +++ b/src/models/django_celery_beat_clockedschedule.rs @@ -0,0 +1,31 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_celery_beat_clockedschedule")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub clocked_time: DateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatPeriodictask.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} diff --git a/src/models/django_celery_beat_crontabschedule.rs b/src/models/django_celery_beat_crontabschedule.rs new file mode 100644 index 0000000..163c73b --- /dev/null +++ b/src/models/django_celery_beat_crontabschedule.rs @@ -0,0 +1,36 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_celery_beat_crontabschedule")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub minute: String, + pub hour: String, + pub day_of_week: String, + pub day_of_month: String, + pub month_of_year: String, + pub timezone: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatPeriodictask.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} diff --git a/src/models/django_celery_beat_intervalschedule.rs b/src/models/django_celery_beat_intervalschedule.rs new file mode 100644 index 0000000..1eeeaa4 --- /dev/null +++ b/src/models/django_celery_beat_intervalschedule.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_celery_beat_intervalschedule")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub every: i64, + pub period: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatPeriodictask.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} diff --git a/src/models/django_celery_beat_periodictask.rs b/src/models/django_celery_beat_periodictask.rs new file mode 100644 index 0000000..0fa6b6b --- /dev/null +++ b/src/models/django_celery_beat_periodictask.rs @@ -0,0 +1,111 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_celery_beat_periodictask")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique)] + pub name: String, + pub task: String, + #[sea_orm(column_type = "Text")] + pub args: String, + #[sea_orm(column_type = "Text")] + pub kwargs: String, + pub queue: Option, + pub exchange: Option, + pub routing_key: Option, + pub expires: Option, + pub enabled: bool, + pub last_run_at: Option, + pub total_run_count: i64, + pub date_changed: DateTimeWithTimeZone, + #[sea_orm(column_type = "Text")] + pub description: String, + pub crontab_id: Option, + pub interval_id: Option, + pub solar_id: Option, + pub one_off: bool, + pub start_time: Option, + pub priority: Option, + #[sea_orm(column_type = "Text")] + pub headers: String, + pub clocked_id: Option, + pub expire_seconds: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::django_celery_beat_clockedschedule::Entity", + from = "Column::ClockedId", + to = "super::django_celery_beat_clockedschedule::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + DjangoCeleryBeatClockedschedule, + #[sea_orm( + belongs_to = "super::django_celery_beat_crontabschedule::Entity", + from = "Column::CrontabId", + to = "super::django_celery_beat_crontabschedule::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + DjangoCeleryBeatCrontabschedule, + #[sea_orm( + belongs_to = "super::django_celery_beat_intervalschedule::Entity", + from = "Column::IntervalId", + to = "super::django_celery_beat_intervalschedule::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + DjangoCeleryBeatIntervalschedule, + #[sea_orm( + belongs_to = "super::django_celery_beat_solarschedule::Entity", + from = "Column::SolarId", + to = "super::django_celery_beat_solarschedule::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + DjangoCeleryBeatSolarschedule, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatClockedschedule.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatCrontabschedule.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatIntervalschedule.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatSolarschedule.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::django_celery_beat_clockedschedule::Entity")] + DjangoCeleryBeatClockedschedule, + #[sea_orm(entity = "super::django_celery_beat_crontabschedule::Entity")] + DjangoCeleryBeatCrontabschedule, + #[sea_orm(entity = "super::django_celery_beat_intervalschedule::Entity")] + DjangoCeleryBeatIntervalschedule, + #[sea_orm(entity = "super::django_celery_beat_solarschedule::Entity")] + DjangoCeleryBeatSolarschedule, +} diff --git a/src/models/django_celery_beat_periodictasks.rs b/src/models/django_celery_beat_periodictasks.rs new file mode 100644 index 0000000..bc88d01 --- /dev/null +++ b/src/models/django_celery_beat_periodictasks.rs @@ -0,0 +1,19 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_celery_beat_periodictasks")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub ident: i16, + pub last_update: DateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity {} diff --git a/src/models/django_celery_beat_solarschedule.rs b/src/models/django_celery_beat_solarschedule.rs new file mode 100644 index 0000000..a9dbee8 --- /dev/null +++ b/src/models/django_celery_beat_solarschedule.rs @@ -0,0 +1,42 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_celery_beat_solarschedule")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique_key = "django_celery_beat_solar_event_latitude_longitude_ba64999a_uniq")] + pub event: String, + #[sea_orm( + column_type = "Decimal(Some((9, 6)))", + unique_key = "django_celery_beat_solar_event_latitude_longitude_ba64999a_uniq" + )] + pub latitude: Decimal, + #[sea_orm( + column_type = "Decimal(Some((9, 6)))", + unique_key = "django_celery_beat_solar_event_latitude_longitude_ba64999a_uniq" + )] + pub longitude: Decimal, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoCeleryBeatPeriodictask.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::django_celery_beat_periodictask::Entity")] + DjangoCeleryBeatPeriodictask, +} diff --git a/src/models/django_content_type.rs b/src/models/django_content_type.rs new file mode 100644 index 0000000..4d92fb0 --- /dev/null +++ b/src/models/django_content_type.rs @@ -0,0 +1,44 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_content_type")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique_key = "django_content_type_app_label_model_76bd3d3b_uniq")] + pub app_label: String, + #[sea_orm(unique_key = "django_content_type_app_label_model_76bd3d3b_uniq")] + pub model: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::auth_permission::Entity")] + AuthPermission, + #[sea_orm(has_many = "super::django_admin_log::Entity")] + DjangoAdminLog, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthPermission.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoAdminLog.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::auth_permission::Entity")] + AuthPermission, + #[sea_orm(entity = "super::django_admin_log::Entity")] + DjangoAdminLog, +} diff --git a/src/models/django_migrations.rs b/src/models/django_migrations.rs new file mode 100644 index 0000000..c48c6fc --- /dev/null +++ b/src/models/django_migrations.rs @@ -0,0 +1,21 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_migrations")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub app: String, + pub name: String, + pub applied: DateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity {} diff --git a/src/models/django_session.rs b/src/models/django_session.rs new file mode 100644 index 0000000..3fcb4fa --- /dev/null +++ b/src/models/django_session.rs @@ -0,0 +1,21 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "django_session")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub session_key: String, + #[sea_orm(column_type = "Text")] + pub session_data: String, + pub expire_date: DateTimeWithTimeZone, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity {} diff --git a/src/models/futures_futuresbotparameter.rs b/src/models/futures_futuresbotparameter.rs new file mode 100644 index 0000000..faaa53d --- /dev/null +++ b/src/models/futures_futuresbotparameter.rs @@ -0,0 +1,64 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "futures_futuresbotparameter")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub exchange: String, + pub symbol: String, + pub grid_size: i16, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub upper_price: Option, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub lower_price: Option, + #[sea_orm(column_type = "Decimal(Some((20, 2)))")] + pub investment_amount: Decimal, + pub leverage: i16, + pub strategy_type: String, + pub end_date: Option, + pub date_created: DateTimeWithTimeZone, + pub date_updated: DateTimeWithTimeZone, + pub user_id: i64, + pub is_running: bool, + pub task_id: Option, + #[sea_orm(column_type = "JsonBinary", nullable)] + pub meta: Option, + #[sea_orm(column_type = "Decimal(Some((3, 2)))")] + pub rebalance_threshold: Decimal, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub loss_threshold: Option, + #[sea_orm(column_type = "Decimal(Some((20, 2)))")] + pub total_pnl: Decimal, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub acceptable_loss_per_grid: Option, + pub enable_grid_stop_loss: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::users_user::Entity", + from = "Column::UserId", + to = "super::users_user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + UsersUser, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUser.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::users_user::Entity")] + UsersUser, +} diff --git a/src/models/home_supportedpair.rs b/src/models/home_supportedpair.rs new file mode 100644 index 0000000..2371a12 --- /dev/null +++ b/src/models/home_supportedpair.rs @@ -0,0 +1,26 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "home_supportedpair")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique)] + pub symbol: String, + pub yfinance_symbol: String, + pub name: String, + pub prediction_horizon: i64, + #[sea_orm(column_type = "Decimal(Some((3, 2)))")] + pub confidence_threshold: Decimal, + pub is_active: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity {} diff --git a/src/models/invoices_invoice.rs b/src/models/invoices_invoice.rs new file mode 100644 index 0000000..d8b2076 --- /dev/null +++ b/src/models/invoices_invoice.rs @@ -0,0 +1,50 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "invoices_invoice")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique)] + pub invoice_id: String, + #[sea_orm(unique)] + pub invoice_slug: String, + pub issue_date: DateTimeWithTimeZone, + #[sea_orm(column_type = "Decimal(Some((10, 2)))")] + pub amount: Decimal, + pub due_date: DateTimeWithTimeZone, + pub status: String, + pub paid_date: Option, + pub user_subscription_id: i64, + #[sea_orm(unique)] + pub tx_hash: Option, + pub payment_link: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::subscriptions_usersubscription::Entity", + from = "Column::UserSubscriptionId", + to = "super::subscriptions_usersubscription::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + SubscriptionsUsersubscription, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SubscriptionsUsersubscription.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::subscriptions_usersubscription::Entity")] + SubscriptionsUsersubscription, +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 11e94f8..1b44942 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -1,30 +1,59 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -mod utils; +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 pub mod prelude; -pub mod address; -pub mod customer; -pub mod customer_address; -pub mod product; -pub mod product_category; -pub mod product_description; -pub mod product_model; -pub mod product_model_product_description; -pub mod sales_order_detail; -pub mod sales_order_header; +pub mod auth_group; +pub mod auth_group_permissions; +pub mod auth_permission; +pub mod django_admin_log; +pub mod django_celery_beat_clockedschedule; +pub mod django_celery_beat_crontabschedule; +pub mod django_celery_beat_intervalschedule; +pub mod django_celery_beat_periodictask; +pub mod django_celery_beat_periodictasks; +pub mod django_celery_beat_solarschedule; +pub mod django_content_type; +pub mod django_migrations; +pub mod django_session; +pub mod futures_futuresbotparameter; +pub mod home_supportedpair; +pub mod invoices_invoice; +pub mod spot_spotbotparameter; +pub mod subscriptions_subscription; +pub mod subscriptions_usersubscription; +pub mod token_blacklist_blacklistedtoken; +pub mod token_blacklist_outstandingtoken; pub mod user; +pub mod users_user; +pub mod users_user_groups; +pub mod users_user_user_permissions; +pub mod waitlist; seaography::register_entity_modules!([ - address, - customer, - customer_address, - product, - product_category, - product_description, - product_model, - product_model_product_description, - sales_order_detail, - sales_order_header, + auth_group, + auth_group_permissions, + auth_permission, + django_admin_log, + django_celery_beat_clockedschedule, + django_celery_beat_crontabschedule, + django_celery_beat_intervalschedule, + django_celery_beat_periodictask, + django_celery_beat_periodictasks, + django_celery_beat_solarschedule, + django_content_type, + django_migrations, + django_session, + futures_futuresbotparameter, + home_supportedpair, + invoices_invoice, + spot_spotbotparameter, + subscriptions_subscription, + subscriptions_usersubscription, + token_blacklist_blacklistedtoken, + token_blacklist_outstandingtoken, + user, + users_user, + users_user_groups, + users_user_user_permissions, + waitlist, ]); diff --git a/src/models/mod.rs.bak b/src/models/mod.rs.bak new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/models/mod.rs.bak @@ -0,0 +1 @@ +pub mod user; diff --git a/src/models/prelude.rs b/src/models/prelude.rs index c0543bb..2b3fda4 100644 --- a/src/models/prelude.rs +++ b/src/models/prelude.rs @@ -1,13 +1,27 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 -pub use super::address::Entity as Address; -pub use super::customer::Entity as Customer; -pub use super::customer_address::Entity as CustomerAddress; -pub use super::product::Entity as Product; -pub use super::product_category::Entity as ProductCategory; -pub use super::product_description::Entity as ProductDescription; -pub use super::product_model::Entity as ProductModel; -pub use super::product_model_product_description::Entity as ProductModelProductDescription; -pub use super::sales_order_detail::Entity as SalesOrderDetail; -pub use super::sales_order_header::Entity as SalesOrderHeader; -pub use super::user::Entity as User; +pub use super::auth_group::Entity as AuthGroup; +pub use super::auth_group_permissions::Entity as AuthGroupPermissions; +pub use super::auth_permission::Entity as AuthPermission; +pub use super::django_admin_log::Entity as DjangoAdminLog; +pub use super::django_celery_beat_clockedschedule::Entity as DjangoCeleryBeatClockedschedule; +pub use super::django_celery_beat_crontabschedule::Entity as DjangoCeleryBeatCrontabschedule; +pub use super::django_celery_beat_intervalschedule::Entity as DjangoCeleryBeatIntervalschedule; +pub use super::django_celery_beat_periodictask::Entity as DjangoCeleryBeatPeriodictask; +pub use super::django_celery_beat_periodictasks::Entity as DjangoCeleryBeatPeriodictasks; +pub use super::django_celery_beat_solarschedule::Entity as DjangoCeleryBeatSolarschedule; +pub use super::django_content_type::Entity as DjangoContentType; +pub use super::django_migrations::Entity as DjangoMigrations; +pub use super::django_session::Entity as DjangoSession; +pub use super::futures_futuresbotparameter::Entity as FuturesFuturesbotparameter; +pub use super::home_supportedpair::Entity as HomeSupportedpair; +pub use super::invoices_invoice::Entity as InvoicesInvoice; +pub use super::spot_spotbotparameter::Entity as SpotSpotbotparameter; +pub use super::subscriptions_subscription::Entity as SubscriptionsSubscription; +pub use super::subscriptions_usersubscription::Entity as SubscriptionsUsersubscription; +pub use super::token_blacklist_blacklistedtoken::Entity as TokenBlacklistBlacklistedtoken; +pub use super::token_blacklist_outstandingtoken::Entity as TokenBlacklistOutstandingtoken; +pub use super::users_user::Entity as UsersUser; +pub use super::users_user_groups::Entity as UsersUserGroups; +pub use super::users_user_user_permissions::Entity as UsersUserUserPermissions; +pub use super::waitlist::Entity as Waitlist; diff --git a/src/models/product.rs b/src/models/product.rs deleted file mode 100644 index 5809d5e..0000000 --- a/src/models/product.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize)] -#[sea_orm(table_name = "product")] -pub struct Model { - #[sea_orm(primary_key)] - pub product_id: i32, - #[sea_orm(unique)] - pub name: String, - #[sea_orm(unique)] - pub product_number: String, - pub color: Option, - pub standard_cost: f64, - pub list_price: f64, - pub size: Option, - pub weight: Option, - pub product_category_id: Option, - pub product_model_id: Option, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub sell_start_date: DateTime, - #[serde(deserialize_with = "super::utils::opt_date_time_from_str")] - pub sell_end_date: Option, - #[serde(deserialize_with = "super::utils::opt_date_time_from_str")] - pub discontinued_date: Option, - #[serde(deserialize_with = "super::utils::opt_bytes_from_str")] - pub thumb_nail_photo: Option, - pub thumbnail_photo_file_name: Option, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::product_category::Entity", - from = "Column::ProductCategoryId", - to = "super::product_category::Column::ProductCategoryId", - on_update = "NoAction", - on_delete = "NoAction" - )] - ProductCategory, - #[sea_orm( - belongs_to = "super::product_model::Entity", - from = "Column::ProductModelId", - to = "super::product_model::Column::ProductModelId", - on_update = "NoAction", - on_delete = "NoAction" - )] - ProductModel, - #[sea_orm(has_many = "super::sales_order_detail::Entity")] - SalesOrderDetail, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ProductCategory.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ProductModel.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::SalesOrderDetail.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - super::sales_order_detail::Relation::SalesOrderHeader.def() - } - fn via() -> Option { - Some(super::sales_order_detail::Relation::Product.def().rev()) - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::product_category::Entity")] - ProductCategory, - #[sea_orm(entity = "super::product_model::Entity")] - ProductModel, - #[sea_orm(entity = "super::sales_order_detail::Entity")] - SalesOrderDetail, - #[sea_orm(entity = "super::sales_order_header::Entity")] - SalesOrderHeader, -} diff --git a/src/models/product_category.rs b/src/models/product_category.rs deleted file mode 100644 index 591a467..0000000 --- a/src/models/product_category.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize)] -#[sea_orm(table_name = "product_category")] -pub struct Model { - #[sea_orm(primary_key)] - pub product_category_id: i32, - pub parent_product_category_id: Option, - #[sea_orm(unique)] - pub name: String, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::product::Entity")] - Product, - #[sea_orm( - belongs_to = "Entity", - from = "Column::ParentProductCategoryId", - to = "Column::ProductCategoryId", - on_update = "NoAction", - on_delete = "NoAction" - )] - SelfRef, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Product.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::product::Entity")] - Product, - #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def()")] - SelfRef, - #[sea_orm(entity = "Entity", def = "Relation::SelfRef.def().rev()")] - SelfRefReverse, -} diff --git a/src/models/product_description.rs b/src/models/product_description.rs deleted file mode 100644 index d54d07d..0000000 --- a/src/models/product_description.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize)] -#[sea_orm(table_name = "product_description")] -pub struct Model { - #[sea_orm(primary_key)] - pub product_description_id: i32, - pub description: String, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::product_model_product_description::Entity")] - ProductModelProductDescription, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ProductModelProductDescription.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::product_model_product_description::Entity")] - ProductModelProductDescription, -} diff --git a/src/models/product_model.rs b/src/models/product_model.rs deleted file mode 100644 index 1d121ad..0000000 --- a/src/models/product_model.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize)] -#[sea_orm(table_name = "product_model")] -pub struct Model { - #[sea_orm(primary_key)] - pub product_model_id: i32, - #[sea_orm(unique)] - pub name: String, - pub catalog_description: Option, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::product::Entity")] - Product, - #[sea_orm(has_many = "super::product_model_product_description::Entity")] - ProductModelProductDescription, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Product.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ProductModelProductDescription.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - super::product_model_product_description::Relation::ProductDescription.def() - } - fn via() -> Option { - Some( - super::product_model_product_description::Relation::ProductModel - .def() - .rev(), - ) - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::product::Entity")] - Product, - #[sea_orm(entity = "super::product_model_product_description::Entity")] - ProductModelProductDescription, - #[sea_orm(entity = "super::product_description::Entity")] - ProductDescription, -} diff --git a/src/models/product_model_product_description.rs b/src/models/product_model_product_description.rs deleted file mode 100644 index ab5217e..0000000 --- a/src/models/product_model_product_description.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Deserialize)] -#[sea_orm(table_name = "product_model_product_description")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub product_model_id: i32, - #[sea_orm(primary_key, auto_increment = false)] - pub product_description_id: i32, - #[sea_orm(primary_key, auto_increment = false)] - pub culture: String, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::product_description::Entity", - from = "Column::ProductDescriptionId", - to = "super::product_description::Column::ProductDescriptionId", - on_update = "NoAction", - on_delete = "NoAction" - )] - ProductDescription, - #[sea_orm( - belongs_to = "super::product_model::Entity", - from = "Column::ProductModelId", - to = "super::product_model::Column::ProductModelId", - on_update = "NoAction", - on_delete = "NoAction" - )] - ProductModel, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ProductDescription.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ProductModel.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::product_description::Entity")] - ProductDescription, - #[sea_orm(entity = "super::product_model::Entity")] - ProductModel, -} diff --git a/src/models/sales_order_detail.rs b/src/models/sales_order_detail.rs deleted file mode 100644 index 94aa9bc..0000000 --- a/src/models/sales_order_detail.rs +++ /dev/null @@ -1,64 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize)] -#[sea_orm(table_name = "sales_order_detail")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub sales_order_id: i32, - #[sea_orm(primary_key, auto_increment = false)] - pub sales_order_detail_id: i32, - pub order_qty: i16, - pub product_id: i32, - pub unit_price: f64, - pub unit_price_discount: f64, - // pub line_total: Decimal, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::product::Entity", - from = "Column::ProductId", - to = "super::product::Column::ProductId", - on_update = "NoAction", - on_delete = "NoAction" - )] - Product, - #[sea_orm( - belongs_to = "super::sales_order_header::Entity", - from = "Column::SalesOrderId", - to = "super::sales_order_header::Column::SalesOrderId", - on_update = "NoAction", - on_delete = "Cascade" - )] - SalesOrderHeader, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Product.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::SalesOrderHeader.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::product::Entity")] - Product, - #[sea_orm(entity = "super::sales_order_header::Entity")] - SalesOrderHeader, -} diff --git a/src/models/sales_order_header.rs b/src/models/sales_order_header.rs deleted file mode 100644 index 0196e08..0000000 --- a/src/models/sales_order_header.rs +++ /dev/null @@ -1,110 +0,0 @@ -//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 - -use sea_orm::entity::prelude::*; -use serde::Deserialize; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize)] -#[sea_orm(table_name = "sales_order_header")] -pub struct Model { - #[sea_orm(primary_key)] - pub sales_order_id: i32, - pub revision_number: i32, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub order_date: DateTime, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub due_date: DateTime, - #[serde(deserialize_with = "super::utils::opt_date_time_from_str")] - pub ship_date: Option, - pub status: i32, - #[serde(deserialize_with = "super::utils::bool_from_int")] - pub online_order_flag: bool, - // #[sea_orm(unique)] - // pub sales_order_number: String, - pub purchase_order_number: Option, - pub account_number: Option, - pub customer_id: i32, - pub ship_to_address_id: Option, - pub bill_to_address_id: Option, - pub ship_method: String, - pub credit_card_approval_code: Option, - pub sub_total: f64, - pub tax_amt: f64, - pub freight: f64, - // pub total_due: f64, - pub comment: Option, - #[sea_orm(unique)] - pub rowguid: Uuid, - #[serde(deserialize_with = "super::utils::date_time_from_str")] - pub created_date: DateTime, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::address::Entity", - from = "Column::BillToAddressId", - to = "super::address::Column::AddressId", - on_update = "NoAction", - on_delete = "NoAction" - )] - Address2, - #[sea_orm( - belongs_to = "super::address::Entity", - from = "Column::ShipToAddressId", - to = "super::address::Column::AddressId", - on_update = "NoAction", - on_delete = "NoAction" - )] - Address1, - #[sea_orm( - belongs_to = "super::customer::Entity", - from = "Column::CustomerId", - to = "super::customer::Column::CustomerId", - on_update = "NoAction", - on_delete = "NoAction" - )] - Customer, - #[sea_orm(has_many = "super::sales_order_detail::Entity")] - SalesOrderDetail, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Customer.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::SalesOrderDetail.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - super::sales_order_detail::Relation::Product.def() - } - fn via() -> Option { - Some( - super::sales_order_detail::Relation::SalesOrderHeader - .def() - .rev(), - ) - } -} - -impl ActiveModelBehavior for ActiveModel {} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] -pub enum RelatedEntity { - #[sea_orm(entity = "super::address::Entity", def = "Relation::Address2.def()")] - Address2, - #[sea_orm(entity = "super::address::Entity", def = "Relation::Address1.def()")] - Address1, - #[sea_orm(entity = "super::customer::Entity")] - Customer, - #[sea_orm(entity = "super::sales_order_detail::Entity")] - SalesOrderDetail, - #[sea_orm(entity = "super::product::Entity")] - Product, -} diff --git a/src/models/spot_spotbotparameter.rs b/src/models/spot_spotbotparameter.rs new file mode 100644 index 0000000..39fc161 --- /dev/null +++ b/src/models/spot_spotbotparameter.rs @@ -0,0 +1,63 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "spot_spotbotparameter")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub exchange: String, + pub symbol: String, + pub grid_size: i16, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub upper_price: Option, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub lower_price: Option, + #[sea_orm(column_type = "Decimal(Some((20, 2)))")] + pub investment_amount: Decimal, + pub strategy_type: String, + pub end_date: Option, + pub is_running: bool, + pub date_created: DateTimeWithTimeZone, + pub date_updated: DateTimeWithTimeZone, + pub task_id: Option, + pub user_id: i64, + #[sea_orm(column_type = "JsonBinary", nullable)] + pub meta: Option, + #[sea_orm(column_type = "Decimal(Some((3, 2)))")] + pub rebalance_threshold: Decimal, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub loss_threshold: Option, + #[sea_orm(column_type = "Decimal(Some((20, 2)))")] + pub total_pnl: Decimal, + #[sea_orm(column_type = "Decimal(Some((20, 2)))", nullable)] + pub acceptable_loss_per_grid: Option, + pub enable_grid_stop_loss: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::users_user::Entity", + from = "Column::UserId", + to = "super::users_user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + UsersUser, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUser.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::users_user::Entity")] + UsersUser, +} diff --git a/src/models/subscriptions_subscription.rs b/src/models/subscriptions_subscription.rs new file mode 100644 index 0000000..29ef6e5 --- /dev/null +++ b/src/models/subscriptions_subscription.rs @@ -0,0 +1,41 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "subscriptions_subscription")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub name: String, + #[sea_orm(unique)] + pub slug: String, + #[sea_orm(column_type = "Text")] + pub description: String, + #[sea_orm(column_type = "Decimal(Some((10, 2)))")] + pub price: Decimal, + pub created_at: DateTimeWithTimeZone, + pub updated_at: DateTimeWithTimeZone, + #[sea_orm(column_type = "JsonBinary")] + pub permissions: Json, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::subscriptions_usersubscription::Entity")] + SubscriptionsUsersubscription, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SubscriptionsUsersubscription.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::subscriptions_usersubscription::Entity")] + SubscriptionsUsersubscription, +} diff --git a/src/models/subscriptions_usersubscription.rs b/src/models/subscriptions_usersubscription.rs new file mode 100644 index 0000000..ed91892 --- /dev/null +++ b/src/models/subscriptions_usersubscription.rs @@ -0,0 +1,73 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "subscriptions_usersubscription")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub status: String, + pub date_subscribed: DateTimeWithTimeZone, + pub date_updated: DateTimeWithTimeZone, + pub last_billing_date: DateTimeWithTimeZone, + pub next_billing_date: Option, + pub has_trial_access: bool, + pub trial_end_date: Option, + pub date_cancelled: Option, + pub subscription_id: i64, + pub user_id: i64, + pub is_excluded: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::invoices_invoice::Entity")] + InvoicesInvoice, + #[sea_orm( + belongs_to = "super::subscriptions_subscription::Entity", + from = "Column::SubscriptionId", + to = "super::subscriptions_subscription::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + SubscriptionsSubscription, + #[sea_orm( + belongs_to = "super::users_user::Entity", + from = "Column::UserId", + to = "super::users_user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + UsersUser, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::InvoicesInvoice.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SubscriptionsSubscription.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUser.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::invoices_invoice::Entity")] + InvoicesInvoice, + #[sea_orm(entity = "super::subscriptions_subscription::Entity")] + SubscriptionsSubscription, + #[sea_orm(entity = "super::users_user::Entity")] + UsersUser, +} diff --git a/src/models/token_blacklist_blacklistedtoken.rs b/src/models/token_blacklist_blacklistedtoken.rs new file mode 100644 index 0000000..53f8689 --- /dev/null +++ b/src/models/token_blacklist_blacklistedtoken.rs @@ -0,0 +1,39 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "token_blacklist_blacklistedtoken")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub blacklisted_at: DateTimeWithTimeZone, + #[sea_orm(unique)] + pub token_id: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::token_blacklist_outstandingtoken::Entity", + from = "Column::TokenId", + to = "super::token_blacklist_outstandingtoken::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + TokenBlacklistOutstandingtoken, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::TokenBlacklistOutstandingtoken.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::token_blacklist_outstandingtoken::Entity")] + TokenBlacklistOutstandingtoken, +} diff --git a/src/models/token_blacklist_outstandingtoken.rs b/src/models/token_blacklist_outstandingtoken.rs new file mode 100644 index 0000000..e1a9422 --- /dev/null +++ b/src/models/token_blacklist_outstandingtoken.rs @@ -0,0 +1,53 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "token_blacklist_outstandingtoken")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(column_type = "Text")] + pub token: String, + pub created_at: Option, + pub expires_at: DateTimeWithTimeZone, + pub user_id: Option, + #[sea_orm(unique)] + pub jti: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_one = "super::token_blacklist_blacklistedtoken::Entity")] + TokenBlacklistBlacklistedtoken, + #[sea_orm( + belongs_to = "super::users_user::Entity", + from = "Column::UserId", + to = "super::users_user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + UsersUser, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::TokenBlacklistBlacklistedtoken.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUser.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::token_blacklist_blacklistedtoken::Entity")] + TokenBlacklistBlacklistedtoken, + #[sea_orm(entity = "super::users_user::Entity")] + UsersUser, +} diff --git a/src/models/user.rs.bak b/src/models/user.rs.bak new file mode 100644 index 0000000..4ba0826 --- /dev/null +++ b/src/models/user.rs.bak @@ -0,0 +1,29 @@ +use sea_orm::entity::prelude::*; +use serde::Deserialize; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Deserialize)] +#[sea_orm(table_name = "user")] +pub struct Model { + pub created_at: DateTime, + pub updated_at: DateTime, + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique)] + pub pid: Uuid, + #[sea_orm(unique)] + pub email: String, + #[seaography(ignore)] + pub password: String, + #[sea_orm(unique)] + #[seaography(ignore)] + pub api_key: String, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity {} diff --git a/src/models/users_user.rs b/src/models/users_user.rs new file mode 100644 index 0000000..01fab07 --- /dev/null +++ b/src/models/users_user.rs @@ -0,0 +1,105 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "users_user")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub password: String, + pub last_login: Option, + pub is_superuser: bool, + #[sea_orm(unique)] + pub username: String, + pub first_name: String, + pub last_name: String, + pub is_staff: bool, + pub is_active: bool, + pub date_joined: DateTimeWithTimeZone, + #[sea_orm(unique)] + pub email: String, + pub is_email_verified: bool, + pub otp_code: Option, + pub otp_expiry: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::django_admin_log::Entity")] + DjangoAdminLog, + #[sea_orm(has_many = "super::futures_futuresbotparameter::Entity")] + FuturesFuturesbotparameter, + #[sea_orm(has_many = "super::spot_spotbotparameter::Entity")] + SpotSpotbotparameter, + #[sea_orm(has_many = "super::subscriptions_usersubscription::Entity")] + SubscriptionsUsersubscription, + #[sea_orm(has_many = "super::token_blacklist_outstandingtoken::Entity")] + TokenBlacklistOutstandingtoken, + #[sea_orm(has_many = "super::users_user_groups::Entity")] + UsersUserGroups, + #[sea_orm(has_many = "super::users_user_user_permissions::Entity")] + UsersUserUserPermissions, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::DjangoAdminLog.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::FuturesFuturesbotparameter.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SpotSpotbotparameter.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SubscriptionsUsersubscription.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::TokenBlacklistOutstandingtoken.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUserGroups.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUserUserPermissions.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::django_admin_log::Entity")] + DjangoAdminLog, + #[sea_orm(entity = "super::futures_futuresbotparameter::Entity")] + FuturesFuturesbotparameter, + #[sea_orm(entity = "super::spot_spotbotparameter::Entity")] + SpotSpotbotparameter, + #[sea_orm(entity = "super::subscriptions_usersubscription::Entity")] + SubscriptionsUsersubscription, + #[sea_orm(entity = "super::token_blacklist_outstandingtoken::Entity")] + TokenBlacklistOutstandingtoken, + #[sea_orm(entity = "super::users_user_groups::Entity")] + UsersUserGroups, + #[sea_orm(entity = "super::users_user_user_permissions::Entity")] + UsersUserUserPermissions, +} diff --git a/src/models/users_user_groups.rs b/src/models/users_user_groups.rs new file mode 100644 index 0000000..2ceeb34 --- /dev/null +++ b/src/models/users_user_groups.rs @@ -0,0 +1,56 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "users_user_groups")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique_key = "users_user_groups_user_id_group_id_b88eab82_uniq")] + pub user_id: i64, + #[sea_orm(unique_key = "users_user_groups_user_id_group_id_b88eab82_uniq")] + pub group_id: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::auth_group::Entity", + from = "Column::GroupId", + to = "super::auth_group::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + AuthGroup, + #[sea_orm( + belongs_to = "super::users_user::Entity", + from = "Column::UserId", + to = "super::users_user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + UsersUser, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthGroup.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUser.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::auth_group::Entity")] + AuthGroup, + #[sea_orm(entity = "super::users_user::Entity")] + UsersUser, +} diff --git a/src/models/users_user_user_permissions.rs b/src/models/users_user_user_permissions.rs new file mode 100644 index 0000000..da897f3 --- /dev/null +++ b/src/models/users_user_user_permissions.rs @@ -0,0 +1,56 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "users_user_user_permissions")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + #[sea_orm(unique_key = "users_user_user_permissions_user_id_permission_id_43338c45_uniq")] + pub user_id: i64, + #[sea_orm(unique_key = "users_user_user_permissions_user_id_permission_id_43338c45_uniq")] + pub permission_id: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::auth_permission::Entity", + from = "Column::PermissionId", + to = "super::auth_permission::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + AuthPermission, + #[sea_orm( + belongs_to = "super::users_user::Entity", + from = "Column::UserId", + to = "super::users_user::Column::Id", + on_update = "NoAction", + on_delete = "NoAction" + )] + UsersUser, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthPermission.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::UsersUser.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity { + #[sea_orm(entity = "super::auth_permission::Entity")] + AuthPermission, + #[sea_orm(entity = "super::users_user::Entity")] + UsersUser, +} diff --git a/src/models/utils.rs b/src/models/utils.rs deleted file mode 100644 index 22fcabb..0000000 --- a/src/models/utils.rs +++ /dev/null @@ -1,80 +0,0 @@ -use base64::{engine::general_purpose::STANDARD, Engine as _}; -use seaography::itertools::Itertools; -use serde::Deserialize; - -pub fn bool_from_int<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - match u8::deserialize(deserializer)? { - 0 => Ok(false), - 1 => Ok(true), - other => Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Unsigned(other as u64), - &"zero or one", - )), - } -} - -pub fn date_time_from_str<'de, D>(deserializer: D) -> Result -where - D: serde::Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - let date_time = chrono::naive::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S%.3f") - .map_err(|_| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Str(&s), - &"Chrono Naive Date Time", - ) - })?; - Ok(date_time) -} - -pub fn opt_date_time_from_str<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - match Option::::deserialize(deserializer)? { - Some(s) => { - let date_time = - chrono::naive::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S%.3f").map_err( - |_| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Str(&s), - &"Chrono Naive Date Time", - ) - }, - )?; - Ok(Some(date_time)) - } - None => Ok(None), - } -} - -pub fn opt_bytes_from_str<'de, D>(deserializer: D) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - match Option::::deserialize(deserializer)? { - Some(s) => { - let mut bytes = Vec::new(); - for byte in &s.chars().chunks(2) { - let byte: String = byte.collect(); - let b = u8::from_str_radix(&byte, 16).map_err(|_| { - serde::de::Error::invalid_value( - serde::de::Unexpected::Str(&byte), - &"Base 16 Bytes", - ) - })?; - bytes.push(b); - } - let base64_encoded = STANDARD.encode(bytes); - let src = format!("data:image;base64,{base64_encoded}"); - Ok(Some(src)) - } - None => Ok(None), - } -} diff --git a/src/models/waitlist.rs b/src/models/waitlist.rs new file mode 100644 index 0000000..85cba73 --- /dev/null +++ b/src/models/waitlist.rs @@ -0,0 +1,29 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "waitlist")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i64, + pub first_name: String, + pub last_name: String, + #[sea_orm(unique)] + pub email: String, + pub phone: Option, + #[sea_orm(column_type = "Text")] + pub trading_experience: String, + #[sea_orm(column_type = "JsonBinary")] + pub traded_markets: Json, + #[sea_orm(column_type = "JsonBinary")] + pub trading_challenges: Json, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelatedEntity)] +pub enum RelatedEntity {} diff --git a/src/tasks/mod.rs.bak b/src/tasks/mod.rs.bak new file mode 100644 index 0000000..01fbdda --- /dev/null +++ b/src/tasks/mod.rs.bak @@ -0,0 +1 @@ +pub mod seed; diff --git a/src/tasks/seed.rs b/src/tasks/seed.rs index f0cc34f..4e9e665 100644 --- a/src/tasks/seed.rs +++ b/src/tasks/seed.rs @@ -46,93 +46,8 @@ impl Task for SeedData { db::migrate::(db).await?; } - // Start: Scaffold // - // Some boilerplate - const CRLF: csv::Terminator = csv::Terminator::CRLF; - const DOLLAR_SIGN: csv::Terminator = csv::Terminator::Any(b'$'); - macro_rules! seed_table_win_1252 { - ($entity: ident, $csv: expr) => { - seed_table::($csv, WINDOWS_1252, b'\t', CRLF, db) - .await?; - }; - } - macro_rules! seed_table_utf_16 { - ($entity: ident, $csv: expr) => { - seed_table::($csv, UTF_16LE, b'\t', CRLF, db).await?; - }; - } - macro_rules! seed_table_utf_16_tilde { - ($entity: ident, $csv: expr) => { - seed_table::($csv, UTF_16LE, b'~', DOLLAR_SIGN, db) - .await?; - }; - } - - // Seed each table in sequence - seed_table_win_1252!(customer, "Customer.tsv"); - seed_table_win_1252!(address, "Address.tsv"); - seed_table_win_1252!(customer_address, "CustomerAddress.tsv"); - seed_table_win_1252!(sales_order_header, "SalesOrderHeader.tsv"); - seed_table_win_1252!(product_category, "ProductCategory.tsv"); - seed_table_utf_16_tilde!(product_model, "ProductModel.tsv"); - seed_table_win_1252!(product, "Product.tsv"); - seed_table_win_1252!(sales_order_detail, "SalesOrderDetail.tsv"); - seed_table_utf_16!(product_description, "ProductDescription.tsv"); - seed_table_win_1252!( - product_model_product_description, - "ProductModelProductDescription.tsv" - ); - - println!("All Tables Completed Seeding!"); - // End: Scaffold // Ok(()) } } -// Start: Scaffold // -async fn seed_table( - csv: &str, - encoding: &'static encoding_rs::Encoding, - delimiter: u8, - terminator: csv::Terminator, - db: &DbConn, -) -> Result<()> -where - T: DeserializeOwned + ModelTrait + IntoActiveModel, - A: ActiveModelTrait + ActiveModelBehavior, - <::Entity as EntityTrait>::Model: IntoActiveModel, -{ - println!("Reading: {csv:?}"); - // Read data from CSV - let file = std::fs::OpenOptions::new().read(true).open(format!( - "{}/migration/AdventureWorks-2012-LT-Script/{}", - env!("CARGO_MANIFEST_DIR"), - csv - ))?; - let transcoded = encoding_rs_io::DecodeReaderBytesBuilder::new() - .encoding(Some(encoding)) - .build(file); - let mut rdr = csv::ReaderBuilder::new() - .delimiter(delimiter) - .terminator(terminator) - .has_headers(false) - .trim(csv::Trim::All) - .from_reader(transcoded); - // Collect into SeaORM's active model - let mut active_models: Vec = Vec::new(); - for result in rdr.deserialize() { - let model: T = result.unwrap(); - active_models.push(model.into_active_model()); - } - // Insert active model in chunks - for models in active_models.chunks(100) { - let models = models.iter().cloned(); - ::insert_many(models) - .exec(db) - .await?; - } - println!("Seeding Completed: {csv:?}"); - Ok(()) -} -// End: Scaffold // diff --git a/src/tasks/seed.rs.bak b/src/tasks/seed.rs.bak new file mode 100644 index 0000000..f0cc34f --- /dev/null +++ b/src/tasks/seed.rs.bak @@ -0,0 +1,138 @@ +//! 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 encoding_rs::{UTF_16LE, WINDOWS_1252}; +use loco_rs::{db, prelude::*}; +use migration::Migrator; +use sea_orm::DbConn; +use serde::de::DeserializeOwned; + +use crate::models; + +#[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: &task::Vars) -> Result<()> { + let refresh = vars + .cli_arg("refresh") + .is_ok_and(|refresh| refresh == "true"); + let db = &app_context.db; + + // Run migration before seeding database + if refresh { + db::reset::(db).await?; + } else { + db::migrate::(db).await?; + } + + // Start: Scaffold // + // Some boilerplate + const CRLF: csv::Terminator = csv::Terminator::CRLF; + const DOLLAR_SIGN: csv::Terminator = csv::Terminator::Any(b'$'); + macro_rules! seed_table_win_1252 { + ($entity: ident, $csv: expr) => { + seed_table::($csv, WINDOWS_1252, b'\t', CRLF, db) + .await?; + }; + } + macro_rules! seed_table_utf_16 { + ($entity: ident, $csv: expr) => { + seed_table::($csv, UTF_16LE, b'\t', CRLF, db).await?; + }; + } + macro_rules! seed_table_utf_16_tilde { + ($entity: ident, $csv: expr) => { + seed_table::($csv, UTF_16LE, b'~', DOLLAR_SIGN, db) + .await?; + }; + } + + // Seed each table in sequence + seed_table_win_1252!(customer, "Customer.tsv"); + seed_table_win_1252!(address, "Address.tsv"); + seed_table_win_1252!(customer_address, "CustomerAddress.tsv"); + seed_table_win_1252!(sales_order_header, "SalesOrderHeader.tsv"); + seed_table_win_1252!(product_category, "ProductCategory.tsv"); + seed_table_utf_16_tilde!(product_model, "ProductModel.tsv"); + seed_table_win_1252!(product, "Product.tsv"); + seed_table_win_1252!(sales_order_detail, "SalesOrderDetail.tsv"); + seed_table_utf_16!(product_description, "ProductDescription.tsv"); + seed_table_win_1252!( + product_model_product_description, + "ProductModelProductDescription.tsv" + ); + + println!("All Tables Completed Seeding!"); + // End: Scaffold // + + Ok(()) + } +} + +// Start: Scaffold // +async fn seed_table( + csv: &str, + encoding: &'static encoding_rs::Encoding, + delimiter: u8, + terminator: csv::Terminator, + db: &DbConn, +) -> Result<()> +where + T: DeserializeOwned + ModelTrait + IntoActiveModel, + A: ActiveModelTrait + ActiveModelBehavior, + <::Entity as EntityTrait>::Model: IntoActiveModel, +{ + println!("Reading: {csv:?}"); + // Read data from CSV + let file = std::fs::OpenOptions::new().read(true).open(format!( + "{}/migration/AdventureWorks-2012-LT-Script/{}", + env!("CARGO_MANIFEST_DIR"), + csv + ))?; + let transcoded = encoding_rs_io::DecodeReaderBytesBuilder::new() + .encoding(Some(encoding)) + .build(file); + let mut rdr = csv::ReaderBuilder::new() + .delimiter(delimiter) + .terminator(terminator) + .has_headers(false) + .trim(csv::Trim::All) + .from_reader(transcoded); + // Collect into SeaORM's active model + let mut active_models: Vec = Vec::new(); + for result in rdr.deserialize() { + let model: T = result.unwrap(); + active_models.push(model.into_active_model()); + } + // Insert active model in chunks + for models in active_models.chunks(100) { + let models = models.iter().cloned(); + ::insert_many(models) + .exec(db) + .await?; + } + println!("Seeding Completed: {csv:?}"); + Ok(()) +} +// End: Scaffold //