mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
39
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9653a1d21 | ||
|
|
42c867cfea | ||
|
|
72a2d956b9 | ||
|
|
62a02c07df | ||
|
|
ec991185c6 | ||
|
|
fc3423d05e | ||
|
|
98cd19bd40 | ||
|
|
94d9de09cc | ||
|
|
7d788bff62 | ||
|
|
d0e2ad9269 | ||
|
|
6d9401c1f1 | ||
|
|
49e49efa3c | ||
|
|
d51c001835 | ||
|
|
f94fc5bf07 | ||
|
|
f397ad825c | ||
|
|
7eea797883 | ||
|
|
73856fca85 | ||
|
|
3d27c620e4 | ||
|
|
062b23d3a0 | ||
|
|
129fc3d593 | ||
|
|
ad40798a12 | ||
|
|
3db8e66204 | ||
|
|
963521d87b | ||
|
|
4323743fe7 | ||
|
|
752fad12b3 | ||
|
|
99b831c3b0 | ||
|
|
4a1d1acddd | ||
|
|
47d6ac6841 | ||
|
|
689bda8bf3 | ||
|
|
7e36149d13 | ||
|
|
2bbeb3de78 | ||
|
|
eeacbf6719 | ||
|
|
af763669cc | ||
|
|
7ff78c6d94 | ||
|
|
588478da73 | ||
|
|
5cb7b80b44 | ||
|
|
50b1a7ed06 | ||
|
|
44cadf84cc | ||
|
|
b7c8151d28 |
@@ -6,7 +6,11 @@
|
||||
- No minimum supported Rust version; use latest stable features freely
|
||||
- `#[macro_use] extern crate serde_derive;` is used in binary crates; library crates use `use serde_derive::{Serialize, Deserialize};`
|
||||
- Never use `println!` or `eprintln!` outside of tests; use `log` crate macros (`info!`, `warn!`, `error!`, `debug!`, `trace!`) instead
|
||||
- Use `anyhow::Result` / `AnyhowResult` for fallible functions in application code
|
||||
- Use `anyhow::Result` / `AnyhowResult` for fallible functions in application code; new database queries should use `Result<T, sqlx::Error>` instead
|
||||
- SQLx queries: prefer `sqlx::query!` and `sqlx::query_as!` compile-time checked macros over
|
||||
runtime `sqlx::query()` / `sqlx::query_as()`. The macros catch SQL typos, column mismatches,
|
||||
and type errors at build time. Use runtime queries only when assembling complicated
|
||||
dynamic/branching queries that cannot be expressed as a compile-time macro.
|
||||
- Enum Display/Debug: use `impl_enum_display_and_debug_using_to_str!` macro, not `derive_more::Display` (CLion doesn't understand it)
|
||||
- Fields that are optional use the `maybe_` prefix: `maybe_creator_user_token`, `maybe_prompt_token`
|
||||
- When implementing `fmt::Display` for error types, use the pattern: `write!(f, "{:?}", self)`
|
||||
@@ -56,8 +60,14 @@ then the story, then the fine print.
|
||||
`use artcraft_router::...::CommonResolution as CommonResolutionRouter;`
|
||||
This avoids long fully-qualified paths in function signatures and bodies.
|
||||
|
||||
## MySQL
|
||||
|
||||
- MySQL tables: primary key `id` columns must be `BIGINT(20) UNSIGNED`. Do not use signed IDs.
|
||||
- Always create tables with `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin`.
|
||||
|
||||
## TypeScript / Frontend
|
||||
|
||||
- TypeScript with React, Vite, Zustand, Three.js
|
||||
- Nx monorepo under `crates/frontend/`
|
||||
- Two spaces for indentation
|
||||
|
||||
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n uir.id,\n uir.impersonator_user_token as `impersonator_user_token: UserToken`,\n impersonator.username as impersonator_username,\n impersonator.display_name as impersonator_display_name,\n uir.impersonated_user_token as `impersonated_user_token: UserToken`,\n impersonated.username as impersonated_username,\n impersonated.display_name as impersonated_display_name,\n uir.is_redeemed as `is_redeemed: bool`,\n (uir.expires_at < NOW()) as `is_expired: bool`,\n uir.expires_at,\n uir.created_at,\n uir.updated_at\nFROM user_impersonation_requests uir\nJOIN users impersonator ON impersonator.token = uir.impersonator_user_token\nJOIN users impersonated ON impersonated.token = uir.impersonated_user_token\nWHERE uir.impersonated_user_token = ?\n AND uir.id < ?\nORDER BY uir.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "impersonator_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "impersonator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "impersonator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "impersonated_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "impersonated_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "impersonated_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_redeemed: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "is_expired: bool",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "expires_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "27cebaa36f0ec873f86cdf8fa994d499db1a5cc84d2c6fceb3587145eb2f9bec"
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n uir.id,\n uir.impersonator_user_token as `impersonator_user_token: UserToken`,\n impersonator.username as impersonator_username,\n impersonator.display_name as impersonator_display_name,\n uir.impersonated_user_token as `impersonated_user_token: UserToken`,\n impersonated.username as impersonated_username,\n impersonated.display_name as impersonated_display_name,\n uir.is_redeemed as `is_redeemed: bool`,\n (uir.expires_at < NOW()) as `is_expired: bool`,\n uir.expires_at,\n uir.created_at,\n uir.updated_at\nFROM user_impersonation_requests uir\nJOIN users impersonator ON impersonator.token = uir.impersonator_user_token\nJOIN users impersonated ON impersonated.token = uir.impersonated_user_token\nWHERE uir.impersonated_user_token = ?\nORDER BY uir.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "impersonator_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "impersonator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "impersonator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "impersonated_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "impersonated_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "impersonated_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_redeemed: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "is_expired: bool",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "expires_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "381e13ab37a2f4ce39bd6db9d90202c301091376108f645a9bb5264a9cd113d7"
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n sal.id,\n sal.token as `token: StaffAuditLogToken`,\n sal.audit_action,\n sal.maybe_entity_type,\n sal.maybe_entity_token,\n target_user.username as maybe_target_username,\n target_user.display_name as maybe_target_display_name,\n sal.staff_user_token as `staff_user_token: UserToken`,\n staff_user.username as staff_username,\n staff_user.display_name as staff_display_name,\n sal.staff_ip_address,\n sal.created_at\nFROM staff_audit_logs sal\nLEFT JOIN users target_user ON target_user.token = sal.maybe_entity_token\nLEFT JOIN users staff_user ON staff_user.token = sal.staff_user_token\nORDER BY sal.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "token: StaffAuditLogToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "audit_action",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_entity_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_entity_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_target_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_target_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "staff_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "staff_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "staff_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "staff_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "3846628765bd34ea2ce01e15a0db732121ce473c5c542d0e971ad29b2b421193"
|
||||
}
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE users\nSET\n is_banned = ?,\n maybe_mod_comments = ?,\n maybe_mod_user_token = ?,\n version = version + 1\n\nWHERE users.token = ?\nLIMIT 1\n ",
|
||||
"query": "\nUPDATE users\nSET\n is_banned = ?,\n maybe_mod_comments = ?,\n maybe_mod_user_token = ?,\n version = version + 1\n\nWHERE users.token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
@@ -8,5 +8,5 @@
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "eab69a8d679b2dcfb06a9ccc79851f6c693d0c372142ff0e7ded89c95e4bcf44"
|
||||
"hash": "3d746dcb479005f1f4ae5e157a5ace7626eda4849d44c6c2d7079fd346eb4edc"
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n uir.id,\n uir.impersonator_user_token as `impersonator_user_token: UserToken`,\n impersonator.username as impersonator_username,\n impersonator.display_name as impersonator_display_name,\n uir.impersonated_user_token as `impersonated_user_token: UserToken`,\n impersonated.username as impersonated_username,\n impersonated.display_name as impersonated_display_name,\n uir.is_redeemed as `is_redeemed: bool`,\n (uir.expires_at < NOW()) as `is_expired: bool`,\n uir.expires_at,\n uir.created_at,\n uir.updated_at\nFROM user_impersonation_requests uir\nJOIN users impersonator ON impersonator.token = uir.impersonator_user_token\nJOIN users impersonated ON impersonated.token = uir.impersonated_user_token\nWHERE uir.id < ?\nORDER BY uir.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "impersonator_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "impersonator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "impersonator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "impersonated_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "impersonated_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "impersonated_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_redeemed: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "is_expired: bool",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "expires_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5d45897a5d4c326c8d17f105cae81db7f5878c197b55e8ea61c225e739d11234"
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n sal.id,\n sal.token as `token: StaffAuditLogToken`,\n sal.audit_action,\n sal.maybe_entity_type,\n sal.maybe_entity_token,\n target_user.username as maybe_target_username,\n target_user.display_name as maybe_target_display_name,\n sal.staff_user_token as `staff_user_token: UserToken`,\n staff_user.username as staff_username,\n staff_user.display_name as staff_display_name,\n sal.staff_ip_address,\n sal.created_at\nFROM staff_audit_logs sal\nLEFT JOIN users target_user ON target_user.token = sal.maybe_entity_token\nLEFT JOIN users staff_user ON staff_user.token = sal.staff_user_token\nWHERE sal.id < ?\nORDER BY sal.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "token: StaffAuditLogToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "audit_action",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_entity_type",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_entity_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_target_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_target_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "staff_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "staff_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "staff_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "staff_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9132f832752ea40736bedf4729c76af7fe019cc69ed5cc280fce87e68c3f4846"
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token,\n impersonated_user_token as `impersonated_user_token: UserToken`,\n impersonator_user_token as `impersonator_user_token: UserToken`,\n user_impersonation_token,\n is_redeemed as `is_redeemed: bool`,\n expires_at\nFROM user_impersonation_requests\nWHERE user_impersonation_token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "impersonated_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "impersonator_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "user_impersonation_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "is_redeemed: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "expires_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9dc83df0b7f1cf2716352e947b6b7fcb6c455fb0c12d72e584612f35366831f4"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO user_sessions (\n token,\n user_token,\n maybe_impersonation_user_token,\n ip_address_creation,\n expires_at\n)\nVALUES (?, ?, ?, ?, ?)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ba5b0a542d4b95b781077b8895529d222dd682eb28c9d00031a820ec6f37e095"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO staff_audit_logs\nSET\n token = ?,\n audit_action = ?,\n maybe_entity_type = ?,\n maybe_entity_token = ?,\n staff_user_token = ?,\n staff_ip_address = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c39800423a5fe0aa324e117e624015f85d039c3ca6833cf6865471345ab0768e"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO user_impersonation_requests\nSET\n token = ?,\n impersonated_user_token = ?,\n impersonator_user_token = ?,\n user_impersonation_token = ?,\n ip_address_creation = ?,\n expires_at = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cc782c807dadcaaaad08f469935f61dc2e9f3217aa01532d6602b8e37cedf92d"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE user_impersonation_requests\nSET\n is_redeemed = TRUE,\n ip_address_redemption = ?\nWHERE user_impersonation_token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "cf4be26ce3a48c7108640faa6c9fc68a48136327a9d374c52c16848c006f0984"
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n uir.id,\n uir.impersonator_user_token as `impersonator_user_token: UserToken`,\n impersonator.username as impersonator_username,\n impersonator.display_name as impersonator_display_name,\n uir.impersonated_user_token as `impersonated_user_token: UserToken`,\n impersonated.username as impersonated_username,\n impersonated.display_name as impersonated_display_name,\n uir.is_redeemed as `is_redeemed: bool`,\n (uir.expires_at < NOW()) as `is_expired: bool`,\n uir.expires_at,\n uir.created_at,\n uir.updated_at\nFROM user_impersonation_requests uir\nJOIN users impersonator ON impersonator.token = uir.impersonator_user_token\nJOIN users impersonated ON impersonated.token = uir.impersonated_user_token\nORDER BY uir.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "impersonator_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "impersonator_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "impersonator_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "impersonated_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "impersonated_username",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "impersonated_display_name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "is_redeemed: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "is_expired: bool",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "expires_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "e72b3dfdd9df4ee10a696faa4dfc3fb8addc9d2b88017f503c339fb616bef6f1"
|
||||
}
|
||||
@@ -58,7 +58,7 @@ artcraft/
|
||||
|
||||
- Rust with no minimum supported version
|
||||
- Actix-web for HTTP services
|
||||
- SQLx for MySQL and SQLite
|
||||
- SQLx for MySQL and SQLite; prefer `sqlx::query!` / `sqlx::query_as!` compile-time checked macros over runtime `sqlx::query()` whenever possible
|
||||
- A mix of wreq and reqwest for Rust HTTP clients
|
||||
- Never use `println!` or `eprintln!` outside of tests; use `log` crate macros instead
|
||||
- When two crates export the same type name, alias imports with a suffix: `use foo::Bar as BarFoo;`
|
||||
|
||||
Generated
+22
@@ -770,6 +770,7 @@ dependencies = [
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"artcraft_client",
|
||||
"enums",
|
||||
"fal_client",
|
||||
"log",
|
||||
"muapi_client",
|
||||
@@ -3876,6 +3877,13 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error_utils"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errors"
|
||||
version = "0.0.1"
|
||||
@@ -7743,6 +7751,17 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "opaque_cursors"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"magic-crypt",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "open"
|
||||
version = "5.3.3"
|
||||
@@ -11234,6 +11253,7 @@ dependencies = [
|
||||
"elasticsearch",
|
||||
"elasticsearch_schema",
|
||||
"enums",
|
||||
"error_utils",
|
||||
"errors",
|
||||
"fal_client",
|
||||
"ffmpeg_utils",
|
||||
@@ -11261,6 +11281,7 @@ dependencies = [
|
||||
"mimetypes",
|
||||
"mysql_queries",
|
||||
"once_cell",
|
||||
"opaque_cursors",
|
||||
"pager",
|
||||
"password",
|
||||
"primitives",
|
||||
@@ -14772,6 +14793,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"test_data",
|
||||
"tokio",
|
||||
"uuid 1.22.0",
|
||||
"workspace-build-acceleration",
|
||||
|
||||
@@ -59,6 +59,7 @@ members = [
|
||||
"crates/lib/data/primitives",
|
||||
"crates/lib/data/web_base64",
|
||||
"crates/lib/easyenv", # NB: Vendored version of https://github.com/echelon/easyenv.rs that we'll improve.
|
||||
"crates/lib/error_utils",
|
||||
"crates/lib/errors",
|
||||
"crates/lib/ffmpeg_utils",
|
||||
"crates/lib/files/file_to_base64_url",
|
||||
@@ -72,6 +73,7 @@ members = [
|
||||
"crates/lib/http_headers",
|
||||
"crates/lib/jobs_common",
|
||||
"crates/lib/jwt_light",
|
||||
"crates/lib/opaque_cursors",
|
||||
"crates/lib/jwt_signer",
|
||||
"crates/lib/logging",
|
||||
"crates/lib/markdown",
|
||||
@@ -236,6 +238,7 @@ cookie_store = { path = "crates/lib/cookie_store" }
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
easyenv = { path = "crates/lib/easyenv" }
|
||||
enums = { path = "crates/schema/public/enums" }
|
||||
error_utils = { path = "crates/lib/error_utils" }
|
||||
errors = { path = "crates/lib/errors" }
|
||||
fal_client = { path = "crates/api_clients/fal_client" }
|
||||
ffmpeg_utils = { path = "crates/lib/ffmpeg_utils" }
|
||||
@@ -250,6 +253,7 @@ images = { path = "crates/lib/files/images" }
|
||||
jobs_common = { path = "crates/lib/jobs_common" }
|
||||
jwt_light = { path = "crates/lib/jwt_light" }
|
||||
jwt_signer = { path = "crates/lib/jwt_signer" }
|
||||
opaque_cursors = { path = "crates/lib/opaque_cursors" }
|
||||
memory_caching = { path = "crates/lib/caching/memory_caching" }
|
||||
memory_store = { path = "crates/lib/caching/memory_store" }
|
||||
midjourney_client = { path = "crates/api_clients/midjourney_client" }
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS staff_audit_logs;
|
||||
@@ -0,0 +1,39 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE staff_audit_logs (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" of the audit log item (in case we add a UI or notes)
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of action taken
|
||||
audit_action VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of entity acted upon (if any)
|
||||
maybe_entity_type VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- The token of the entity mutated (if any)
|
||||
maybe_entity_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The staff user who performed the action.
|
||||
staff_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- For abuse tracking.
|
||||
-- Wide enough for IPv4/6
|
||||
staff_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
KEY index_maybe_entity_type (maybe_entity_type),
|
||||
KEY index_maybe_entity_token (maybe_entity_token),
|
||||
KEY index_staff_user_token (staff_user_token),
|
||||
KEY index_created_at (created_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE user_sessions
|
||||
DROP COLUMN maybe_impersonation_user_token;
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE user_sessions
|
||||
ADD COLUMN maybe_impersonation_user_token VARCHAR(32) DEFAULT NULL
|
||||
AFTER user_token;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS user_impersonation_requests;
|
||||
@@ -0,0 +1,44 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE user_impersonation_requests (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Internal "primary key"
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The user being impersonated.
|
||||
impersonated_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The staff user who initiated the impersonation.
|
||||
impersonator_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The actual secret that must be presented to redeem the impersonation.
|
||||
user_impersonation_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Whether this request has been redeemed (used to create a session).
|
||||
is_redeemed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- IP address of the staff user who created the request.
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address from which the request was redeemed, if redeemed.
|
||||
ip_address_redemption VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- The request expires after this time if not redeemed.
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (user_impersonation_token),
|
||||
KEY index_impersonated_user_token (impersonated_user_token),
|
||||
KEY index_impersonator_user_token (impersonator_user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Recreate with the original signed BIGINT id.
|
||||
CREATE TABLE user_impersonation_requests (
|
||||
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
impersonated_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
impersonator_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
user_impersonation_token VARCHAR(32) NOT NULL,
|
||||
|
||||
is_redeemed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
|
||||
ip_address_redemption VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (user_impersonation_token),
|
||||
KEY index_impersonated_user_token (impersonated_user_token),
|
||||
KEY index_impersonator_user_token (impersonator_user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS user_impersonation_requests;
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS user_impersonation_requests;
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE user_impersonation_requests (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Internal "primary key"
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The user being impersonated.
|
||||
impersonated_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The staff user who initiated the impersonation.
|
||||
impersonator_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The actual secret that must be presented to redeem the impersonation.
|
||||
user_impersonation_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Whether this request has been redeemed (used to create a session).
|
||||
is_redeemed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- IP address of the staff user who created the request.
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address from which the request was redeemed, if redeemed.
|
||||
ip_address_redemption VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- The request expires after this time if not redeemed.
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (user_impersonation_token),
|
||||
KEY index_impersonated_user_token (impersonated_user_token),
|
||||
KEY index_impersonator_user_token (impersonator_user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,31 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Recreate with the original signed BIGINT id.
|
||||
CREATE TABLE staff_audit_logs (
|
||||
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
audit_action VARCHAR(32) NOT NULL,
|
||||
|
||||
maybe_entity_type VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
maybe_entity_token VARCHAR(32) NOT NULL,
|
||||
|
||||
staff_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
staff_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
KEY index_maybe_entity_type (maybe_entity_type),
|
||||
KEY index_maybe_entity_token (maybe_entity_token),
|
||||
KEY index_staff_user_token (staff_user_token),
|
||||
KEY index_created_at (created_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS staff_audit_logs;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS staff_audit_logs;
|
||||
@@ -0,0 +1,39 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE staff_audit_logs (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" of the audit log item (in case we add a UI or notes)
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of action taken
|
||||
audit_action VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of entity acted upon (if any)
|
||||
maybe_entity_type VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- The token of the entity mutated (if any)
|
||||
maybe_entity_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The staff user who performed the action.
|
||||
staff_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- For abuse tracking.
|
||||
-- Wide enough for IPv4/6
|
||||
staff_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
KEY index_maybe_entity_type (maybe_entity_type),
|
||||
KEY index_maybe_entity_token (maybe_entity_token),
|
||||
KEY index_staff_user_token (staff_user_token),
|
||||
KEY index_created_at (created_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,38 @@
|
||||
# SQL Migrations
|
||||
|
||||
Incremental schema changes applied in order by timestamp prefix.
|
||||
|
||||
## Naming Convention
|
||||
|
||||
Migration directories follow the format:
|
||||
|
||||
```
|
||||
YYYY-MM-DD-HHMMSS-NNNN_description_goes_here/
|
||||
up.sql
|
||||
down.sql
|
||||
```
|
||||
|
||||
These are created with the `diesel` tool, and in particular:
|
||||
|
||||
> `diesel migration generate description_goes_here`
|
||||
|
||||
- `NNNN` is a zero-padded sequence number for ordering multiple migrations on the same timestamp.
|
||||
- `up.sql` applies the migration forward.
|
||||
- `down.sql` reverses it.
|
||||
|
||||
## Table Conventions
|
||||
|
||||
- Primary key `id` columns must be `BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT`. Do not use signed IDs.
|
||||
- Always create tables with `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin`.
|
||||
- All SQL files should start with the standard IntelliJ noinspection comments:
|
||||
```sql
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
```
|
||||
|
||||
## Rules
|
||||
|
||||
- Every `up.sql` must have a corresponding `down.sql` that cleanly reverses the migration.
|
||||
- For `CREATE TABLE` migrations, the `down.sql` is typically `DROP TABLE IF EXISTS table_name;`.
|
||||
- For `ALTER TABLE` migrations, the `down.sql` reverses the alteration (e.g. `DROP COLUMN`).
|
||||
@@ -0,0 +1,26 @@
|
||||
# Squashed / Materialized Schemas
|
||||
|
||||
Fully materialized table definitions representing the current state of each table
|
||||
after all migrations have been applied. These are not executed — they exist as
|
||||
human-readable documentation of the current schema.
|
||||
|
||||
## When to Update
|
||||
|
||||
Update the corresponding squashed file whenever a migration changes a table's schema
|
||||
(adding columns, changing types, dropping and recreating, etc.).
|
||||
|
||||
## Table Conventions
|
||||
|
||||
- Primary key `id` columns must be `BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT`. Do not use signed IDs.
|
||||
- Always create tables with `ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin`.
|
||||
- All SQL files should start with the standard IntelliJ noinspection comments:
|
||||
```sql
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
```
|
||||
|
||||
## File Organization
|
||||
|
||||
Files are named after their table (e.g. `users.sql`, `media_files.sql`).
|
||||
Some tables are grouped into subdirectories (e.g. `user_tables/`).
|
||||
@@ -0,0 +1,39 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE staff_audit_logs (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" of the audit log item (in case we add a UI or notes)
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of action taken
|
||||
audit_action VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The type of entity acted upon (if any)
|
||||
maybe_entity_type VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- The token of the entity mutated (if any)
|
||||
maybe_entity_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The staff user who performed the action.
|
||||
staff_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- For abuse tracking.
|
||||
-- Wide enough for IPv4/6
|
||||
staff_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
KEY index_maybe_entity_type (maybe_entity_type),
|
||||
KEY index_maybe_entity_token (maybe_entity_token),
|
||||
KEY index_staff_user_token (staff_user_token),
|
||||
KEY index_created_at (created_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,44 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE user_impersonation_requests (
|
||||
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Internal "primary key"
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The user being impersonated.
|
||||
impersonated_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The staff user who initiated the impersonation.
|
||||
impersonator_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The actual secret that must be presented to redeem the impersonation.
|
||||
user_impersonation_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Whether this request has been redeemed (used to create a session).
|
||||
is_redeemed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- IP address of the staff user who created the request.
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address from which the request was redeemed, if redeemed.
|
||||
ip_address_redemption VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- The request expires after this time if not redeemed.
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (user_impersonation_token),
|
||||
KEY index_impersonated_user_token (impersonated_user_token),
|
||||
KEY index_impersonator_user_token (impersonator_user_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,50 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE user_sessions (
|
||||
-- Not used for anything except replication.
|
||||
id BIGINT(20) NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Session primary key
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The user that the session belongs to.
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- NEVER IMPERSONATE A USER'S ACCOUNT WITHOUT THEIR CONSENT!
|
||||
--
|
||||
-- For support requests and inquiries, we can have staff log into user accounts
|
||||
-- to verify and debug possible broken states. The user is always told about
|
||||
-- this if we are to use this for support cases.
|
||||
--
|
||||
-- Every action performed by impersonated sessions is logged for auditing.
|
||||
--
|
||||
-- If this session is impersonating another user, this is the token of
|
||||
-- the staff user who initiated the impersonation.
|
||||
maybe_impersonation_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Track each session's creation IP.
|
||||
-- Wide enough for IPv4/6
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- Session termination time.
|
||||
-- This must be set by the server code, or the session is invalid
|
||||
expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- deletion = session termination
|
||||
-- Typically these are destroyed by users, but if in the future we allow mods to
|
||||
-- delete them, it doesn't really matter who did the deletion: sessions are not
|
||||
-- designed to be recoverable.
|
||||
deleted_at TIMESTAMP NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
KEY fk_user_token (user_token),
|
||||
KEY index_ip_address_creation (ip_address_creation)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
+1
-1
@@ -15,7 +15,7 @@ body:
|
||||
type: json
|
||||
content: |
|
||||
{
|
||||
"user_token": "user_jg7ng4kdt2x5y", // newuser946
|
||||
"user_token": "user_rtdhen9kz73kx", //
|
||||
"payments_namespace": "artcraft"
|
||||
}
|
||||
order: 4100053395280358
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
$kind: http-request
|
||||
name: Impersonate User Request
|
||||
url: "{{api_host}}/v1/moderation/user_sessions/impersonate"
|
||||
method: POST
|
||||
body:
|
||||
type: json
|
||||
content: |-
|
||||
{
|
||||
"username": "newuser3"
|
||||
}
|
||||
order: 4318829221021218
|
||||
+2
-1
@@ -16,7 +16,8 @@ pub struct GenerateVeo2ImageToVideoRequest {
|
||||
|
||||
/// Optional text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
|
||||
/// **NB(bt): ASPECT RATIO IS ACTUALLY NOT USED IN VEO 2 IMAGE-TO-VIDEO**
|
||||
/// Optional: aspect ratio of the generated video.
|
||||
pub aspect_ratio: Option<GenerateVeo2AspectRatio>,
|
||||
|
||||
|
||||
+11
@@ -21,6 +21,9 @@ pub struct GenerateVeo3FastImageToVideoRequest {
|
||||
pub resolution: Option<GenerateVeo3FastResolution>,
|
||||
|
||||
/// Optional: aspect ratio of the generated video.
|
||||
pub aspect_ratio: Option<GenerateVeo3FastAspectRatio>,
|
||||
|
||||
/// Optional: duration of the generated video.
|
||||
pub duration: Option<GenerateVeo3FastDuration>,
|
||||
|
||||
/// Optional: Generate an audio track along with the video
|
||||
@@ -34,6 +37,14 @@ pub enum GenerateVeo3FastResolution {
|
||||
TenEightyP, // 1080p
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateVeo3FastAspectRatio {
|
||||
Auto,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateVeo3FastDuration {
|
||||
|
||||
+4
@@ -42,6 +42,10 @@ pub struct GenerateVeo3ImageToVideoRequest {
|
||||
pub enum GenerateVeo3AspectRatio {
|
||||
WideSixteenNine, // 16:9
|
||||
TallNineSixteen, // 9:16
|
||||
|
||||
/// NB: DO NOT REMOVE. This is an unsupported resolution for Veo 3, but we can't
|
||||
/// impact existing callers by removing it. The handler will convert this to a
|
||||
/// supported resolution or 400 the request instead.
|
||||
Square, // 1:1
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -2,6 +2,7 @@ use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
use enums::common::generation::common_image_model::CommonImageModel;
|
||||
use enums::common::generation::common_quality::CommonQuality;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
use enums::common::generation::model_creator::ModelCreator;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
@@ -56,6 +57,9 @@ pub struct OmniGenImageProviderModelDetails {
|
||||
pub struct OmniGenImageModelDetails {
|
||||
pub model: CommonImageModel,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model_creator: Option<ModelCreator>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub full_name: Option<String>,
|
||||
|
||||
@@ -120,12 +124,16 @@ pub struct OmniGenImageModelDetails {
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub batch_size_default: Option<u16>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_disabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for OmniGenImageModelDetails {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: CommonImageModel::NanoBananaPro,
|
||||
model_creator: None,
|
||||
full_name: None,
|
||||
text_prompt_supported: None,
|
||||
text_prompt_max_length: None,
|
||||
@@ -145,6 +153,7 @@ impl Default for OmniGenImageModelDetails {
|
||||
batch_size_max: None,
|
||||
batch_size_options: None,
|
||||
batch_size_default: None,
|
||||
is_disabled: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
@@ -2,6 +2,7 @@ use enums::common::generation::common_aspect_ratio::CommonAspectRatio;
|
||||
use enums::common::generation::common_quality::CommonQuality;
|
||||
use enums::common::generation::common_resolution::CommonResolution;
|
||||
use enums::common::generation::common_video_model::CommonVideoModel;
|
||||
use enums::common::generation::model_creator::ModelCreator;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
@@ -65,6 +66,9 @@ pub struct OmniGenVideoModelDetails {
|
||||
|
||||
pub model: CommonVideoModel,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub model_creator: Option<ModelCreator>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub full_name: Option<String>,
|
||||
|
||||
@@ -163,12 +167,16 @@ pub struct OmniGenVideoModelDetails {
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub batch_size_default: Option<u16>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub is_disabled: Option<bool>,
|
||||
}
|
||||
|
||||
impl Default for OmniGenVideoModelDetails {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
model: DEFAULT_VIDEO_MODEL,
|
||||
model_creator: None,
|
||||
full_name: None,
|
||||
text_prompt_supported: None,
|
||||
text_prompt_max_length: None,
|
||||
@@ -202,6 +210,7 @@ impl Default for OmniGenVideoModelDetails {
|
||||
batch_size_max: None,
|
||||
batch_size_options: None,
|
||||
batch_size_default: None,
|
||||
is_disabled: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ pub mod analytics;
|
||||
pub mod credits;
|
||||
pub mod generate;
|
||||
pub mod jobs;
|
||||
pub mod omni_gen;
|
||||
pub mod media_files;
|
||||
pub mod prompts;
|
||||
pub mod stripe_artcraft;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod video;
|
||||
+1
@@ -0,0 +1 @@
|
||||
pub mod omni_gen_video;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::omni_gen::cost_and_generate_requests::omni_gen_video_cost_and_generate_request::OmniGenVideoCostAndGenerateRequest;
|
||||
use artcraft_api_defs::omni_gen::generate_response::omni_gen_video_generate_response::OmniGenVideoGenerateResponse;
|
||||
|
||||
pub const OMNI_GEN_VIDEO_GENERATE_PATH: &str = "/v1/omni_gen/generate/video";
|
||||
|
||||
pub async fn omni_gen_video_generate(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: OmniGenVideoCostAndGenerateRequest,
|
||||
) -> Result<OmniGenVideoGenerateResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
OMNI_GEN_VIDEO_GENERATE_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod generate;
|
||||
@@ -17,6 +17,7 @@ workspace-build-acceleration.workspace = true
|
||||
|
||||
# Internal
|
||||
artcraft_api_defs.workspace = true
|
||||
enums.workspace = true
|
||||
artcraft_client.workspace = true
|
||||
fal_client.workspace = true
|
||||
muapi_client.workspace = true
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum AudioListRef<'a> {
|
||||
MediaFileTokens(&'a Vec<MediaFileToken>),
|
||||
Urls(&'a Vec<String>),
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum AudioListRef {
|
||||
MediaFileTokens(Vec<MediaFileToken>),
|
||||
Urls(Vec<String>),
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use tokens::tokens::characters::CharacterToken;
|
||||
|
||||
/// Forward-compatible character reference list.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum CharacterListRef<'a> {
|
||||
CharacterTokens(&'a Vec<CharacterToken>),
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum CharacterListRef {
|
||||
CharacterTokens(Vec<CharacterToken>),
|
||||
|
||||
// In the future, we may have other identifiers for characters.
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ pub enum CommonVideoModel {
|
||||
#[serde(rename = "seedance_2p0")]
|
||||
Seedance2p0,
|
||||
|
||||
#[serde(rename = "seedance_2p0_fast")]
|
||||
Seedance2p0Fast,
|
||||
|
||||
#[serde(rename = "sora_2")]
|
||||
Sora2,
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ImageListRef<'a> {
|
||||
MediaFileTokens(&'a Vec<MediaFileToken>),
|
||||
Urls(&'a Vec<String>),
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ImageListRef {
|
||||
MediaFileTokens(Vec<MediaFileToken>),
|
||||
Urls(Vec<String>),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum ImageRef<'a> {
|
||||
MediaFileToken(&'a MediaFileToken),
|
||||
Url(&'a str),
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ImageRef {
|
||||
MediaFileToken(MediaFileToken),
|
||||
Url(String),
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum VideoListRef<'a> {
|
||||
MediaFileTokens(&'a Vec<MediaFileToken>),
|
||||
Urls(&'a Vec<String>),
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum VideoListRef {
|
||||
MediaFileTokens(Vec<MediaFileToken>),
|
||||
Urls(Vec<String>),
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_dev::PlanArtcraftFlux1Dev;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_1_dev(
|
||||
plan: &PlanArtcraftFlux1Dev<'_>,
|
||||
plan: &PlanArtcraftFlux1Dev,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.02/image (2 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 2;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_1_schnell::PlanArtcraftFlux1Schnell;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_1_schnell(
|
||||
_plan: &PlanArtcraftFlux1Schnell<'_>,
|
||||
_plan: &PlanArtcraftFlux1Schnell,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: not available
|
||||
ImageGenerationCostEstimate {
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_2_lora_angles::PlanArtcraftFlux2LoraAngles;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_2_lora_angles(
|
||||
plan: &PlanArtcraftFlux2LoraAngles<'_>,
|
||||
plan: &PlanArtcraftFlux2LoraAngles,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 4;
|
||||
@@ -43,7 +43,7 @@ mod tests {
|
||||
model: CommonImageModel::Flux2LoraAngles,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1::PlanArtcraftFluxPro11;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_pro_1p1(
|
||||
plan: &PlanArtcraftFluxPro11<'_>,
|
||||
plan: &PlanArtcraftFluxPro11,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Fal pricing: $0.04 per megapixel, billed by rounding up to the nearest
|
||||
// megapixel. The default image_size values are ~1MP, so 4 cents per image.
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_flux_pro_1p1_ultra::PlanArtcraftFluxPro11Ultra;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_flux_pro_1p1_ultra(
|
||||
plan: &PlanArtcraftFluxPro11Ultra<'_>,
|
||||
plan: &PlanArtcraftFluxPro11Ultra,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.06/image (6 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 6;
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraf
|
||||
};
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_gpt_image_1(
|
||||
plan: &PlanArtcraftGptImage1<'_>,
|
||||
plan: &PlanArtcraftGptImage1,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing per the fal-ai GPT Image 1 docs, adapted for our artcraft billing:
|
||||
//
|
||||
@@ -62,7 +62,7 @@ mod tests {
|
||||
use crate::generate::generate_image::generate_image_request::GenerateImageRequest;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
fn base() -> GenerateImageRequest<'static> {
|
||||
fn base() -> GenerateImageRequest {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::GptImage1,
|
||||
provider: Provider::Artcraft,
|
||||
@@ -109,7 +109,7 @@ mod tests {
|
||||
quality,
|
||||
aspect_ratio,
|
||||
image_batch_count: Some(batch),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
|
||||
..base()
|
||||
};
|
||||
request.build().unwrap().estimate_costs()
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_gpt_image_1p5::PlanArtcraftGptImage1p5;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_gpt_image_1p5(
|
||||
plan: &PlanArtcraftGptImage1p5<'_>,
|
||||
plan: &PlanArtcraftGptImage1p5,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Per fal docs (fal-ai/gpt-image-1.5 and fal-ai/gpt-image-1.5/edit):
|
||||
//
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana::PlanArtcraftNanaBanana;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_nano_banana(
|
||||
plan: &PlanArtcraftNanaBanana<'_>,
|
||||
plan: &PlanArtcraftNanaBanana,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 4;
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_2::PlanArtcraftNanaBanana2;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_nano_banana_2(
|
||||
plan: &PlanArtcraftNanaBanana2<'_>,
|
||||
plan: &PlanArtcraftNanaBanana2,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing (USD cents per image), scaled off the 1K base cost of $0.08:
|
||||
// 0.5K → $0.06 (0.75×)
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_nano_banana_pro::PlanArtcraftNanaBananaPro;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_nano_banana_pro(
|
||||
plan: &PlanArtcraftNanaBananaPro<'_>,
|
||||
plan: &PlanArtcraftNanaBananaPro,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.15/image at 1K or 2K resolution, $0.30/image at 4K.
|
||||
// (Stored as USD cents.)
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_qwen_edit_2511_angles::PlanArtcraftQwenEdit2511Angles;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_qwen_edit_2511_angles(
|
||||
plan: &PlanArtcraftQwenEdit2511Angles<'_>,
|
||||
plan: &PlanArtcraftQwenEdit2511Angles,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 4;
|
||||
@@ -43,7 +43,7 @@ mod tests {
|
||||
model: CommonImageModel::QwenEdit2511Angles,
|
||||
provider: Provider::Artcraft,
|
||||
prompt: None,
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
quality: None,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4::PlanArtcraftSeedream4;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_seedream_4(
|
||||
plan: &PlanArtcraftSeedream4<'_>,
|
||||
plan: &PlanArtcraftSeedream4,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.03/image (3 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 3;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_4p5::PlanArtcraftSeedream4p5;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_seedream_4p5(
|
||||
plan: &PlanArtcraftSeedream4p5<'_>,
|
||||
plan: &PlanArtcraftSeedream4p5,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 4;
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::artcraft::plan_generate_image_artcraft_seedream_5_lite::PlanArtcraftSeedream5Lite;
|
||||
|
||||
pub(crate) fn estimate_image_cost_artcraft_seedream_5_lite(
|
||||
plan: &PlanArtcraftSeedream5Lite<'_>,
|
||||
plan: &PlanArtcraftSeedream5Lite,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04/image (4 cents). 1 credit = 1 USD cent.
|
||||
let cost_per_image: u64 = 4;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_dev::PlanFalFlux1Dev;
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_flux_1_dev(
|
||||
plan: &PlanFalFlux1Dev<'_>,
|
||||
plan: &PlanFalFlux1Dev,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.025 per megapixel, billed by rounding up to nearest megapixel.
|
||||
// Default image sizes are ~1MP (1024x1024), so ~$0.025/image = 3 cents rounded up.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_1_schnell::PlanFalFlux1Schnell;
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_flux_1_schnell(
|
||||
plan: &PlanFalFlux1Schnell<'_>,
|
||||
plan: &PlanFalFlux1Schnell,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.003 per megapixel, billed by rounding up to nearest megapixel.
|
||||
// Default image sizes are ~1MP (1024x1024), so ~$0.003/image = 1 cent rounded up.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1::PlanFalFluxPro11;
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_flux_pro_1p1(
|
||||
plan: &PlanFalFluxPro11<'_>,
|
||||
plan: &PlanFalFluxPro11,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04 per megapixel, billed by rounding up to nearest megapixel.
|
||||
// Default image sizes are ~1MP (1024x1024), so ~$0.04/image = 4 cents.
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_flux_pro_1p1_ultra::PlanFalFluxPro11Ultra;
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_flux_pro_1p1_ultra(
|
||||
plan: &PlanFalFluxPro11Ultra<'_>,
|
||||
plan: &PlanFalFluxPro11Ultra,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.06 per image.
|
||||
let cost_per_image: u64 = 6;
|
||||
|
||||
+5
-5
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_imag
|
||||
};
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_gpt_image_1(
|
||||
plan: &PlanFalGptImage1<'_>,
|
||||
plan: &PlanFalGptImage1,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Per fal docs (fal-ai/gpt-image-1/{text-to-image,edit-image}):
|
||||
//
|
||||
@@ -59,9 +59,9 @@ mod tests {
|
||||
quality: FalGptImage1Quality,
|
||||
image_size: Option<FalGptImage1ImageSize>,
|
||||
num_images: FalGptImage1NumImages,
|
||||
) -> PlanFalGptImage1<'static> {
|
||||
) -> PlanFalGptImage1 {
|
||||
PlanFalGptImage1 {
|
||||
prompt: Some("p"),
|
||||
prompt: Some("p".to_string()),
|
||||
image_urls: vec![],
|
||||
image_size,
|
||||
quality,
|
||||
@@ -74,12 +74,12 @@ mod tests {
|
||||
image_size: Option<FalGptImage1ImageSize>,
|
||||
num_images: FalGptImage1NumImages,
|
||||
num_input_images: usize,
|
||||
) -> PlanFalGptImage1<'static> {
|
||||
) -> PlanFalGptImage1 {
|
||||
let image_urls = (0..num_input_images)
|
||||
.map(|i| format!("https://fake.example.com/img{}.png", i))
|
||||
.collect();
|
||||
PlanFalGptImage1 {
|
||||
prompt: Some("p"),
|
||||
prompt: Some("p".to_string()),
|
||||
image_urls,
|
||||
image_size,
|
||||
quality,
|
||||
|
||||
+3
-3
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_gpt_imag
|
||||
};
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_gpt_image_1p5(
|
||||
plan: &PlanFalGptImage1p5<'_>,
|
||||
plan: &PlanFalGptImage1p5,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Per fal docs (fal-ai/gpt-image-1.5 and fal-ai/gpt-image-1.5/edit):
|
||||
//
|
||||
@@ -50,9 +50,9 @@ mod tests {
|
||||
quality: FalGptImage1p5Quality,
|
||||
image_size: Option<FalGptImage1p5ImageSize>,
|
||||
num_images: FalGptImage1p5NumImages,
|
||||
) -> PlanFalGptImage1p5<'static> {
|
||||
) -> PlanFalGptImage1p5 {
|
||||
PlanFalGptImage1p5 {
|
||||
prompt: Some("p"),
|
||||
prompt: Some("p".to_string()),
|
||||
image_urls: vec![],
|
||||
image_size,
|
||||
quality,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
|
||||
};
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_nano_banana(
|
||||
plan: &PlanFalNanoBanana<'_>,
|
||||
plan: &PlanFalNanoBanana,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.039/image (no resolution multiplier — Gemini 25 Flash has no resolution option).
|
||||
// Rounded to 4 cents per image.
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
|
||||
};
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_nano_banana_2(
|
||||
plan: &PlanFalNanaBanana2<'_>,
|
||||
plan: &PlanFalNanaBanana2,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.08/image base (1K). Multipliers: 0.5K = 0.75x, 2K = 1.5x, 4K = 2x.
|
||||
// (Stored as USD cents.)
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ use crate::generate::generate_image::plan::fal::plan_generate_image_fal_nano_ban
|
||||
};
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_nano_banana_pro(
|
||||
plan: &PlanFalNanaBananaPro<'_>,
|
||||
plan: &PlanFalNanaBananaPro,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.15/image at 1K or 2K resolution, $0.30/image at 4K.
|
||||
// (Stored as USD cents.)
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4::PlanFalSeedream4;
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_seedream_4(
|
||||
plan: &PlanFalSeedream4<'_>,
|
||||
plan: &PlanFalSeedream4,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.03 per image.
|
||||
let cost_per_image: u64 = 3;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_4p5::PlanFalSeedream4p5;
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_seedream_4p5(
|
||||
plan: &PlanFalSeedream4p5<'_>,
|
||||
plan: &PlanFalSeedream4p5,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.04 per image.
|
||||
let cost_per_image: u64 = 4;
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::generate::generate_image::image_generation_cost_estimate::ImageGenera
|
||||
use crate::generate::generate_image::plan::fal::plan_generate_image_fal_seedream_5_lite::PlanFalSeedream5Lite;
|
||||
|
||||
pub(crate) fn estimate_image_cost_fal_seedream_5_lite(
|
||||
plan: &PlanFalSeedream5Lite<'_>,
|
||||
plan: &PlanFalSeedream5Lite,
|
||||
) -> ImageGenerationCostEstimate {
|
||||
// Pricing: $0.035 per image — round up to 4 cents.
|
||||
let cost_per_image: u64 = 4;
|
||||
|
||||
+5
-5
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image:
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_1_dev(
|
||||
plan: &PlanArtcraftFlux1Dev<'_>,
|
||||
plan: &PlanArtcraftFlux1Dev,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFlux1DevTextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
prompt: plan.prompt.clone(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
@@ -49,7 +49,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
prompt: Some("a dog surfing a wave, cinematic".to_string()),
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night"),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_1_dev_image_request()
|
||||
};
|
||||
|
||||
+5
-5
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_1_schnell_text_to_im
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_1_schnell_text_to_image::generate_flux_1_schnell_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_1_schnell(
|
||||
plan: &PlanArtcraftFlux1Schnell<'_>,
|
||||
plan: &PlanArtcraftFlux1Schnell,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFlux1SchnellTextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
prompt: plan.prompt.clone(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
@@ -48,7 +48,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a man and woman walking through a cyberpunk city at night"),
|
||||
prompt: Some("a man and woman walking through a cyberpunk city at night".to_string()),
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
|
||||
@@ -71,7 +71,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
prompt: Some("a dog surfing a wave, cinematic".to_string()),
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
|
||||
@@ -97,7 +97,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a man and woman walking through a futuristic city at evening"),
|
||||
prompt: Some("a man and woman walking through a futuristic city at evening".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_1_schnell_image_request()
|
||||
};
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ use artcraft_api_defs::generate::image::angle::flux_2_lora_edit_image_angle::Flu
|
||||
use artcraft_client::endpoints::generate::image::angle::flux_2_lora_edit_image_angle::flux_2_lora_edit_image_angle;
|
||||
|
||||
pub async fn execute_artcraft_flux_2_lora_angles(
|
||||
plan: &PlanArtcraftFlux2LoraAngles<'_>,
|
||||
plan: &PlanArtcraftFlux2LoraAngles,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = Flux2LoraEditImageAngleRequest {
|
||||
@@ -53,7 +53,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::SquareHd),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
|
||||
horizontal_angle: Some(30.0),
|
||||
vertical_angle: Some(0.0),
|
||||
zoom: Some(1.0),
|
||||
|
||||
+5
-5
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_text_to_image::generate_flux_pro_11_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_pro_1p1(
|
||||
plan: &PlanArtcraftFluxPro11<'_>,
|
||||
plan: &PlanArtcraftFluxPro11,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFluxPro11TextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
prompt: plan.prompt.clone(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
@@ -49,7 +49,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
prompt: Some("a dog surfing a wave, cinematic".to_string()),
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night"),
|
||||
prompt: Some("a bear walking through an abandoned desert city at night".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_pro_1p1_image_request()
|
||||
};
|
||||
|
||||
+5
-5
@@ -9,12 +9,12 @@ use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to
|
||||
use artcraft_client::endpoints::generate::image::text::generate_flux_pro_11_ultra_text_to_image::generate_flux_pro_11_ultra_text_to_image;
|
||||
|
||||
pub async fn execute_artcraft_flux_pro_1p1_ultra(
|
||||
plan: &PlanArtcraftFluxPro11Ultra<'_>,
|
||||
plan: &PlanArtcraftFluxPro11Ultra,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GenerateFluxPro11UltraTextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
prompt: plan.prompt.clone(),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: Some(plan.num_images),
|
||||
};
|
||||
@@ -49,7 +49,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
|
||||
@@ -72,7 +72,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
prompt: Some("a dog surfing a wave, cinematic".to_string()),
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
|
||||
@@ -98,7 +98,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a t-rex walking through an abandoned desert city at night"),
|
||||
prompt: Some("a t-rex walking through an abandoned desert city at night".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_flux_pro_1p1_ultra_image_request()
|
||||
};
|
||||
|
||||
+8
-8
@@ -14,14 +14,14 @@ use artcraft_client::endpoints::generate::image::text::generate_gpt_image_1_text
|
||||
/// text-to-image, one for image-edit), so we dispatch on whether image refs
|
||||
/// are present rather than calling a single multi-function endpoint.
|
||||
pub async fn execute_artcraft_gpt_image_1(
|
||||
plan: &PlanArtcraftGptImage1<'_>,
|
||||
plan: &PlanArtcraftGptImage1,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let inference_job_token = match plan.image_inputs {
|
||||
let inference_job_token = match plan.image_inputs.clone() {
|
||||
Some(image_inputs) => {
|
||||
let request = GptImage1EditImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: Some(image_inputs.to_owned()),
|
||||
image_size: plan.image_size.map(|s| s.to_edit()),
|
||||
num_images: Some(plan.num_images.to_edit()),
|
||||
@@ -39,7 +39,7 @@ pub async fn execute_artcraft_gpt_image_1(
|
||||
None => {
|
||||
let request = GenerateGptImage1TextToImageRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_size: plan.image_size.map(|s| s.to_t2i()),
|
||||
num_images: Some(plan.num_images.to_t2i()),
|
||||
image_quality: Some(plan.quality.to_t2i()),
|
||||
@@ -89,7 +89,7 @@ mod tests {
|
||||
fn build_edit_image_plan_smoke() {
|
||||
let tokens = vec![MediaFileToken::new_from_str("mf_test00000000000000000000000000")];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
|
||||
..base_gpt_image_1_image_request()
|
||||
};
|
||||
let plan = request.build().expect("plan should build");
|
||||
@@ -106,7 +106,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a horse walking through a cyberpunk city at night"),
|
||||
prompt: Some("a horse walking through a cyberpunk city at night".to_string()),
|
||||
..base_gpt_image_1_image_request()
|
||||
};
|
||||
|
||||
@@ -129,8 +129,8 @@ mod tests {
|
||||
];
|
||||
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a desert with cacti"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a desert with cacti".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
image_batch_count: Some(1),
|
||||
..base_gpt_image_1_image_request()
|
||||
|
||||
+9
-9
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::gpt_image_1p5_multi_func
|
||||
use artcraft_client::endpoints::generate::image::multi_function::gpt_image_1p5_multi_function_image_gen_image::gpt_image_1p5_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_gpt_image_1p5(
|
||||
plan: &PlanArtcraftGptImage1p5<'_>,
|
||||
plan: &PlanArtcraftGptImage1p5,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = GptImage1p5MultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: plan.image_inputs.clone(),
|
||||
mask_image_token: None,
|
||||
num_images: Some(plan.num_images),
|
||||
image_size: plan.image_size,
|
||||
@@ -56,7 +56,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a horse walking through a cyberpunk city at night"),
|
||||
prompt: Some("a horse walking through a cyberpunk city at night".to_string()),
|
||||
..base_gpt_image_1p5_image_request()
|
||||
};
|
||||
|
||||
@@ -81,8 +81,8 @@ mod tests {
|
||||
];
|
||||
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a desert with cacti"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a desert with cacti".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto),
|
||||
image_batch_count: Some(1),
|
||||
..base_gpt_image_1p5_image_request()
|
||||
@@ -109,7 +109,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a bear walking through a abandoned desert city at night"),
|
||||
prompt: Some("a bear walking through a abandoned desert city at night".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_gpt_image_1p5_image_request()
|
||||
};
|
||||
@@ -140,8 +140,8 @@ mod tests {
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- image-edit aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to an alien world"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to an alien world".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(ar),
|
||||
image_batch_count: Some(1),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
|
||||
+9
-9
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_multi_functi
|
||||
use artcraft_client::endpoints::generate::image::multi_function::nano_banana_multi_function_image_gen_image::nano_banana_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_nano_banana(
|
||||
plan: &PlanArtcraftNanaBanana<'_>,
|
||||
plan: &PlanArtcraftNanaBanana,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = NanoBananaMultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: plan.image_inputs.clone(),
|
||||
num_images: Some(plan.num_images),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
};
|
||||
@@ -52,7 +52,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
|
||||
..base_nano_banana_image_request()
|
||||
};
|
||||
|
||||
@@ -77,8 +77,8 @@ mod tests {
|
||||
];
|
||||
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a sunny beach".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
@@ -107,7 +107,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a shiba walking through a cyberpunk city at night"),
|
||||
prompt: Some("a shiba walking through a cyberpunk city at night".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_nano_banana_image_request()
|
||||
};
|
||||
@@ -138,8 +138,8 @@ mod tests {
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- image-edit aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a sunny beach".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
|
||||
+4
-4
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_2_multi_func
|
||||
use artcraft_client::endpoints::generate::image::multi_function::nano_banana_2_multi_function_image_gen_image::nano_banana_2_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_nano_banana_2(
|
||||
plan: &PlanArtcraftNanaBanana2<'_>,
|
||||
plan: &PlanArtcraftNanaBanana2,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = NanaBanana2MultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: plan.image_inputs.clone(),
|
||||
num_images: Some(plan.num_images),
|
||||
resolution: plan.resolution,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
@@ -51,7 +51,7 @@ mod tests {
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
|
||||
..base_nano_banana_2_image_request()
|
||||
};
|
||||
|
||||
|
||||
+7
-7
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::nano_banana_pro_multi_fu
|
||||
use artcraft_client::endpoints::generate::image::multi_function::nano_banana_pro_multi_function_image_gen_image::nano_banana_pro_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_nano_banana_pro(
|
||||
plan: &PlanArtcraftNanaBananaPro<'_>,
|
||||
plan: &PlanArtcraftNanaBananaPro,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = NanoBananaProMultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: plan.image_inputs.clone(),
|
||||
num_images: Some(plan.num_images),
|
||||
resolution: plan.resolution,
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
@@ -53,7 +53,7 @@ mod tests {
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
|
||||
..base_image_request()
|
||||
};
|
||||
|
||||
@@ -77,7 +77,7 @@ mod tests {
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
image_batch_count: Some(4),
|
||||
prompt: Some("a dog surfing a wave, cinematic"),
|
||||
prompt: Some("a dog surfing a wave, cinematic".to_string()),
|
||||
..base_image_request()
|
||||
};
|
||||
|
||||
@@ -104,8 +104,8 @@ mod tests {
|
||||
];
|
||||
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Add the man in the first image into the fantasy scene in the second image. Keep his hoodie and attire, but match the vibe of the fantasy scene."),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Add the man in the first image into the fantasy scene in the second image. Keep his hoodie and attire, but match the vibe of the fantasy scene.".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto), // edit mode: preserve source dimensions
|
||||
resolution: Some(CommonResolution::OneK),
|
||||
quality: None,
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ use artcraft_api_defs::generate::image::angle::qwen_edit_2511_edit_image_angle::
|
||||
use artcraft_client::endpoints::generate::image::angle::qwen_edit_2511_edit_image_angle::qwen_edit_2511_edit_image_angle;
|
||||
|
||||
pub async fn execute_artcraft_qwen_edit_2511_angles(
|
||||
plan: &PlanArtcraftQwenEdit2511Angles<'_>,
|
||||
plan: &PlanArtcraftQwenEdit2511Angles,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = QwenEdit2511EditImageAngleRequest {
|
||||
@@ -18,7 +18,7 @@ pub async fn execute_artcraft_qwen_edit_2511_angles(
|
||||
horizontal_angle: plan.horizontal_angle,
|
||||
vertical_angle: plan.vertical_angle,
|
||||
zoom: plan.zoom,
|
||||
additional_prompt: plan.prompt.map(|p| p.to_string()),
|
||||
additional_prompt: plan.prompt.clone(),
|
||||
num_images: Some(plan.num_images),
|
||||
image_size: plan.image_size,
|
||||
};
|
||||
@@ -55,7 +55,7 @@ mod tests {
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: None,
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&tokens)),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(tokens.clone())),
|
||||
horizontal_angle: Some(30.0),
|
||||
vertical_angle: Some(0.0),
|
||||
zoom: Some(1.0),
|
||||
|
||||
+9
-9
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4_mu
|
||||
use artcraft_client::endpoints::generate::image::multi_function::bytedance_seedream_v4_multi_function_image_gen_image::bytedance_seedream_v4_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_seedream_4(
|
||||
plan: &PlanArtcraftSeedream4<'_>,
|
||||
plan: &PlanArtcraftSeedream4,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = BytedanceSeedreamV4MultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: plan.image_inputs.clone(),
|
||||
num_images: Some(plan.num_images),
|
||||
image_size: plan.image_size,
|
||||
max_images: None,
|
||||
@@ -53,7 +53,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night"),
|
||||
prompt: Some("a cat walking through a cyberpunk city at night".to_string()),
|
||||
..base_seedream_4_image_request()
|
||||
};
|
||||
|
||||
@@ -78,8 +78,8 @@ mod tests {
|
||||
];
|
||||
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a sunny beach".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
@@ -108,7 +108,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a shiba walking through a cyberpunk city at night"),
|
||||
prompt: Some("a shiba walking through a cyberpunk city at night".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_seedream_4_image_request()
|
||||
};
|
||||
@@ -139,8 +139,8 @@ mod tests {
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- image-edit aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a sunny beach"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a sunny beach".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
|
||||
+9
-9
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4p5_
|
||||
use artcraft_client::endpoints::generate::image::multi_function::bytedance_seedream_v4p5_multi_function_image_gen_image::bytedance_seedream_v4p5_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_seedream_4p5(
|
||||
plan: &PlanArtcraftSeedream4p5<'_>,
|
||||
plan: &PlanArtcraftSeedream4p5,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = BytedanceSeedreamV4p5MultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: plan.image_inputs.clone(),
|
||||
num_images: Some(plan.num_images),
|
||||
image_size: plan.image_size,
|
||||
max_images: None,
|
||||
@@ -53,7 +53,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night"),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night".to_string()),
|
||||
..base_seedream_4p5_image_request()
|
||||
};
|
||||
|
||||
@@ -78,8 +78,8 @@ mod tests {
|
||||
];
|
||||
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a spooky haunted house"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a spooky haunted house".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(CommonAspectRatio::Auto2k),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
@@ -108,7 +108,7 @@ mod tests {
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night"),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night".to_string()),
|
||||
request_mismatch_mitigation_strategy: RequestMismatchMitigationStrategy::PayMoreUpgrade,
|
||||
..base_seedream_4p5_image_request()
|
||||
};
|
||||
@@ -139,8 +139,8 @@ mod tests {
|
||||
for ar in CommonAspectRatio::iter() {
|
||||
println!("--- image-edit aspect ratio: {:?} ---", ar);
|
||||
let request = GenerateImageRequest {
|
||||
prompt: Some("Change the background to a spooky haunted house"),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(&image_tokens)),
|
||||
prompt: Some("Change the background to a spooky haunted house".to_string()),
|
||||
image_inputs: Some(ImageListRef::MediaFileTokens(image_tokens.clone())),
|
||||
aspect_ratio: Some(ar),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
|
||||
+4
-4
@@ -9,13 +9,13 @@ use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_5_lit
|
||||
use artcraft_client::endpoints::generate::image::multi_function::bytedance_seedream_5_lite_multi_function_image_gen_image::bytedance_seedream_5_lite_multi_function_image_gen;
|
||||
|
||||
pub async fn execute_artcraft_seedream_5_lite(
|
||||
plan: &PlanArtcraftSeedream5Lite<'_>,
|
||||
plan: &PlanArtcraftSeedream5Lite,
|
||||
artcraft_client: &RouterArtcraftClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let request = BytedanceSeedream5LiteMultiFunctionImageGenRequest {
|
||||
uuid_idempotency_token: plan.idempotency_token.clone(),
|
||||
prompt: plan.prompt.map(|p| p.to_string()),
|
||||
image_media_tokens: plan.image_inputs.map(|tokens| tokens.to_owned()),
|
||||
prompt: plan.prompt.clone(),
|
||||
image_media_tokens: plan.image_inputs.clone(),
|
||||
num_images: Some(plan.num_images),
|
||||
image_size: plan.image_size,
|
||||
max_images: None,
|
||||
@@ -49,7 +49,7 @@ mod tests {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
quality: None,
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night"),
|
||||
prompt: Some("a corgi walking through a cyberpunk city at night".to_string()),
|
||||
..base_seedream_5_lite_image_request()
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -13,13 +13,13 @@ use fal_client::requests::webhook::image::text::enqueue_flux_1_dev_text_to_image
|
||||
};
|
||||
|
||||
pub async fn execute_fal_flux_1_dev(
|
||||
plan: &PlanFalFlux1Dev<'_>,
|
||||
plan: &PlanFalFlux1Dev,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
|
||||
// Image-to-image mode
|
||||
let args = Flux1DevEditImageArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
image_url: image_url.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
@@ -31,7 +31,7 @@ pub async fn execute_fal_flux_1_dev(
|
||||
} else {
|
||||
// Text-to-image mode
|
||||
let args = Flux1DevArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: plan.num_images.to_t2i(),
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ use fal_client::requests::webhook::image::text::enqueue_flux_1_schnell_text_to_i
|
||||
};
|
||||
|
||||
pub async fn execute_fal_flux_1_schnell(
|
||||
plan: &PlanFalFlux1Schnell<'_>,
|
||||
plan: &PlanFalFlux1Schnell,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if let Some(image_url) = &plan.maybe_image_url {
|
||||
@@ -31,7 +31,7 @@ pub async fn execute_fal_flux_1_schnell(
|
||||
} else {
|
||||
// Text-to-image mode
|
||||
let args = Flux1SchnellArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
aspect_ratio: plan.t2i_aspect_ratio,
|
||||
num_images: plan.num_images.to_t2i(),
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
|
||||
+2
-2
@@ -10,11 +10,11 @@ use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_text_to_imag
|
||||
};
|
||||
|
||||
pub async fn execute_fal_flux_pro_1p1(
|
||||
plan: &PlanFalFluxPro11<'_>,
|
||||
plan: &PlanFalFluxPro11,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let args = FluxPro11Args {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: plan.num_images.to_fal(),
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
|
||||
+2
-2
@@ -10,11 +10,11 @@ use fal_client::requests::webhook::image::text::enqueue_flux_pro_11_ultra_text_t
|
||||
};
|
||||
|
||||
pub async fn execute_fal_flux_pro_1p1_ultra(
|
||||
plan: &PlanFalFluxPro11Ultra<'_>,
|
||||
plan: &PlanFalFluxPro11Ultra,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let args = FluxPro11UltraArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
aspect_ratio: plan.aspect_ratio,
|
||||
num_images: plan.num_images.to_fal(),
|
||||
webhook_url: fal_client.webhook_url.as_str(),
|
||||
|
||||
+9
-9
@@ -13,12 +13,12 @@ use fal_client::requests::webhook::image::text::enqueue_gpt_image_1_text_to_imag
|
||||
};
|
||||
|
||||
pub async fn execute_fal_gpt_image_1(
|
||||
plan: &PlanFalGptImage1<'_>,
|
||||
plan: &PlanFalGptImage1,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let args = EnqueueGptImage1TextToImageArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
num_images: plan.num_images.to_t2i(),
|
||||
image_size: plan.image_size.map(|s| s.to_t2i()),
|
||||
quality: Some(plan.quality.to_t2i()),
|
||||
@@ -32,7 +32,7 @@ pub async fn execute_fal_gpt_image_1(
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
} else {
|
||||
let args = EnqueueGptImage1EditImageArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
image_urls: plan.image_urls.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
mask_image_url: None,
|
||||
@@ -67,11 +67,11 @@ mod tests {
|
||||
use crate::generate::generate_image::image_generation_plan::ImageGenerationPlan;
|
||||
use crate::test_helpers::get_fal_client;
|
||||
|
||||
fn base_fal_request() -> GenerateImageRequest<'static> {
|
||||
fn base_fal_request() -> GenerateImageRequest {
|
||||
GenerateImageRequest {
|
||||
model: CommonImageModel::GptImage1,
|
||||
provider: Provider::Fal,
|
||||
prompt: Some("a cat in space"),
|
||||
prompt: Some("a cat in space".to_string()),
|
||||
image_inputs: None,
|
||||
resolution: None,
|
||||
aspect_ratio: None,
|
||||
@@ -106,7 +106,7 @@ mod tests {
|
||||
fn build_edit_image_plan_smoke() {
|
||||
let urls = vec!["https://example.com/img.jpg".to_string()];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::Urls(&urls)),
|
||||
image_inputs: Some(ImageListRef::Urls(urls.clone())),
|
||||
..base_fal_request()
|
||||
};
|
||||
let plan = request.build().expect("plan should build");
|
||||
@@ -123,7 +123,7 @@ mod tests {
|
||||
let request = GenerateImageRequest {
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("a horse walking through a cyberpunk city at night"),
|
||||
prompt: Some("a horse walking through a cyberpunk city at night".to_string()),
|
||||
..base_fal_request()
|
||||
};
|
||||
|
||||
@@ -143,10 +143,10 @@ mod tests {
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
];
|
||||
let request = GenerateImageRequest {
|
||||
image_inputs: Some(ImageListRef::Urls(&urls)),
|
||||
image_inputs: Some(ImageListRef::Urls(urls.clone())),
|
||||
aspect_ratio: Some(CommonAspectRatio::Square),
|
||||
image_batch_count: Some(1),
|
||||
prompt: Some("change the background to a desert"),
|
||||
prompt: Some("change the background to a desert".to_string()),
|
||||
..base_fal_request()
|
||||
};
|
||||
|
||||
|
||||
+3
-3
@@ -13,12 +13,12 @@ use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_im
|
||||
};
|
||||
|
||||
pub async fn execute_fal_gpt_image_1p5(
|
||||
plan: &PlanFalGptImage1p5<'_>,
|
||||
plan: &PlanFalGptImage1p5,
|
||||
fal_client: &RouterFalClient,
|
||||
) -> Result<GenerateImageResponse, ArtcraftRouterError> {
|
||||
let webhook_response = if plan.image_urls.is_empty() {
|
||||
let args = EnqueueGptImage1p5TextToImageArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
num_images: plan.num_images.to_t2i(),
|
||||
image_size: plan.image_size.map(|s| s.to_t2i()),
|
||||
background: None,
|
||||
@@ -32,7 +32,7 @@ pub async fn execute_fal_gpt_image_1p5(
|
||||
.map_err(|e| ArtcraftRouterError::Provider(ProviderError::Fal(e)))?
|
||||
} else {
|
||||
let args = EnqueueGptImage1p5EditImageArgs {
|
||||
prompt: plan.prompt.unwrap_or(""),
|
||||
prompt: plan.prompt.as_deref().unwrap_or(""),
|
||||
image_urls: plan.image_urls.clone(),
|
||||
num_images: plan.num_images.to_edit(),
|
||||
mask_image_url: None,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user