mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 01:12:40 -04:00
Compare commits
105
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1795e2b94 | ||
|
|
9de8fb8979 | ||
|
|
a61fcd50ed | ||
|
|
c16db8793c | ||
|
|
b3311d1574 | ||
|
|
35add74498 | ||
|
|
d99da56df0 | ||
|
|
3e4f998599 | ||
|
|
3bccac5b2e | ||
|
|
aa9e862ff2 | ||
|
|
db871126df | ||
|
|
aaff33955e | ||
|
|
ad5ae7968f | ||
|
|
fef83ad271 | ||
|
|
864cfb5faf | ||
|
|
2aed062790 | ||
|
|
4d5f8e9c21 | ||
|
|
98f9504bdd | ||
|
|
7ff72d2ecc | ||
|
|
10eb062b6f | ||
|
|
3b225a7c6d | ||
|
|
61a0880c80 | ||
|
|
c5c68d301a | ||
|
|
81de7838d3 | ||
|
|
9653e9082e | ||
|
|
ee6c064f4f | ||
|
|
630b132ae7 | ||
|
|
5742e638ed | ||
|
|
dc33a99d75 | ||
|
|
f6c5ea94eb | ||
|
|
123fe83a3d | ||
|
|
654f9164d3 | ||
|
|
539824564b | ||
|
|
fef420129d | ||
|
|
39dd4dee9a | ||
|
|
02ac2319a1 | ||
|
|
6af1493c1c | ||
|
|
7c7e050d02 | ||
|
|
fb93d297d3 | ||
|
|
38d0642904 | ||
|
|
2f9ffd4575 | ||
|
|
e8b19148fa | ||
|
|
31e912c4be | ||
|
|
ceb9d402cf | ||
|
|
ac2e728000 | ||
|
|
ef255f4ee6 | ||
|
|
0c11a95669 | ||
|
|
9191fe337a | ||
|
|
eb3b733852 | ||
|
|
7d8d9462d1 | ||
|
|
660cf9f0ac | ||
|
|
bf373b1d5c | ||
|
|
914c2532ec | ||
|
|
c7341d8f63 | ||
|
|
92e99cfcb6 | ||
|
|
754cdb8a6c | ||
|
|
7b9db101fe | ||
|
|
e94987e5a4 | ||
|
|
47d83be3fb | ||
|
|
fa4b57980c | ||
|
|
87d060e68c | ||
|
|
fc7651071c | ||
|
|
7a0bfa35e6 | ||
|
|
0b4e288a3e | ||
|
|
a0b6047a78 | ||
|
|
ebe1287182 | ||
|
|
de538ba94c | ||
|
|
29a127aac0 | ||
|
|
126f874c90 | ||
|
|
65e72f8c24 | ||
|
|
fafb485a60 | ||
|
|
1e20f2bfff | ||
|
|
e30b4366ce | ||
|
|
d27d5cec75 | ||
|
|
749124f834 | ||
|
|
65b18b9b51 | ||
|
|
6eb85010d6 | ||
|
|
855e169b44 | ||
|
|
ea2f5106df | ||
|
|
0de867ad13 | ||
|
|
a1cb1b2629 | ||
|
|
8da68b009c | ||
|
|
e4bc031fbb | ||
|
|
d937a7f78f | ||
|
|
fef96c3835 | ||
|
|
b9e793098c | ||
|
|
222f3e958d | ||
|
|
4ab06030dc | ||
|
|
189c5afc80 | ||
|
|
2818831a66 | ||
|
|
8497a8c83a | ||
|
|
5fff16ee22 | ||
|
|
1c3810e5a5 | ||
|
|
5206a1d3e5 | ||
|
|
10f8297c1f | ||
|
|
edb771ccfb | ||
|
|
5979002f7e | ||
|
|
74e30c4ffb | ||
|
|
4061f00874 | ||
|
|
633003c43b | ||
|
|
957db2d07f | ||
|
|
b82a4e1599 | ||
|
|
bee790b952 | ||
|
|
a2d57e3685 | ||
|
|
9104faf806 |
@@ -19,9 +19,18 @@ Handlers in `storyteller_web` follow this structure:
|
||||
|
||||
### Session/Auth
|
||||
|
||||
- `require_user_session_using_connection()` reuses an open DB connection (preferred)
|
||||
- `require_user_session()` acquires its own connection (deprecated)
|
||||
- `require_moderator()` for admin-only endpoints
|
||||
Helpers live in `storyteller_web` under `http_server/web_utils/user_session/`. Each takes a
|
||||
generic `mysql_executor: E` (any `sqlx::Executor`), so whether it reuses a connection or acquires
|
||||
its own depends on the **argument you pass**:
|
||||
|
||||
- `require_user_session(http_request, session_checker, mysql_executor)` — require a logged-in user.
|
||||
- `require_user_session_extended(...)` — same, with extra session detail.
|
||||
- `require_moderator(http_request, session_checker, mysql_executor)` — admin-only endpoints.
|
||||
|
||||
PREFER passing an already-open connection (`&mut *conn`) so the helper reuses it. Passing
|
||||
`&server_state.mysql_pool` makes it acquire a fresh connection — avoid that when the handler
|
||||
already holds one (the double-acquire adds pool pressure; this crate has had pool-timeout
|
||||
incidents). There is no `*_using_connection` variant — the reuse/acquire choice is the argument.
|
||||
|
||||
### API Type Definitions
|
||||
|
||||
|
||||
@@ -7,10 +7,11 @@
|
||||
- `#[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; 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.
|
||||
- SQLx: MySQL queries belong in the `mysql_queries` crate. Do NOT write `sqlx::query!` /
|
||||
`sqlx::query()` in application/service crates (e.g. `storyteller-web`, which has zero direct
|
||||
SQLx queries) — add or reuse a function in `mysql_queries` and call it. The compile-time
|
||||
`query!` / `query_as!` macro conventions for that crate are documented in
|
||||
`crates/schema/database/mysql_queries/CLAUDE.md`.
|
||||
- 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)`
|
||||
|
||||
@@ -38,6 +38,7 @@ script/socat_dev_database_connect.sh
|
||||
venv/
|
||||
|
||||
# LLM Testing
|
||||
/external/
|
||||
/prompts/
|
||||
/reports/
|
||||
/requests/
|
||||
@@ -102,3 +103,4 @@ windows/sqlite/sqlite3.lib
|
||||
# ArtCraft Windows DLLs
|
||||
crates/desktop/src-tauri/*.dll
|
||||
windows_dlls/
|
||||
|
||||
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: ApiKeyToken`,\n LEFT(api_key, 20) as `api_key_prefix!`,\n name,\n maybe_description,\n owner_user_token as `owner_user_token: UserToken`,\n ip_address_creation,\n ip_address_update,\n created_at,\n updated_at,\n maybe_deleted_at\nFROM api_keys\nWHERE owner_user_token = ?\nORDER BY id DESC\nLIMIT ? OFFSET ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: ApiKeyToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "api_key_prefix!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 2048
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "owner_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": "ip_address_creation",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "ip_address_update",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_deleted_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "097b8a8d02734c8c8e31dbb8a3bf4ace970693a2a0df2f05a31caf86b4d3d0ab"
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT token AS `token: UserToken`\nFROM users\nWHERE email_address = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "0d2eadc64240b9ce5348b3b238b0ae6229df21697c952b08e930d03f97414cf4"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO uploaded_videos\nSET\n token = ?,\n sha1_checksum = ?,\n filesize_bytes = ?,\n maybe_filename = ?,\n maybe_width = ?,\n maybe_height = ?,\n maybe_resolution = ?,\n maybe_detected_model_family = ?,\n maybe_detected_model_type = ?,\n maybe_report = ?,\n upload_ip_address = ?,\n created_at = NOW()\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 11
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "14e63e59f7d550181126151029bcd17d08278196ba0ee4d32450c2567b1da26e"
|
||||
}
|
||||
+14
-3
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n jobs.maybe_external_third_party_id as `order_id`,\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n jobs.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_wallet_ledger_entry_token as `maybe_wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n jobs.platform_type as `maybe_platform_type: enums::common::platform_type::PlatformType`\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\n AND jobs.job_type = ?\n AND jobs.status NOT IN ('complete_success', 'complete_failure')\n AND jobs.maybe_external_third_party_id IS NOT NULL\n\nLIMIT 25000\n ",
|
||||
"query": "\nSELECT\n jobs.token as `job_token: tokens::tokens::generic_inference_jobs::InferenceJobToken`,\n jobs.maybe_external_third_party_id as `order_id`,\n jobs.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n jobs.maybe_creator_anonymous_visitor_token as `maybe_creator_anonymous_visitor_token: tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken`,\n jobs.creator_ip_address,\n jobs.creator_set_visibility as `creator_set_visibility: enums::common::visibility::Visibility`,\n jobs.maybe_prompt_token as `maybe_prompt_token: tokens::tokens::prompts::PromptToken`,\n jobs.maybe_wallet_ledger_entry_token as `maybe_wallet_ledger_entry_token: tokens::tokens::wallet_ledger_entries::WalletLedgerEntryToken`,\n jobs.platform_type as `maybe_platform_type: enums::common::platform_type::PlatformType`,\n jobs.created_at\n\nFROM generic_inference_jobs as jobs\n\nWHERE jobs.maybe_external_third_party = ?\n AND jobs.job_type = ?\n AND jobs.status NOT IN ('complete_success', 'complete_failure')\n AND jobs.maybe_external_third_party_id IS NOT NULL\n\nLIMIT 25000\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -92,6 +92,16 @@
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
@@ -106,8 +116,9 @@
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "ddbbd3886c00268edd3993508f635549fc8ddc877bb059fd58f14b8c07586fc9"
|
||||
"hash": "1703bc63ce65d98cbcad81d515cbcf211a943c533e38e887d2c12713c78b687d"
|
||||
}
|
||||
+18
-7
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n p.token as `token: tokens::tokens::prompts::PromptToken`,\n\n p.prompt_type as `prompt_type: enums::by_table::prompts::prompt_type::PromptType`,\n\n p.maybe_model_type as `maybe_model_type: enums::common::generation::common_model_type::CommonModelType`,\n p.maybe_generation_provider as `maybe_generation_provider: enums::common::generation_provider::GenerationProvider`,\n\n p.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n\n p.maybe_positive_prompt,\n p.maybe_negative_prompt,\n\n p.maybe_generation_mode as `maybe_generation_mode: enums::common::generation::common_generation_mode::CommonGenerationMode`,\n p.maybe_aspect_ratio as `maybe_aspect_ratio: enums::common::generation::common_aspect_ratio::CommonAspectRatio`,\n p.maybe_resolution as `maybe_resolution: enums::common::generation::common_resolution::CommonResolution`,\n p.maybe_batch_count as `maybe_batch_count: u8`,\n p.maybe_generate_audio as `maybe_generate_audio: bool`,\n p.maybe_duration_seconds as `maybe_duration_seconds: u32`,\n\n p.maybe_other_args,\n\n p.creator_ip_address,\n p.created_at\n\nFROM prompts as p\nWHERE\n p.token = ?\n ",
|
||||
"query": "\nSELECT\n p.token as `token: tokens::tokens::prompts::PromptToken`,\n\n p.prompt_type as `prompt_type: enums::by_table::prompts::prompt_type::PromptType`,\n\n p.maybe_model_type as `maybe_model_type: enums::common::generation::common_model_type::CommonModelType`,\n p.maybe_generation_provider as `maybe_generation_provider: enums::common::generation_provider::GenerationProvider`,\n\n p.maybe_creator_user_token as `maybe_creator_user_token: tokens::tokens::users::UserToken`,\n\n p.maybe_positive_prompt,\n p.maybe_negative_prompt,\n\n p.maybe_generation_mode as `maybe_generation_mode: enums::common::generation::common_generation_mode::CommonGenerationMode`,\n p.maybe_aspect_ratio as `maybe_aspect_ratio: enums::common::generation::common_aspect_ratio::CommonAspectRatio`,\n p.maybe_resolution as `maybe_resolution: enums::common::generation::common_resolution::CommonResolution`,\n p.maybe_bitrate as `maybe_bitrate: enums::common::generation::common_bitrate::CommonBitrate`,\n p.maybe_batch_count as `maybe_batch_count: u8`,\n p.maybe_generate_audio as `maybe_generate_audio: bool`,\n p.maybe_duration_seconds as `maybe_duration_seconds: u32`,\n\n p.maybe_other_args,\n\n p.creator_ip_address,\n p.created_at\n\nFROM prompts as p\nWHERE\n p.token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
@@ -105,6 +105,16 @@
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_bitrate: enums::common::generation::common_bitrate::CommonBitrate",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_batch_count: u8",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -114,7 +124,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"ordinal": 12,
|
||||
"name": "maybe_generate_audio: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
@@ -124,7 +134,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"ordinal": 13,
|
||||
"name": "maybe_duration_seconds: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
@@ -134,7 +144,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"ordinal": 14,
|
||||
"name": "maybe_other_args",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
@@ -144,7 +154,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"ordinal": 15,
|
||||
"name": "creator_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
@@ -154,7 +164,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"ordinal": 16,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
@@ -182,9 +192,10 @@
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "4a50b7bf06061f05e769a41e547cb927bd2b3a6f67c8ceb37883b744237ba34c"
|
||||
"hash": "1b69f192c3bb51bd249f97d37f209ae271d498bb46c978351b2c1d8759338bab"
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n payment_occurred_at AS `payment_occurred_at!: DateTime<Utc>`,\n amount_usd_cents,\n event_type AS `event_type!: PaymentEventType`,\n maybe_credits_granted AS `maybe_credits_granted: u32`\nFROM user_spend_events\nWHERE maybe_user_token = ?\n AND payments_namespace = ?\n AND is_production = TRUE\nORDER BY payment_occurred_at ASC\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "payment_occurred_at!: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "amount_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "event_type!: PaymentEventType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_credits_granted: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "1c997019b7ed18059bc08d857918c3ce3f5e6ed56d358894cbc496d345566519"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n stripe_customer_id AS `stripe_customer_id!`,\n user_token AS `user_token!: UserToken`\nFROM user_stripe_customer_links\nWHERE payments_namespace = 'artcraft'\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "stripe_customer_id!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "user_token!: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "2d49cfbafc29bce35f3e03135054c7521fb0182663aefa971245f91f33a6fc49"
|
||||
}
|
||||
+2
-2
@@ -48,7 +48,7 @@
|
||||
"name": "maybe_entity_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
@@ -132,7 +132,7 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO user_spend_events\nSET\n token = ?,\n payments_namespace = ?,\n maybe_user_token = ?,\n event_type = ?,\n amount_usd_cents = ?,\n maybe_credits_granted = ?,\n maybe_user_subscription_token = ?,\n maybe_wallet_ledger_entry_token = ?,\n payment_source = ?,\n source_object_id = ?,\n maybe_stripe_customer_id = ?,\n maybe_stripe_invoice_id = ?,\n maybe_stripe_payment_intent_id = ?,\n maybe_stripe_charge_id = ?,\n maybe_stripe_event_id = ?,\n is_production = ?,\n payment_occurred_at = ?\nON DUPLICATE KEY UPDATE token = token\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 17
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3e5a71cedc31cc656177c8331c2de1d1d2d2eb310aabeb7e6238943b073f6998"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE uploaded_video_notes\nSET\n maybe_filename = ?,\n maybe_reported_model_type = ?,\n maybe_reported_model_name = ?,\n maybe_website = ?,\n maybe_other_website = ?,\n maybe_comments = ?,\n maybe_email_address = ?,\n can_share_report = ?,\n was_scammed = ?,\n maybe_comment_update_ip_address = ?,\n version = version + 1\nWHERE token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 11
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "4036dfd9d4e9e77e557776468c14b75f49170de1e55a1b9b9e83446a0c356e74"
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: UploadedVideoToken`,\n sha1_checksum,\n filesize_bytes as `filesize_bytes: u32`,\n maybe_width as `maybe_width: u32`,\n maybe_height as `maybe_height: u32`,\n maybe_resolution,\n maybe_detected_model_family as `maybe_detected_model_family: UploadedVideoDetectedModelFamily`,\n maybe_detected_model_type as `maybe_detected_model_type: UploadedVideoDetectedModelType`,\n maybe_report,\n upload_ip_address,\n maybe_updated_ip_address,\n created_at,\n updated_at\nFROM uploaded_videos\nWHERE sha1_checksum = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: UploadedVideoToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "sha1_checksum",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "filesize_bytes: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_width: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_height: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_resolution",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 48
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_detected_model_family: UploadedVideoDetectedModelFamily",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_detected_model_type: UploadedVideoDetectedModelType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_report",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 67108860
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "upload_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_updated_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"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,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "408c893a0ced805de4c3c6cfdf0c6a4bcc93dd1eaf98382522eae46e72eb3aa5"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO user_spend_summaries\nSET\n payments_namespace = ?,\n user_token = ?,\n lifetime_gross_spend_usd_cents = ?,\n lifetime_subscription_spend_usd_cents = ?,\n lifetime_credits_spend_usd_cents = ?,\n lifetime_refund_usd_cents = ?,\n lifetime_net_spend_usd_cents = ?,\n lifetime_payment_count = ?,\n lifetime_refund_count = ?,\n first_payment_at = ?,\n first_spend_usd_cents = ?,\n last_payment_at = ?,\n last_spend_usd_cents = ?,\n days_since_first_payment = ?,\n days_since_last_payment = ?,\n net_spend_7d_usd_cents = ?,\n net_spend_prev_7d_usd_cents = ?,\n net_spend_14d_usd_cents = ?,\n net_spend_prev_14d_usd_cents = ?,\n net_spend_30d_usd_cents = ?,\n net_spend_prev_30d_usd_cents = ?,\n net_spend_60d_usd_cents = ?,\n net_spend_90d_usd_cents = ?,\n net_spend_this_year_usd_cents = ?,\n avg_weekly_net_spend_4w_usd_cents = ?,\n avg_weekly_net_spend_12w_usd_cents = ?,\n active_weeks_in_last_4 = ?,\n active_weeks_in_last_8 = ?,\n active_weeks_in_last_12 = ?,\n active_weeks_in_last_24 = ?,\n active_weeks_in_last_52 = ?,\n consecutive_active_weeks = ?,\n consecutive_inactive_weeks = ?,\n weeks_since_last_spend = ?,\n is_active_subscriber = ?,\n maybe_subscription_interval = ?,\n maybe_reengagement_score = ?\nON DUPLICATE KEY UPDATE\n lifetime_gross_spend_usd_cents = VALUES(lifetime_gross_spend_usd_cents),\n lifetime_subscription_spend_usd_cents = VALUES(lifetime_subscription_spend_usd_cents),\n lifetime_credits_spend_usd_cents = VALUES(lifetime_credits_spend_usd_cents),\n lifetime_refund_usd_cents = VALUES(lifetime_refund_usd_cents),\n lifetime_net_spend_usd_cents = VALUES(lifetime_net_spend_usd_cents),\n lifetime_payment_count = VALUES(lifetime_payment_count),\n lifetime_refund_count = VALUES(lifetime_refund_count),\n first_payment_at = VALUES(first_payment_at),\n first_spend_usd_cents = VALUES(first_spend_usd_cents),\n last_payment_at = VALUES(last_payment_at),\n last_spend_usd_cents = VALUES(last_spend_usd_cents),\n days_since_first_payment = VALUES(days_since_first_payment),\n days_since_last_payment = VALUES(days_since_last_payment),\n net_spend_7d_usd_cents = VALUES(net_spend_7d_usd_cents),\n net_spend_prev_7d_usd_cents = VALUES(net_spend_prev_7d_usd_cents),\n net_spend_14d_usd_cents = VALUES(net_spend_14d_usd_cents),\n net_spend_prev_14d_usd_cents = VALUES(net_spend_prev_14d_usd_cents),\n net_spend_30d_usd_cents = VALUES(net_spend_30d_usd_cents),\n net_spend_prev_30d_usd_cents = VALUES(net_spend_prev_30d_usd_cents),\n net_spend_60d_usd_cents = VALUES(net_spend_60d_usd_cents),\n net_spend_90d_usd_cents = VALUES(net_spend_90d_usd_cents),\n net_spend_this_year_usd_cents = VALUES(net_spend_this_year_usd_cents),\n avg_weekly_net_spend_4w_usd_cents = VALUES(avg_weekly_net_spend_4w_usd_cents),\n avg_weekly_net_spend_12w_usd_cents = VALUES(avg_weekly_net_spend_12w_usd_cents),\n active_weeks_in_last_4 = VALUES(active_weeks_in_last_4),\n active_weeks_in_last_8 = VALUES(active_weeks_in_last_8),\n active_weeks_in_last_12 = VALUES(active_weeks_in_last_12),\n active_weeks_in_last_24 = VALUES(active_weeks_in_last_24),\n active_weeks_in_last_52 = VALUES(active_weeks_in_last_52),\n consecutive_active_weeks = VALUES(consecutive_active_weeks),\n consecutive_inactive_weeks = VALUES(consecutive_inactive_weeks),\n weeks_since_last_spend = VALUES(weeks_since_last_spend),\n is_active_subscriber = VALUES(is_active_subscriber),\n maybe_subscription_interval = VALUES(maybe_subscription_interval),\n maybe_reengagement_score = VALUES(maybe_reengagement_score)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 37
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "41cf2f8d64aa36fb930cc1bf8a46d37e0987b743438b44e1c55859fc6b6ee6a0"
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT status as `status: enums::common::job_status_plus::JobStatusPlus`\nFROM generic_inference_jobs\nWHERE token = ?\nFOR UPDATE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "status: enums::common::job_status_plus::JobStatusPlus",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 76
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "489c6ef00507330fedbaf2b6e024ecf409a920efd85f75456829a8ed412d9bc3"
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n s.user_token AS `user_token!: UserToken`,\n u.username AS `username!`,\n u.display_name AS `display_name!`,\n u.email_gravatar_hash AS `email_gravatar_hash!`,\n s.maybe_reengagement_score AS `reengagement_score!: u32`,\n s.lifetime_net_spend_usd_cents,\n s.last_payment_at AS `maybe_last_payment_at: DateTime<Utc>`,\n s.days_since_last_payment AS `maybe_days_since_last_payment: u32`,\n s.weeks_since_last_spend AS `maybe_weeks_since_last_spend: u32`,\n s.is_active_subscriber AS `is_active_subscriber: bool`\nFROM user_spend_summaries s\nJOIN users u ON u.token = s.user_token\nWHERE s.payments_namespace = ?\n AND s.maybe_reengagement_score > 0\nORDER BY s.maybe_reengagement_score DESC, s.id ASC\nLIMIT ? OFFSET ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "user_token!: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "display_name!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email_gravatar_hash!",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "reengagement_score!: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "lifetime_net_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_last_payment_at: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_days_since_last_payment: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_weeks_since_last_spend: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "is_active_subscriber: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "5b1954301fc16703a04e2f63fd85c28bf2283bc1ecb5624107686dad0dfe364f"
|
||||
}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = ?,\n maybe_model_token = ?,\n\n maybe_input_source_token = ?,\n maybe_input_source_token_type = ?,\n\n maybe_download_url = ?,\n maybe_cover_image_media_file_token = ?,\n\n maybe_prompt_token = ?,\n maybe_wallet_ledger_entry_token = ?,\n\n maybe_raw_inference_text = ?,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n platform_type = ?,\n\n priority_level = ?,\n is_keepalive_required = ?,\n max_duration_seconds = ?,\n is_debug_request = ?,\n\n maybe_routing_tag = ?,\n\n maybe_debug_log_event_token = ?,\n\n frontend_failure_category = ?,\n failure_reason = ?,\n\n status = ?\n ",
|
||||
"query": "\nINSERT INTO generic_inference_jobs\nSET\n token = ?,\n uuid_idempotency_token = ?,\n\n job_type = ?,\n\n maybe_external_third_party = ?,\n maybe_external_third_party_id = ?,\n\n product_category = ?,\n inference_category = ?,\n\n maybe_model_type = ?,\n maybe_model_token = ?,\n\n maybe_input_source_token = ?,\n maybe_input_source_token_type = ?,\n\n maybe_download_url = ?,\n maybe_cover_image_media_file_token = ?,\n\n maybe_prompt_token = ?,\n maybe_wallet_ledger_entry_token = ?,\n\n maybe_raw_inference_text = ?,\n\n maybe_inference_args = ?,\n\n maybe_creator_user_token = ?,\n maybe_creator_anonymous_visitor_token = ?,\n creator_ip_address = ?,\n creator_set_visibility = ?,\n\n platform_type = ?,\n\n maybe_external_third_party_cost_credits = ?,\n maybe_external_third_party_cost_usd_cents = ?,\n maybe_system_cost_credits = ?,\n maybe_system_cost_usd_cents = ?,\n\n priority_level = ?,\n is_keepalive_required = ?,\n max_duration_seconds = ?,\n is_debug_request = ?,\n\n maybe_routing_tag = ?,\n\n maybe_debug_log_event_token = ?,\n\n frontend_failure_category = ?,\n failure_reason = ?,\n\n status = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 31
|
||||
"Right": 35
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "108731dc51d915ebb8b6ed391c5bae4c41ae6f2c813be92c07d8a10ca401db53"
|
||||
"hash": "5c67c3dd7f8bb6771c6b058a8cf6ffcc66b2be2bea41bd988070713173982dab"
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: UploadedVideoToken`,\n sha1_checksum,\n filesize_bytes as `filesize_bytes: u32`,\n maybe_width as `maybe_width: u32`,\n maybe_height as `maybe_height: u32`,\n maybe_resolution,\n maybe_detected_model_family as `maybe_detected_model_family: UploadedVideoDetectedModelFamily`,\n maybe_detected_model_type as `maybe_detected_model_type: UploadedVideoDetectedModelType`,\n maybe_report,\n upload_ip_address,\n maybe_updated_ip_address,\n created_at,\n updated_at\nFROM uploaded_videos\nWHERE token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: UploadedVideoToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "sha1_checksum",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "filesize_bytes: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_width: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_height: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_resolution",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 48
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "maybe_detected_model_family: UploadedVideoDetectedModelFamily",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "maybe_detected_model_type: UploadedVideoDetectedModelType",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_report",
|
||||
"type_info": {
|
||||
"type": "Blob",
|
||||
"flags": "BLOB | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 67108860
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "upload_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_updated_ip_address",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"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,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "73d70c5ee77f5059801d324610a80b3f52da3ddec2e89f63e8f41fc68ff0aaec"
|
||||
}
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO prompts\nSET\n token = ?,\n prompt_type = ?,\n\n maybe_creator_user_token = ?,\n \n maybe_model_type = ?,\n maybe_generation_provider = ?,\n\n maybe_positive_prompt = ?,\n maybe_negative_prompt = ?,\n\n maybe_generation_mode = ?,\n maybe_aspect_ratio = ?,\n maybe_resolution = ?,\n maybe_batch_count = ?,\n maybe_generate_audio = ?,\n maybe_duration_seconds = ?,\n\n maybe_other_args = ?,\n\n creator_ip_address = ?\n ",
|
||||
"query": "\nINSERT INTO prompts\nSET\n token = ?,\n prompt_type = ?,\n\n maybe_creator_user_token = ?,\n \n maybe_model_type = ?,\n maybe_generation_provider = ?,\n\n maybe_positive_prompt = ?,\n maybe_negative_prompt = ?,\n\n maybe_generation_mode = ?,\n maybe_aspect_ratio = ?,\n maybe_resolution = ?,\n maybe_bitrate = ?,\n maybe_batch_count = ?,\n maybe_generate_audio = ?,\n maybe_duration_seconds = ?,\n\n maybe_other_args = ?,\n\n creator_ip_address = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 15
|
||||
"Right": 16
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ccf92ebe0328d3019ac6b01be4b442df706a183362fa7e5c331f5a4bf1a74a00"
|
||||
"hash": "7519c1dcc2946f4c31a8aad995bbb66a5519f6ee571797a6c0f597c9e57986a7"
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n payments_namespace AS `payments_namespace!: PaymentsNamespace`,\n user_token AS `user_token!: UserToken`,\n spend_date AS `spend_date!: NaiveDate`,\n subscription_spend_usd_cents,\n credits_spend_usd_cents,\n gross_spend_usd_cents,\n refund_usd_cents,\n net_spend_usd_cents,\n payment_count,\n credits_granted,\n created_at AS `created_at: DateTime<Utc>`,\n updated_at AS `updated_at: DateTime<Utc>`\nFROM user_daily_spends\nWHERE user_token = ?\n AND payments_namespace = ?\nORDER BY spend_date DESC\nLIMIT ? OFFSET ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "payments_namespace!: PaymentsNamespace",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "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": "spend_date!: NaiveDate",
|
||||
"type_info": {
|
||||
"type": "Date",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "subscription_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "credits_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "gross_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "refund_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "net_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "payment_count",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "credits_granted",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "updated_at: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "77dca63cffdd18957cbc4be6b7d982a2599a2c06df07f8e34501a032cd688ead"
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n maybe_user_token AS `user_token!: UserToken`,\n payments_namespace AS `payments_namespace!: PaymentsNamespace`,\n CAST(SUM(CASE WHEN amount_usd_cents > 0\n AND event_type IN ('subscription_initial', 'subscription_renewal', 'subscription_proration_upgrade')\n THEN amount_usd_cents ELSE 0 END) AS UNSIGNED) AS `subscription_spend_usd_cents!: u64`,\n CAST(SUM(CASE WHEN amount_usd_cents > 0 AND event_type = 'credit_pack_purchase'\n THEN amount_usd_cents ELSE 0 END) AS UNSIGNED) AS `credits_spend_usd_cents!: u64`,\n CAST(SUM(CASE WHEN amount_usd_cents > 0 THEN amount_usd_cents ELSE 0 END) AS UNSIGNED) AS `gross_spend_usd_cents!: u64`,\n CAST(SUM(CASE WHEN amount_usd_cents < 0 THEN -amount_usd_cents ELSE 0 END) AS UNSIGNED) AS `refund_usd_cents!: u64`,\n CAST(SUM(amount_usd_cents) AS SIGNED) AS `net_spend_usd_cents!: i64`,\n CAST(SUM(CASE WHEN amount_usd_cents > 0 THEN 1 ELSE 0 END) AS UNSIGNED) AS `payment_count!: u64`,\n CAST(COALESCE(SUM(maybe_credits_granted), 0) AS UNSIGNED) AS `credits_granted!: u64`\nFROM user_spend_events\nWHERE maybe_user_token IS NOT NULL\n AND is_production = TRUE\n AND payment_occurred_at >= ?\n AND payment_occurred_at < ?\nGROUP BY maybe_user_token, payments_namespace\nHAVING SUM(CASE WHEN amount_usd_cents <> 0 THEN 1 ELSE 0 END) > 0\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "user_token!: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "payments_namespace!: PaymentsNamespace",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "subscription_spend_usd_cents!: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNSIGNED | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "credits_spend_usd_cents!: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNSIGNED | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "gross_spend_usd_cents!: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNSIGNED | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "refund_usd_cents!: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNSIGNED | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "net_spend_usd_cents!: i64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "payment_count!: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "UNSIGNED | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "credits_granted!: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 21
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "83fc05a3594b379482745f851cd5a22786adde9968dd70490c91100cf21f8023"
|
||||
}
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n fmf.id as `membership_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\nORDER BY fmf.id DESC\nLIMIT ?\n ",
|
||||
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\nORDER BY mf.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "membership_id: u64",
|
||||
"name": "media_file_id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
@@ -263,5 +263,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "88673127b54626dc38d28693e709dfaeced9a05300a26b3f3c52a17f28171c9a"
|
||||
"hash": "8aca9bc976b980ea1a3b3052bc8a45dd4d8db55c20fd1aede2a4415cc1395cf3"
|
||||
}
|
||||
+2
-2
@@ -48,7 +48,7 @@
|
||||
"name": "maybe_entity_token",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
@@ -132,7 +132,7 @@
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT status as `status: enums::common::job_status_plus::JobStatusPlus`\nFROM generic_inference_jobs\nWHERE token = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "status: enums::common::job_status_plus::JobStatusPlus",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | ENUM",
|
||||
"char_set": 224,
|
||||
"max_size": 76
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9312694d1651235b06d1744d904b59f64d66449b8927c1606bb08bd5fdc15c7e"
|
||||
}
|
||||
+443
@@ -0,0 +1,443 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n payments_namespace AS `payments_namespace!: PaymentsNamespace`,\n user_token AS `user_token!: UserToken`,\n lifetime_gross_spend_usd_cents,\n lifetime_subscription_spend_usd_cents,\n lifetime_credits_spend_usd_cents,\n lifetime_refund_usd_cents,\n lifetime_net_spend_usd_cents,\n lifetime_payment_count,\n lifetime_refund_count,\n first_payment_at AS `maybe_first_payment_at: DateTime<Utc>`,\n first_spend_usd_cents,\n last_payment_at AS `maybe_last_payment_at: DateTime<Utc>`,\n last_spend_usd_cents,\n days_since_first_payment AS `maybe_days_since_first_payment: u32`,\n days_since_last_payment AS `maybe_days_since_last_payment: u32`,\n net_spend_7d_usd_cents,\n net_spend_prev_7d_usd_cents,\n net_spend_14d_usd_cents,\n net_spend_prev_14d_usd_cents,\n net_spend_30d_usd_cents,\n net_spend_prev_30d_usd_cents,\n net_spend_60d_usd_cents,\n net_spend_90d_usd_cents,\n net_spend_this_year_usd_cents,\n avg_weekly_net_spend_4w_usd_cents,\n avg_weekly_net_spend_12w_usd_cents,\n active_weeks_in_last_4,\n active_weeks_in_last_8,\n active_weeks_in_last_12,\n active_weeks_in_last_24,\n active_weeks_in_last_52,\n consecutive_active_weeks,\n consecutive_inactive_weeks,\n weeks_since_last_spend AS `maybe_weeks_since_last_spend: u32`,\n is_active_subscriber AS `is_active_subscriber: bool`,\n maybe_subscription_interval,\n maybe_reengagement_score AS `maybe_reengagement_score: u32`,\n created_at AS `created_at: DateTime<Utc>`,\n updated_at AS `updated_at: DateTime<Utc>`\nFROM user_spend_summaries\nWHERE payments_namespace = ?\n AND user_token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "payments_namespace!: PaymentsNamespace",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "user_token!: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "lifetime_gross_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "lifetime_subscription_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "lifetime_credits_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "lifetime_refund_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "lifetime_net_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "lifetime_payment_count",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "lifetime_refund_count",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_first_payment_at: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "first_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_last_payment_at: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "last_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_days_since_first_payment: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_days_since_last_payment: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "net_spend_7d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "net_spend_prev_7d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "net_spend_14d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 18,
|
||||
"name": "net_spend_prev_14d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 19,
|
||||
"name": "net_spend_30d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 20,
|
||||
"name": "net_spend_prev_30d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 21,
|
||||
"name": "net_spend_60d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 22,
|
||||
"name": "net_spend_90d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 23,
|
||||
"name": "net_spend_this_year_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 24,
|
||||
"name": "avg_weekly_net_spend_4w_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 25,
|
||||
"name": "avg_weekly_net_spend_12w_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 26,
|
||||
"name": "active_weeks_in_last_4",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 27,
|
||||
"name": "active_weeks_in_last_8",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 28,
|
||||
"name": "active_weeks_in_last_12",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 29,
|
||||
"name": "active_weeks_in_last_24",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 30,
|
||||
"name": "active_weeks_in_last_52",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 31,
|
||||
"name": "consecutive_active_weeks",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 32,
|
||||
"name": "consecutive_inactive_weeks",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 33,
|
||||
"name": "maybe_weeks_since_last_spend: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 34,
|
||||
"name": "is_active_subscriber: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 35,
|
||||
"name": "maybe_subscription_interval",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 36,
|
||||
"name": "maybe_reengagement_score: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 37,
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 38,
|
||||
"name": "updated_at: DateTime<Utc>",
|
||||
"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,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "9715e4dc2ec6e596fdb91ecc50a2b80f63494bbc0c9df6c0a57ede2360716869"
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: ApiKeyToken`,\n LEFT(api_key, 20) as `api_key_prefix!`,\n name,\n maybe_description,\n owner_user_token as `owner_user_token: UserToken`,\n ip_address_creation,\n ip_address_update,\n created_at,\n updated_at,\n maybe_deleted_at\nFROM api_keys\nWHERE api_key = ?\n AND maybe_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: ApiKeyToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "api_key_prefix!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 2048
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "owner_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": "ip_address_creation",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "ip_address_update",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_deleted_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "9b30c67842e4c408c9002fc116dc86705484268efcadb3100ef13244bcd8f922"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO uploaded_video_notes\nSET\n token = ?,\n uploaded_video_token = ?,\n maybe_filename = ?,\n maybe_reported_model_type = ?,\n maybe_reported_model_name = ?,\n maybe_website = ?,\n maybe_other_website = ?,\n maybe_comments = ?,\n maybe_email_address = ?,\n can_share_report = ?,\n was_scammed = ?,\n comment_create_ip_address = ?,\n created_at = NOW()\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 12
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b1f30a79864351a28a58cae43ee38ff12a8d64d9393b8f27590fe03b0ae3ab05"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE api_keys\nSET\n maybe_description = ?,\n ip_address_update = ?\nWHERE token = ?\n AND maybe_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b72d951fc8b60e3622bb981dd5cd66ce86c6c2d788c5606d2df80a3d07e588fe"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO user_daily_spends\nSET\n user_token = ?,\n payments_namespace = ?,\n spend_date = ?,\n subscription_spend_usd_cents = ?,\n credits_spend_usd_cents = ?,\n gross_spend_usd_cents = ?,\n refund_usd_cents = ?,\n net_spend_usd_cents = ?,\n payment_count = ?,\n credits_granted = ?\nON DUPLICATE KEY UPDATE\n subscription_spend_usd_cents = VALUES(subscription_spend_usd_cents),\n credits_spend_usd_cents = VALUES(credits_spend_usd_cents),\n gross_spend_usd_cents = VALUES(gross_spend_usd_cents),\n refund_usd_cents = VALUES(refund_usd_cents),\n net_spend_usd_cents = VALUES(net_spend_usd_cents),\n payment_count = VALUES(payment_count),\n credits_granted = VALUES(credits_granted)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 10
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b7a5fea76da70c03b8744c985940a5bcdabdb3316aa407c5ca883bca863ee633"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO api_keys\nSET\n token = ?,\n api_key = ?,\n name = ?,\n maybe_description = ?,\n owner_user_token = ?,\n ip_address_creation = ?,\n ip_address_update = ?,\n created_at = NOW()\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "bb39045ffcc9d3a9e150b32e7ff884dccbae60101deeaaad23ccd430b25bb5a4"
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n token as `token: ApiKeyToken`,\n LEFT(api_key, 20) as `api_key_prefix!`,\n name,\n maybe_description,\n owner_user_token as `owner_user_token: UserToken`,\n ip_address_creation,\n ip_address_update,\n created_at,\n updated_at,\n maybe_deleted_at\nFROM api_keys\nWHERE token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token: ApiKeyToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "api_key_prefix!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "name",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_description",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 2048
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "owner_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": "ip_address_creation",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "ip_address_update",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 160
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "created_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "updated_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP | ON_UPDATE_NOW",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "maybe_deleted_at",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "bb542b8a86cf2bf770daf29e09a92be981b307810241dd3cc1a7225a83f6824c"
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT DISTINCT maybe_user_token AS `user_token!: UserToken`\nFROM user_spend_events\nWHERE maybe_user_token IS NOT NULL\n AND is_production = TRUE\n AND payments_namespace = ?\n AND maybe_user_token > ?\nORDER BY maybe_user_token\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "user_token!: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": [
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "bcd07050d5557bf59d6b2691478763b5b067447d295b002f6e766209ea077f42"
|
||||
}
|
||||
+4
-4
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n fmf.id as `membership_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND fmf.id < ?\nORDER BY fmf.id DESC\nLIMIT ?\n ",
|
||||
"query": "\nSELECT\n mf.id as `media_file_id: u64`,\n fmf.created_at as `added_to_folder_at: DateTime<Utc>`,\n\n mf.token as `media_file_token: MediaFileToken`,\n mf.media_class as `media_class: MediaFileClass`,\n mf.media_type as `media_type: MediaFileType`,\n\n mf.maybe_batch_token as `maybe_batch_token: BatchGenerationToken`,\n\n mf.public_bucket_directory_hash,\n mf.maybe_public_bucket_prefix,\n mf.maybe_public_bucket_extension,\n\n cover.public_bucket_directory_hash as `maybe_cover_public_bucket_directory_hash?`,\n cover.maybe_public_bucket_prefix as `maybe_cover_public_bucket_prefix?`,\n cover.maybe_public_bucket_extension as `maybe_cover_public_bucket_extension?`,\n\n mf.creator_set_visibility as `creator_set_visibility: Visibility`,\n mf.is_user_upload as `is_user_upload: bool`,\n mf.is_intermediate_system_file as `is_intermediate_system_file: bool`,\n\n mf.maybe_title,\n mf.maybe_prompt_token as `maybe_prompt_token: PromptToken`,\n mf.maybe_origin_filename,\n\n mf.maybe_duration_millis,\n mf.maybe_frame_width,\n mf.maybe_frame_height,\n\n mf.created_at,\n mf.updated_at\nFROM folder_media_files fmf\nJOIN media_files mf\n ON mf.token = fmf.media_file_token\nLEFT JOIN media_files cover\n ON cover.token = mf.maybe_cover_image_media_file_token\nWHERE fmf.folder_token = ?\n AND mf.user_deleted_at IS NULL\n AND mf.mod_deleted_at IS NULL\n AND mf.id < ?\nORDER BY mf.id DESC\nLIMIT ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "membership_id: u64",
|
||||
"name": "media_file_id: u64",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | UNSIGNED | AUTO_INCREMENT",
|
||||
"flags": "NOT_NULL | PRIMARY_KEY | AUTO_INCREMENT",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
@@ -263,5 +263,5 @@
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d2a406bcf08f656fd602aa6ba1124a77d04ad874b15abafca300e8b381c7b211"
|
||||
"hash": "c0ba9f9aa3184b1cb3d714173b31e530e201a6c24afd29ad534cf7f3954c7316"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE api_keys\nSET maybe_deleted_at = NOW()\nWHERE token = ?\n AND maybe_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c28c716aa08601b208c727102680bfa91025f0e3958390ed9e27e521d38b5805"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nUPDATE uploaded_videos\nSET\n maybe_filename = ?,\n maybe_width = ?,\n maybe_height = ?,\n maybe_resolution = ?,\n maybe_detected_model_family = ?,\n maybe_detected_model_type = ?,\n maybe_report = ?,\n maybe_updated_ip_address = ?,\n version = version + 1\nWHERE token = ?\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 9
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "c6bbdc963e0d49ac55973a728d04b371967c20bfb9dd4bd75e4693dc7a114c99"
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT DISTINCT payments_namespace AS `payments_namespace!: PaymentsNamespace`\nFROM user_spend_events\nWHERE maybe_user_token IS NOT NULL\n AND is_production = TRUE\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "payments_namespace!: PaymentsNamespace",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "cf8c702126bba73cd3abc2ed7202962b6da773460505222a75408dab1d26b70f"
|
||||
}
|
||||
+212
@@ -0,0 +1,212 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n e.token AS `token!: UserSpendEventToken`,\n e.payments_namespace AS `payments_namespace!`,\n e.maybe_user_token AS `maybe_user_token: UserToken`,\n u.username AS `maybe_username?`,\n u.display_name AS `maybe_display_name?`,\n u.email_gravatar_hash AS `maybe_email_gravatar_hash?`,\n e.event_type AS `event_type!`,\n e.amount_usd_cents,\n e.maybe_credits_granted AS `maybe_credits_granted: u32`,\n e.payment_source AS `payment_source!`,\n e.source_object_id AS `maybe_source_object_id`,\n e.maybe_stripe_invoice_id,\n e.maybe_stripe_payment_intent_id,\n e.maybe_stripe_charge_id,\n e.maybe_stripe_customer_id,\n e.is_production AS `is_production: bool`,\n e.payment_occurred_at AS `payment_occurred_at!: DateTime<Utc>`,\n e.created_at AS `created_at!: DateTime<Utc>`\nFROM user_spend_events e\nLEFT JOIN users u ON u.token = e.maybe_user_token\nWHERE (? IS NULL OR e.payments_namespace = ?)\nORDER BY e.payment_occurred_at DESC, e.id DESC\nLIMIT ? OFFSET ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "token!: UserSpendEventToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "payments_namespace!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "maybe_user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "maybe_username?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "maybe_display_name?",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "maybe_email_gravatar_hash?",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "event_type!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "amount_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "maybe_credits_granted: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "payment_source!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 64
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "maybe_source_object_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "maybe_stripe_invoice_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_stripe_payment_intent_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 13,
|
||||
"name": "maybe_stripe_charge_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 14,
|
||||
"name": "maybe_stripe_customer_id",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 15,
|
||||
"name": "is_production: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 16,
|
||||
"name": "payment_occurred_at!: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 17,
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "d5d3a59ea4885868b26168cd305f6168a1e62fe9f8fa663063591335c645b927"
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n wle.maybe_entity_ref AS `stripe_object_id!`,\n wle.entry_type AS `entry_type!`,\n wle.token AS `ledger_token!: WalletLedgerEntryToken`,\n w.owner_user_token AS `owner_user_token!: UserToken`,\n wle.credits_delta AS `credits_delta!`,\n wle.created_at AS `created_at!: DateTime<Utc>`\nFROM wallet_ledger_entries wle\nJOIN wallets w ON w.token = wle.wallet_token\nWHERE w.wallet_namespace = 'artcraft'\n AND wle.entry_type IN ('credit_banked', 'credit_monthly')\n AND wle.created_at >= ?\n AND (wle.maybe_entity_ref LIKE 'pi\\_%' OR wle.maybe_entity_ref LIKE 'in\\_%')\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "stripe_object_id!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 1020
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "entry_type!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "ledger_token!: WalletLedgerEntryToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "owner_user_token!: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "credits_delta!",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 11
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "created_at!: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "de69f3d564830f8fff90f754d13982de623fcda50118d261466eeaf034de5ecb"
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n api_keys.token as `api_key_token: ApiKeyToken`,\n users.token as `user_token: UserToken`,\n users.is_banned\nFROM api_keys\nINNER JOIN users\n ON users.token = api_keys.owner_user_token\nWHERE api_keys.api_key = ?\n AND api_keys.maybe_deleted_at IS NULL\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "api_key_token: ApiKeyToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "user_token: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "is_banned",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f52c495a7ff5cafb357f79c4cdb503bbbef045fd193a5a37a66180fcac77f363"
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n s.user_token AS `user_token!: UserToken`,\n u.username AS `username!`,\n u.display_name AS `display_name!`,\n u.email_gravatar_hash AS `email_gravatar_hash!`,\n s.lifetime_net_spend_usd_cents,\n s.net_spend_7d_usd_cents,\n s.net_spend_14d_usd_cents,\n s.net_spend_30d_usd_cents,\n s.net_spend_60d_usd_cents,\n s.net_spend_90d_usd_cents,\n s.net_spend_this_year_usd_cents,\n s.is_active_subscriber AS `is_active_subscriber: bool`,\n s.maybe_reengagement_score AS `maybe_reengagement_score: u32`\nFROM user_spend_summaries s\nJOIN users u ON u.token = s.user_token\nWHERE s.payments_namespace = ?\nORDER BY\n (CASE ?\n WHEN '7d' THEN s.net_spend_7d_usd_cents\n WHEN '14d' THEN s.net_spend_14d_usd_cents\n WHEN '30d' THEN s.net_spend_30d_usd_cents\n WHEN '60d' THEN s.net_spend_60d_usd_cents\n WHEN '90d' THEN s.net_spend_90d_usd_cents\n WHEN '1y' THEN s.net_spend_this_year_usd_cents\n ELSE CAST(s.lifetime_net_spend_usd_cents AS SIGNED)\n END) DESC,\n s.id ASC\nLIMIT ? OFFSET ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "user_token!: UserToken",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "username!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "display_name!",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 80
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email_gravatar_hash!",
|
||||
"type_info": {
|
||||
"type": "String",
|
||||
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "lifetime_net_spend_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL | UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "net_spend_7d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "net_spend_14d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "net_spend_30d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 8,
|
||||
"name": "net_spend_60d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 9,
|
||||
"name": "net_spend_90d_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 10,
|
||||
"name": "net_spend_this_year_usd_cents",
|
||||
"type_info": {
|
||||
"type": "LongLong",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 20
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 11,
|
||||
"name": "is_active_subscriber: bool",
|
||||
"type_info": {
|
||||
"type": "Tiny",
|
||||
"flags": "NOT_NULL",
|
||||
"char_set": 63,
|
||||
"max_size": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 12,
|
||||
"name": "maybe_reengagement_score: u32",
|
||||
"type_info": {
|
||||
"type": "Long",
|
||||
"flags": "UNSIGNED",
|
||||
"char_set": 63,
|
||||
"max_size": 10
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "f6db2f3bf8695fa8431db209576328154b8ac20aa6e899739095b6206bb38944"
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nSELECT\n maybe_stripe_subscription_status,\n maybe_stripe_recurring_interval,\n subscription_expires_at AS `subscription_expires_at!: DateTime<Utc>`\nFROM user_subscriptions\nWHERE user_token = ?\n AND subscription_namespace = ?\nORDER BY subscription_expires_at DESC\nLIMIT 1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "maybe_stripe_subscription_status",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "MULTIPLE_KEY | BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "maybe_stripe_recurring_interval",
|
||||
"type_info": {
|
||||
"type": "VarString",
|
||||
"flags": "BINARY",
|
||||
"char_set": 224,
|
||||
"max_size": 128
|
||||
}
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "subscription_expires_at!: DateTime<Utc>",
|
||||
"type_info": {
|
||||
"type": "Timestamp",
|
||||
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
|
||||
"char_set": 63,
|
||||
"max_size": 19
|
||||
}
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
true,
|
||||
true,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "f74615faa4321ec4d583829997e12cb8ae152afefd2574385c120c63278893d2"
|
||||
}
|
||||
@@ -58,7 +58,7 @@ artcraft/
|
||||
|
||||
- Rust with no minimum supported version
|
||||
- Actix-web for HTTP services
|
||||
- SQLx for MySQL and SQLite; prefer `sqlx::query!` / `sqlx::query_as!` compile-time checked macros over runtime `sqlx::query()` whenever possible
|
||||
- SQLx for MySQL and SQLite. MySQL queries belong in the `mysql_queries` crate, not in application/service crates like `storyteller-web` — call those query functions instead of embedding `sqlx::query!` / `sqlx::query()` in handlers. (Query-writing conventions live in `crates/schema/database/mysql_queries/CLAUDE.md`.)
|
||||
- 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
+704
-721
File diff suppressed because it is too large
Load Diff
+21
-13
@@ -7,9 +7,12 @@ members = [
|
||||
# API clients for external services
|
||||
# These might be custom implementations or wrappers over other client libraries.
|
||||
"crates/api_clients/artcraft/artcraft_api_defs",
|
||||
"crates/api_clients/artcraft/artcraft_api_keys",
|
||||
"crates/api_clients/artcraft/artcraft_client",
|
||||
"crates/api_clients/artcraft/artcraft_router",
|
||||
"crates/api_clients/bucket_client",
|
||||
"crates/api_clients/cloud_storage",
|
||||
"crates/api_clients/comet_api_client",
|
||||
"crates/api_clients/datadog_client",
|
||||
"crates/api_clients/fal_client",
|
||||
"crates/api_clients/google_drive_downloader",
|
||||
@@ -85,6 +88,7 @@ members = [
|
||||
"crates/lib/pager",
|
||||
"crates/lib/password",
|
||||
"crates/lib/server_environment",
|
||||
"crates/lib/sha1_hash",
|
||||
"crates/lib/sqlx_mysql_helpers",
|
||||
"crates/lib/storyteller_root",
|
||||
"crates/lib/url_config",
|
||||
@@ -92,6 +96,7 @@ members = [
|
||||
"crates/lib/user_input_common",
|
||||
"crates/lib/users",
|
||||
"crates/lib/uuid_utils",
|
||||
"crates/lib/video_info",
|
||||
#"crates/lib/api_clients/fakeyou_client",
|
||||
#"crates/lib/deprecated/machine_learning/ml_models",
|
||||
|
||||
@@ -131,6 +136,7 @@ members = [
|
||||
"crates/service/job/grok_api_job",
|
||||
"crates/service/job/seedance2_pro_job",
|
||||
"crates/service/job/video_thumbnail_job",
|
||||
"crates/service/job/user_spend_analytics_job",
|
||||
"crates/service/job/worldlabs_job",
|
||||
"crates/service/web/dummy_service",
|
||||
"crates/service/web/storyteller_web",
|
||||
@@ -231,9 +237,11 @@ workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
|
||||
actix_artcraft = { path = "crates/lib/actix_artcraft" }
|
||||
actix_helpers = { path = "crates/lib/actix_helpers" }
|
||||
artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" }
|
||||
artcraft_api_keys = { path = "crates/api_clients/artcraft/artcraft_api_keys" }
|
||||
artcraft_client = { path = "crates/api_clients/artcraft/artcraft_client" }
|
||||
artcraft_router = { path = "crates/api_clients/artcraft/artcraft_router" }
|
||||
bootstrap = { path = "crates/lib/bootstrap" }
|
||||
bucket_client = { path = "crates/api_clients/bucket_client" }
|
||||
bucket_paths = { path = "crates/schema/buckets/bucket_paths" }
|
||||
build_metadata = { path = "crates/lib/build_metadata" }
|
||||
cloud_storage = { path = "crates/api_clients/cloud_storage" }
|
||||
@@ -278,6 +286,7 @@ openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
|
||||
primitives = { path = "crates/lib/data/primitives" }
|
||||
seedance2pro_client = { path = "crates/api_clients/seedance2pro_client" }
|
||||
server_environment = { path = "crates/lib/server_environment" }
|
||||
sha1_hash = { path = "crates/lib/sha1_hash" }
|
||||
shared_service_components = { path = "crates/lib/shared_service_components" }
|
||||
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
|
||||
subprocess_common = { path = "crates/lib/deprecated/subprocess_common" }
|
||||
@@ -287,6 +296,7 @@ tokens = { path = "crates/schema/public/tokens" }
|
||||
url_utils = { path = "crates/lib/url_utils" }
|
||||
users = { path = "crates/lib/users" }
|
||||
uuid_utils = { path = "crates/lib/uuid_utils" }
|
||||
video_info = { path = "crates/lib/video_info" }
|
||||
web_base64 = { path = "crates/lib/data/web_base64" }
|
||||
worldlabs_api_client = { path = "crates/api_clients/worldlabs_api_client" }
|
||||
worldlabs_consumer_client = { path = "crates/api_clients/worldlabs_consumer_client" }
|
||||
@@ -294,16 +304,14 @@ worldlabs_consumer_client = { path = "crates/api_clients/worldlabs_consumer_clie
|
||||
# External libs
|
||||
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
|
||||
# If a crate needs to use an older/newer version, the dependency should be managed in that crate's own Cargo.toml
|
||||
actix = "0.13.5"
|
||||
actix-cors = "0.6.4" # TODO(bt): Carefully upgrade this. CORS broke last time.
|
||||
actix-http = "3.7.0"
|
||||
actix-multipart = "0.6.2"
|
||||
actix-rt = "2.10.0"
|
||||
actix-cors = "0.7.1"
|
||||
actix-http = "3.12.1"
|
||||
actix-multipart = "0.7.2"
|
||||
actix-rt = "2.11.0"
|
||||
actix-service = "2.0.3"
|
||||
actix-utils = "3.0.1"
|
||||
actix-web = "4.7.0" # TODO(bt): We're behind and need to bump versions.
|
||||
actix-web-actors = "4.3.1+deprecated"
|
||||
actix-web-lab = "0.20.2"
|
||||
actix-web = "4.13.0"
|
||||
actix-web-lab = "0.26.0"
|
||||
anyhow = "1.0.102"
|
||||
assertor = "0.0.3"
|
||||
base64 = "0.22.1"
|
||||
@@ -343,11 +351,11 @@ sha2 = "0.11.0-rc.5"
|
||||
sha3 = "0.10.8"
|
||||
speculoos = "0.11.0"
|
||||
strum = { version = "0.28.0", features = ["derive"] }
|
||||
tauri = { version = "2.8.3", features = ["unstable", "devtools", "protocol-asset"] } # NB: "devtools" prevents App Store distribution; "ubstable" is needed for window access
|
||||
tauri-plugin-dialog = "2.3.3"
|
||||
tauri-plugin-fs = "2.4.2"
|
||||
tauri-plugin-log = "2.6.0"
|
||||
tauri-plugin-upload = "2.3.1"
|
||||
tauri = { version = "2.11.2", features = ["unstable", "devtools", "protocol-asset"] } # NB: "devtools" prevents App Store distribution; "ubstable" is needed for window access
|
||||
tauri-plugin-dialog = "2.7.1"
|
||||
tauri-plugin-fs = "2.5.1"
|
||||
tauri-plugin-log = "2.8.0"
|
||||
tauri-plugin-upload = "2.4.0"
|
||||
tempdir = "0.3.7"
|
||||
tempfile = "3.27.0"
|
||||
thiserror = "2.0.18"
|
||||
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
DROP COLUMN maybe_external_third_party_cost_credits;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_external_third_party_cost_credits INT(10) UNSIGNED DEFAULT NULL;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
DROP COLUMN maybe_external_third_party_cost_usd_cents;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_external_third_party_cost_usd_cents INT(10) UNSIGNED DEFAULT NULL;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
DROP COLUMN maybe_system_cost_credits;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_system_cost_credits INT(10) UNSIGNED DEFAULT NULL;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
DROP COLUMN maybe_system_cost_usd_cents;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE generic_inference_jobs
|
||||
ADD COLUMN maybe_system_cost_usd_cents INT(10) UNSIGNED DEFAULT NULL;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS api_keys;
|
||||
@@ -0,0 +1,50 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE api_keys (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (used internally for lookups and as a foreign key).
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The actual API key value presented by clients to authenticate.
|
||||
api_key VARCHAR(255) NOT NULL,
|
||||
|
||||
-- ========== KEY INFO ==========
|
||||
|
||||
-- User-supplied display name for the key.
|
||||
name VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Optional user-supplied description.
|
||||
maybe_description VARCHAR(512) DEFAULT NULL,
|
||||
|
||||
-- ========== OWNER ==========
|
||||
|
||||
-- The user that owns this API key.
|
||||
owner_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== IP ADDRESSES ==========
|
||||
|
||||
-- IP address that created the key. Wide enough for IPv4/IPv6.
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address that last updated the key. Wide enough for IPv4/IPv6.
|
||||
ip_address_update VARCHAR(40) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- Soft-delete timestamp. NULL means the key is live.
|
||||
maybe_deleted_at TIMESTAMP NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (api_key),
|
||||
KEY fk_owner_user_token (owner_user_token),
|
||||
KEY index_maybe_deleted_at (maybe_deleted_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS uploaded_videos;
|
||||
@@ -0,0 +1,62 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE uploaded_videos (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (used internally for lookups and as a foreign key).
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== FILE ==========
|
||||
|
||||
-- SHA-1 checksum of the uploaded bytes (40 hex chars). De-dupes uploads.
|
||||
sha1_checksum VARCHAR(40) NOT NULL,
|
||||
|
||||
-- Size of the uploaded file in bytes.
|
||||
filesize_bytes INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- Pixel dimensions, when they could be determined.
|
||||
maybe_width INT(10) UNSIGNED DEFAULT NULL,
|
||||
maybe_height INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- Resolution label (e.g. "1080p", "1920x1080"), when known.
|
||||
maybe_resolution VARCHAR(12) DEFAULT NULL,
|
||||
|
||||
-- ========== DETECTION ==========
|
||||
|
||||
-- Detected provenance family (e.g. "seedance", "veo", "sora", "dreamina", "kling").
|
||||
maybe_detected_model_family VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Detected model type / variant (e.g. "full", "fast", "mini").
|
||||
maybe_detected_model_type VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Free-form report / analysis blob for this upload.
|
||||
maybe_report MEDIUMTEXT DEFAULT NULL,
|
||||
|
||||
-- ========== IP ADDRESSES ==========
|
||||
|
||||
-- IP address that uploaded the video. Wide enough for IPv4/IPv6.
|
||||
upload_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address that last updated the row. Wide enough for IPv4/IPv6.
|
||||
maybe_updated_ip_address VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
-- ========== VERSION ==========
|
||||
|
||||
-- Optimistic-concurrency "vector clock". Bump by 1 on every update — set in
|
||||
-- the UPDATE statement (MySQL can't auto-increment a plain column via schema).
|
||||
version INT(8) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (sha1_checksum),
|
||||
KEY index_maybe_detected_model_family (maybe_detected_model_family)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS uploaded_video_notes;
|
||||
@@ -0,0 +1,69 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE uploaded_video_notes (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (used internally for lookups and as a foreign key).
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The uploaded video this note is about (FK to uploaded_videos.token).
|
||||
uploaded_video_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== SUBMISSION ==========
|
||||
|
||||
-- Original filename of the submitted clip, as provided by the user.
|
||||
maybe_filename VARCHAR(512) DEFAULT NULL,
|
||||
|
||||
-- The model type the submitter claims this video is.
|
||||
maybe_reported_model_type VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Free-form custom model name when the submitter's model isn't a known type.
|
||||
maybe_reported_model_name VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- The website / platform the video was obtained from.
|
||||
maybe_website VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Free-form website when it isn't a known option.
|
||||
maybe_other_website VARCHAR(128) DEFAULT NULL,
|
||||
|
||||
-- Free-form comments from the submitter.
|
||||
maybe_comments VARCHAR(1024) DEFAULT NULL,
|
||||
|
||||
-- ========== CONTACT & FLAGS ==========
|
||||
|
||||
-- Optional email address for follow-up.
|
||||
maybe_email_address VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Whether the submitter consents to us sharing the report.
|
||||
can_share_report BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- Whether the submitter believes they were scammed.
|
||||
was_scammed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- ========== IP ADDRESSES ==========
|
||||
|
||||
-- IP address that submitted the note. Wide enough for IPv4/IPv6.
|
||||
comment_create_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address that last updated the note. Wide enough for IPv4/IPv6.
|
||||
maybe_comment_update_ip_address VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
-- ========== VERSION ==========
|
||||
|
||||
-- Optimistic-concurrency "vector clock". Bump by 1 on every update — set in
|
||||
-- the UPDATE statement (MySQL can't auto-increment a plain column via schema).
|
||||
version INT(8) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
KEY fk_uploaded_video_token (uploaded_video_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE uploaded_videos
|
||||
DROP COLUMN maybe_filename;
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE uploaded_videos
|
||||
ADD COLUMN maybe_filename VARCHAR(512) DEFAULT NULL AFTER filesize_bytes;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
ALTER TABLE prompts
|
||||
DROP COLUMN maybe_bitrate;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Add bitrate parameter to prompts table.
|
||||
-- Represented by the enum `CommonBitrate` (normal, high).
|
||||
ALTER TABLE prompts
|
||||
ADD COLUMN maybe_bitrate VARCHAR(16) DEFAULT NULL;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Revert `maybe_entity_token` back to NOT NULL.
|
||||
-- NB: This will fail if any rows have a null `maybe_entity_token`.
|
||||
ALTER TABLE staff_audit_logs
|
||||
MODIFY COLUMN maybe_entity_token VARCHAR(32) NOT NULL;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- `maybe_entity_token` is optional (many audited actions have no entity), but it
|
||||
-- was mistakenly created as NOT NULL, so inserts with a null entity token fail
|
||||
-- with "Column 'maybe_entity_token' cannot be null". Make it nullable.
|
||||
ALTER TABLE staff_audit_logs
|
||||
MODIFY COLUMN maybe_entity_token VARCHAR(32) DEFAULT NULL;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS user_spend_events;
|
||||
@@ -0,0 +1,103 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Append-only ledger of real money movements (USD cents), one row per settled
|
||||
-- event. Derived from settled Stripe webhooks (invoice.paid, payment_intent.
|
||||
-- succeeded / checkout.session.completed, charge.refunded, charge.dispute.*),
|
||||
-- NOT from every webhook. Idempotent via uq_source_object so retries never
|
||||
-- double-count. We always work in USD via Stripe, so there are no currency or
|
||||
-- fee columns.
|
||||
CREATE TABLE user_spend_events (
|
||||
-- Replication only.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective public primary key. Token prefix "spend_".
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Multi-site segregation. Reuses the existing PaymentsNamespace enum.
|
||||
payments_namespace VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Who paid. Nullable so we can record money we can't yet attribute and
|
||||
-- backfill -- but expect this set ~always.
|
||||
maybe_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- PaymentEventType enum. Splits subscription vs credit-pack revenue:
|
||||
-- 'subscription_initial' -- first paid invoice of a new subscription
|
||||
-- 'subscription_renewal' -- recurring paid invoice (monthly, or annual once/yr)
|
||||
-- 'subscription_proration_upgrade' -- mid-cycle upgrade proration charge (positive)
|
||||
-- 'subscription_proration_downgrade' -- mid-cycle downgrade proration credit (rare as a settled event)
|
||||
-- 'credit_pack_purchase' -- one-time credit pack (the ~10x-volume case)
|
||||
-- 'refund' -- refund (amount negative)
|
||||
-- 'chargeback' -- dispute/chargeback (amount negative)
|
||||
-- 'manual_adjustment' -- staff/console correction (payment_source = 'manual')
|
||||
-- 'subscription_monthly_refill' -- OPTIONAL non-revenue event for annual-plan
|
||||
-- monthly credit refills (amount 0)
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== MONEY ==========
|
||||
|
||||
-- USD cents. SIGNED: refunds/chargebacks negative; refills are 0. BIGINT so
|
||||
-- downstream lifetime sums never overflow.
|
||||
amount_usd_cents BIGINT NOT NULL,
|
||||
|
||||
-- For credit-pack purchases / subscription grants/refills: how many of OUR
|
||||
-- credits this granted. Lets us correlate dollars-in vs credits-granted vs
|
||||
-- credits-used. NULL for refunds/chargebacks.
|
||||
maybe_credits_granted INTEGER UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- ========== INTERNAL LINKAGE ==========
|
||||
|
||||
-- The subscription this payment belongs to, if any (-> user_subscriptions.token).
|
||||
maybe_user_subscription_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- The credit-grant ledger entry this produced, if any
|
||||
-- (-> wallet_ledger_entries.token). Ties a dollar event to its credit effect.
|
||||
maybe_wallet_ledger_entry_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- ========== SOURCE / PROVIDER ==========
|
||||
|
||||
-- PaymentSource enum: 'stripe' or 'manual'. (USD/Stripe only for the
|
||||
-- foreseeable future; 'manual' covers staff/console adjustments.)
|
||||
payment_source VARCHAR(16) NOT NULL,
|
||||
|
||||
-- The authoritative object this row represents -- the dedup anchor. NULLABLE.
|
||||
-- For Stripe: invoice id (subscriptions), payment_intent id (one-time), or
|
||||
-- refund/dispute id (refunds). For payment_source='manual' (no provider
|
||||
-- object) leave it NULL. MySQL allows multiple NULLs in a unique index, so
|
||||
-- manual rows freely duplicate, while any present id is still deduped.
|
||||
source_object_id VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Convenience copies for joins/forensics (255 + utf8mb4_bin for Stripe IDs).
|
||||
maybe_stripe_customer_id VARCHAR(255) DEFAULT NULL,
|
||||
maybe_stripe_invoice_id VARCHAR(255) DEFAULT NULL,
|
||||
maybe_stripe_payment_intent_id VARCHAR(255) DEFAULT NULL,
|
||||
maybe_stripe_charge_id VARCHAR(255) DEFAULT NULL,
|
||||
-- The webhook event that caused this row (provenance only).
|
||||
maybe_stripe_event_id VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Stripe livemode. Keep test-mode money out of dashboards.
|
||||
is_production BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
-- When the money actually moved (Stripe settlement time). Bin/aggregate on
|
||||
-- THIS, not created_at (which is when we ingested the webhook).
|
||||
payment_occurred_at TIMESTAMP NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
-- Idempotency: one row per settled object. NULL source_object_id rows (manual)
|
||||
-- are exempt -- MySQL treats each NULL in a unique index as distinct.
|
||||
UNIQUE KEY uq_source_object (payment_source, source_object_id),
|
||||
KEY idx_user_occurred (maybe_user_token, payment_occurred_at),
|
||||
KEY idx_occurred (payment_occurred_at),
|
||||
KEY idx_namespace_occurred (payments_namespace, payment_occurred_at),
|
||||
KEY idx_event_type (event_type),
|
||||
KEY idx_stripe_customer (maybe_stripe_customer_id),
|
||||
KEY idx_stripe_invoice (maybe_stripe_invoice_id)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS user_daily_spends;
|
||||
@@ -0,0 +1,44 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Per-user, per-day spend rollup (L1). Net = gross - refunds. Subscription vs
|
||||
-- credit-pack splits expose the revenue mix per day without touching the event
|
||||
-- table. Sparsely populated -- no row for days with zero spend and zero refunds.
|
||||
-- Derived from (and fully rebuildable from) user_spend_events.
|
||||
CREATE TABLE user_daily_spends (
|
||||
-- Replication only.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
payments_namespace VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Reporting day. Bins user_spend_events.payment_occurred_at in a FIXED tz
|
||||
-- (UTC). DATE, not TIMESTAMP.
|
||||
spend_date DATE NOT NULL,
|
||||
|
||||
-- Revenue mix (subset of gross). Category totals can never go negative, so
|
||||
-- they're UNSIGNED.
|
||||
subscription_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
credits_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
gross_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- sum of positive amounts (UNSIGNED)
|
||||
refund_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- refund/chargeback magnitude (UNSIGNED)
|
||||
-- NET is the ONE field here that must be SIGNED: a single day can net negative
|
||||
-- when refunds (often of an earlier day's purchase) exceed that day's purchases.
|
||||
net_spend_usd_cents BIGINT NOT NULL DEFAULT 0, -- gross - refund (SIGNED)
|
||||
|
||||
payment_count INT UNSIGNED NOT NULL DEFAULT 0, -- # positive money events
|
||||
credits_granted BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_user_day (user_token, payments_namespace, spend_date),
|
||||
-- "Top spenders / spend in a date range": filter by date, then group by user.
|
||||
KEY idx_day_user_net (user_token, payments_namespace, spend_date, net_spend_usd_cents),
|
||||
KEY idx_user_day (user_token, spend_date)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
DROP TABLE IF EXISTS user_spend_summaries;
|
||||
@@ -0,0 +1,95 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- Per-user spend summary (L2). Lifetime/recency facts are event-maintained (they
|
||||
-- only grow); sliding windows and weekly-rhythm metrics are job-maintained (they
|
||||
-- shift even on days a user doesn't pay). Powers churn / dip / re-engagement
|
||||
-- dashboards in O(1). Derived from (and fully rebuildable from) user_spend_events
|
||||
-- and user_daily_spends.
|
||||
CREATE TABLE user_spend_summaries (
|
||||
-- Replication only.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
payments_namespace VARCHAR(32) NOT NULL,
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ===== Lifetime (event-maintained) =====
|
||||
-- Gross / category / refund totals are UNSIGNED. Lifetime net is UNSIGNED too:
|
||||
-- over a lifetime you can never refund more than was paid.
|
||||
lifetime_gross_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
lifetime_subscription_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- revenue mix split
|
||||
lifetime_credits_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- revenue mix split
|
||||
lifetime_refund_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
lifetime_net_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- gross - refund
|
||||
-- Event COUNTS (not money): number of payment events vs refund/chargeback
|
||||
-- events over the user's lifetime. Both event-maintained.
|
||||
lifetime_payment_count INT UNSIGNED NOT NULL DEFAULT 0, -- # of positive money events
|
||||
lifetime_refund_count INT UNSIGNED NOT NULL DEFAULT 0, -- # of refund/chargeback events
|
||||
first_payment_at TIMESTAMP NULL DEFAULT NULL,
|
||||
first_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- amount of the first payment
|
||||
last_payment_at TIMESTAMP NULL DEFAULT NULL,
|
||||
last_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- amount of the most recent payment
|
||||
days_since_first_payment INT UNSIGNED DEFAULT NULL, -- job-refreshed
|
||||
days_since_last_payment INT UNSIGNED DEFAULT NULL, -- job-refreshed
|
||||
|
||||
-- ===== Sliding NET spend windows -- SIGNED, a window can net negative
|
||||
-- (job-refreshed from user_daily_spends) =====
|
||||
-- "prev" = the equally-sized window immediately before, for WoW/MoM deltas.
|
||||
net_spend_7d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_prev_7d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_14d_usd_cents BIGINT NOT NULL DEFAULT 0, -- bi-weekly
|
||||
net_spend_prev_14d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_30d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_prev_30d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_60d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_90d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_this_year_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
-- ===== Weekly cadence / rhythm (job-refreshed) -- for the sales team =====
|
||||
-- A "week" is Mon-Sun UTC. These let sales spot users breaking their habit.
|
||||
-- Per-user weekly baselines at two horizons: 4w is responsive (recent norm),
|
||||
-- 12w is stable (established norm). 4w well below 12w is itself a cooling-off
|
||||
-- signal. Mean weekly NET spend over the trailing window (calendar weeks,
|
||||
-- zero-spend weeks included). SIGNED, like the window nets.
|
||||
avg_weekly_net_spend_4w_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
avg_weekly_net_spend_12w_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
-- How many of the last N calendar weeks had any net spend -- a regularity
|
||||
-- measure at several horizons.
|
||||
active_weeks_in_last_4 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..4
|
||||
active_weeks_in_last_8 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..8
|
||||
active_weeks_in_last_12 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..12
|
||||
active_weeks_in_last_24 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..24
|
||||
active_weeks_in_last_52 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..52
|
||||
consecutive_active_weeks INT UNSIGNED NOT NULL DEFAULT 0, -- current "streak"
|
||||
consecutive_inactive_weeks INT UNSIGNED NOT NULL DEFAULT 0, -- current "dip" length
|
||||
-- Recency in weeks. Subsumes the entire "is_active_spender_last_N_weeks"
|
||||
-- family: "active in the last N weeks" == (weeks_since_last_spend < N). So we
|
||||
-- intentionally store NO is_active_* booleans (redundant + poor index
|
||||
-- selectivity); we index this field instead and range-query any N. NULL = never spent.
|
||||
weeks_since_last_spend INT UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- ===== Flags =====
|
||||
is_active_subscriber BOOLEAN NOT NULL DEFAULT FALSE, -- from user_subscriptions
|
||||
maybe_subscription_interval VARCHAR(16) DEFAULT NULL, -- 'month' / 'year' (annual plans behave differently)
|
||||
-- Optional precomputed re-engagement priority (job-set).
|
||||
maybe_reengagement_score INT UNSIGNED DEFAULT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_user (payments_namespace, user_token),
|
||||
KEY idx_lifetime (payments_namespace, lifetime_net_spend_usd_cents),
|
||||
KEY idx_last_payment_at (payments_namespace, last_payment_at),
|
||||
KEY idx_first_payment_at (payments_namespace, first_payment_at),
|
||||
KEY idx_spend_7d (payments_namespace, net_spend_7d_usd_cents),
|
||||
KEY idx_spend_30d (payments_namespace, net_spend_30d_usd_cents),
|
||||
KEY idx_reengagement (payments_namespace, maybe_reengagement_score),
|
||||
-- Sort / range-filter by recency. idx_weeks_since_last_spend also powers every
|
||||
-- "active in the last N weeks" query (weeks_since_last_spend < N).
|
||||
KEY idx_weeks_since_last_spend (payments_namespace, weeks_since_last_spend),
|
||||
KEY idx_days_since_last_payment (payments_namespace, days_since_last_payment)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,50 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE api_keys (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (used internally for lookups and as a foreign key).
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The actual API key value presented by clients to authenticate.
|
||||
api_key VARCHAR(255) NOT NULL,
|
||||
|
||||
-- ========== KEY INFO ==========
|
||||
|
||||
-- User-supplied display name for the key.
|
||||
name VARCHAR(255) NOT NULL,
|
||||
|
||||
-- Optional user-supplied description.
|
||||
maybe_description VARCHAR(512) DEFAULT NULL,
|
||||
|
||||
-- ========== OWNER ==========
|
||||
|
||||
-- The user that owns this API key.
|
||||
owner_user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== IP ADDRESSES ==========
|
||||
|
||||
-- IP address that created the key. Wide enough for IPv4/IPv6.
|
||||
ip_address_creation VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address that last updated the key. Wide enough for IPv4/IPv6.
|
||||
ip_address_update VARCHAR(40) NOT NULL,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- Soft-delete timestamp. NULL means the key is live.
|
||||
maybe_deleted_at TIMESTAMP NULL,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (api_key),
|
||||
KEY fk_owner_user_token (owner_user_token),
|
||||
KEY index_maybe_deleted_at (maybe_deleted_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -56,6 +56,19 @@ CREATE TABLE generic_inference_jobs (
|
||||
-- service uses.
|
||||
maybe_external_third_party_id VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- What the third-party service charges us for the job, in the service's
|
||||
-- own credit denomination (if it bills in credits).
|
||||
maybe_external_third_party_cost_credits INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- What the third-party service charges us for the job, in USD cents.
|
||||
maybe_external_third_party_cost_usd_cents INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- What we charge the user for the job, in our own credits.
|
||||
maybe_system_cost_credits INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- What we charge the user for the job, in USD cents.
|
||||
maybe_system_cost_usd_cents INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- There is an index on this column.
|
||||
-- NB: See notes on inference_category. This is becoming a problematic field. We're using
|
||||
-- this to load the container spin-up arguments and dependencies, but the former is being
|
||||
|
||||
@@ -72,6 +72,10 @@ CREATE TABLE prompts (
|
||||
-- The quality setting (eg. high, medium, low)
|
||||
maybe_quality VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- Represented by the enum `CommonBitrate`.
|
||||
-- The output bitrate setting (eg. normal, high)
|
||||
maybe_bitrate VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- The duration in seconds
|
||||
maybe_duration_seconds INT UNSIGNED DEFAULT NULL,
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
|
||||
-- with comments attached to the fields for centralized documentation.
|
||||
|
||||
-- Per-user, per-day spend rollup (L1). Net = gross - refunds. Subscription vs
|
||||
-- credit-pack splits expose the revenue mix per day without touching the event
|
||||
-- table. Sparsely populated -- no row for days with zero spend and zero refunds.
|
||||
-- Derived from (and fully rebuildable from) user_spend_events.
|
||||
CREATE TABLE user_daily_spends (
|
||||
-- Replication only.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
payments_namespace VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Reporting day. Bins user_spend_events.payment_occurred_at in a FIXED tz
|
||||
-- (UTC). DATE, not TIMESTAMP.
|
||||
spend_date DATE NOT NULL,
|
||||
|
||||
-- Revenue mix (subset of gross). Category totals can never go negative, so
|
||||
-- they're UNSIGNED.
|
||||
subscription_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
credits_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
gross_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- sum of positive amounts (UNSIGNED)
|
||||
refund_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- refund/chargeback magnitude (UNSIGNED)
|
||||
-- NET is the ONE field here that must be SIGNED: a single day can net negative
|
||||
-- when refunds (often of an earlier day's purchase) exceed that day's purchases.
|
||||
net_spend_usd_cents BIGINT NOT NULL DEFAULT 0, -- gross - refund (SIGNED)
|
||||
|
||||
payment_count INT UNSIGNED NOT NULL DEFAULT 0, -- # positive money events
|
||||
credits_granted BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_user_day (user_token, payments_namespace, spend_date),
|
||||
-- "Top spenders / spend in a date range": filter by date, then group by user.
|
||||
KEY idx_day_user_net (user_token, payments_namespace, spend_date, net_spend_usd_cents),
|
||||
KEY idx_user_day (user_token, spend_date)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,106 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
|
||||
-- with comments attached to the fields for centralized documentation.
|
||||
|
||||
-- Append-only ledger of real money movements (USD cents), one row per settled
|
||||
-- event. Derived from settled Stripe webhooks (invoice.paid, payment_intent.
|
||||
-- succeeded / checkout.session.completed, charge.refunded, charge.dispute.*),
|
||||
-- NOT from every webhook. Idempotent via uq_source_object so retries never
|
||||
-- double-count. We always work in USD via Stripe, so there are no currency or
|
||||
-- fee columns.
|
||||
CREATE TABLE user_spend_events (
|
||||
-- Replication only.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective public primary key. Token prefix "spend_".
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Multi-site segregation. Reuses the existing PaymentsNamespace enum.
|
||||
payments_namespace VARCHAR(32) NOT NULL,
|
||||
|
||||
-- Who paid. Nullable so we can record money we can't yet attribute and
|
||||
-- backfill -- but expect this set ~always.
|
||||
maybe_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- PaymentEventType enum. Splits subscription vs credit-pack revenue:
|
||||
-- 'subscription_initial' -- first paid invoice of a new subscription
|
||||
-- 'subscription_renewal' -- recurring paid invoice (monthly, or annual once/yr)
|
||||
-- 'subscription_proration_upgrade' -- mid-cycle upgrade proration charge (positive)
|
||||
-- 'subscription_proration_downgrade' -- mid-cycle downgrade proration credit (rare as a settled event)
|
||||
-- 'credit_pack_purchase' -- one-time credit pack (the ~10x-volume case)
|
||||
-- 'refund' -- refund (amount negative)
|
||||
-- 'chargeback' -- dispute/chargeback (amount negative)
|
||||
-- 'manual_adjustment' -- staff/console correction (payment_source = 'manual')
|
||||
-- 'subscription_monthly_refill' -- OPTIONAL non-revenue event for annual-plan
|
||||
-- monthly credit refills (amount 0)
|
||||
event_type VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== MONEY ==========
|
||||
|
||||
-- USD cents. SIGNED: refunds/chargebacks negative; refills are 0. BIGINT so
|
||||
-- downstream lifetime sums never overflow.
|
||||
amount_usd_cents BIGINT NOT NULL,
|
||||
|
||||
-- For credit-pack purchases / subscription grants/refills: how many of OUR
|
||||
-- credits this granted. Lets us correlate dollars-in vs credits-granted vs
|
||||
-- credits-used. NULL for refunds/chargebacks.
|
||||
maybe_credits_granted INTEGER UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- ========== INTERNAL LINKAGE ==========
|
||||
|
||||
-- The subscription this payment belongs to, if any (-> user_subscriptions.token).
|
||||
maybe_user_subscription_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- The credit-grant ledger entry this produced, if any
|
||||
-- (-> wallet_ledger_entries.token). Ties a dollar event to its credit effect.
|
||||
maybe_wallet_ledger_entry_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- ========== SOURCE / PROVIDER ==========
|
||||
|
||||
-- PaymentSource enum: 'stripe' or 'manual'. (USD/Stripe only for the
|
||||
-- foreseeable future; 'manual' covers staff/console adjustments.)
|
||||
payment_source VARCHAR(16) NOT NULL,
|
||||
|
||||
-- The authoritative object this row represents -- the dedup anchor. NULLABLE.
|
||||
-- For Stripe: invoice id (subscriptions), payment_intent id (one-time), or
|
||||
-- refund/dispute id (refunds). For payment_source='manual' (no provider
|
||||
-- object) leave it NULL. MySQL allows multiple NULLs in a unique index, so
|
||||
-- manual rows freely duplicate, while any present id is still deduped.
|
||||
source_object_id VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Convenience copies for joins/forensics (255 + utf8mb4_bin for Stripe IDs).
|
||||
maybe_stripe_customer_id VARCHAR(255) DEFAULT NULL,
|
||||
maybe_stripe_invoice_id VARCHAR(255) DEFAULT NULL,
|
||||
maybe_stripe_payment_intent_id VARCHAR(255) DEFAULT NULL,
|
||||
maybe_stripe_charge_id VARCHAR(255) DEFAULT NULL,
|
||||
-- The webhook event that caused this row (provenance only).
|
||||
maybe_stripe_event_id VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Stripe livemode. Keep test-mode money out of dashboards.
|
||||
is_production BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
-- When the money actually moved (Stripe settlement time). Bin/aggregate on
|
||||
-- THIS, not created_at (which is when we ingested the webhook).
|
||||
payment_occurred_at TIMESTAMP NOT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
-- Idempotency: one row per settled object. NULL source_object_id rows (manual)
|
||||
-- are exempt -- MySQL treats each NULL in a unique index as distinct.
|
||||
UNIQUE KEY uq_source_object (payment_source, source_object_id),
|
||||
KEY idx_user_occurred (maybe_user_token, payment_occurred_at),
|
||||
KEY idx_occurred (payment_occurred_at),
|
||||
KEY idx_namespace_occurred (payments_namespace, payment_occurred_at),
|
||||
KEY idx_event_type (event_type),
|
||||
KEY idx_stripe_customer (maybe_stripe_customer_id),
|
||||
KEY idx_stripe_invoice (maybe_stripe_invoice_id)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,98 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
-- NB: This is a manually squashed view of all the CREATE and ALTER statements,
|
||||
-- with comments attached to the fields for centralized documentation.
|
||||
|
||||
-- Per-user spend summary (L2). Lifetime/recency facts are event-maintained (they
|
||||
-- only grow); sliding windows and weekly-rhythm metrics are job-maintained (they
|
||||
-- shift even on days a user doesn't pay). Powers churn / dip / re-engagement
|
||||
-- dashboards in O(1). Derived from (and fully rebuildable from) user_spend_events
|
||||
-- and user_daily_spends.
|
||||
CREATE TABLE user_spend_summaries (
|
||||
-- Replication only.
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
payments_namespace VARCHAR(32) NOT NULL,
|
||||
user_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ===== Lifetime (event-maintained) =====
|
||||
-- Gross / category / refund totals are UNSIGNED. Lifetime net is UNSIGNED too:
|
||||
-- over a lifetime you can never refund more than was paid.
|
||||
lifetime_gross_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
lifetime_subscription_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- revenue mix split
|
||||
lifetime_credits_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- revenue mix split
|
||||
lifetime_refund_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
lifetime_net_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- gross - refund
|
||||
-- Event COUNTS (not money): number of payment events vs refund/chargeback
|
||||
-- events over the user's lifetime. Both event-maintained.
|
||||
lifetime_payment_count INT UNSIGNED NOT NULL DEFAULT 0, -- # of positive money events
|
||||
lifetime_refund_count INT UNSIGNED NOT NULL DEFAULT 0, -- # of refund/chargeback events
|
||||
first_payment_at TIMESTAMP NULL DEFAULT NULL,
|
||||
first_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- amount of the first payment
|
||||
last_payment_at TIMESTAMP NULL DEFAULT NULL,
|
||||
last_spend_usd_cents BIGINT UNSIGNED NOT NULL DEFAULT 0, -- amount of the most recent payment
|
||||
days_since_first_payment INT UNSIGNED DEFAULT NULL, -- job-refreshed
|
||||
days_since_last_payment INT UNSIGNED DEFAULT NULL, -- job-refreshed
|
||||
|
||||
-- ===== Sliding NET spend windows -- SIGNED, a window can net negative
|
||||
-- (job-refreshed from user_daily_spends) =====
|
||||
-- "prev" = the equally-sized window immediately before, for WoW/MoM deltas.
|
||||
net_spend_7d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_prev_7d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_14d_usd_cents BIGINT NOT NULL DEFAULT 0, -- bi-weekly
|
||||
net_spend_prev_14d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_30d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_prev_30d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_60d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_90d_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
net_spend_this_year_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
|
||||
-- ===== Weekly cadence / rhythm (job-refreshed) -- for the sales team =====
|
||||
-- A "week" is Mon-Sun UTC. These let sales spot users breaking their habit.
|
||||
-- Per-user weekly baselines at two horizons: 4w is responsive (recent norm),
|
||||
-- 12w is stable (established norm). 4w well below 12w is itself a cooling-off
|
||||
-- signal. Mean weekly NET spend over the trailing window (calendar weeks,
|
||||
-- zero-spend weeks included). SIGNED, like the window nets.
|
||||
avg_weekly_net_spend_4w_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
avg_weekly_net_spend_12w_usd_cents BIGINT NOT NULL DEFAULT 0,
|
||||
-- How many of the last N calendar weeks had any net spend -- a regularity
|
||||
-- measure at several horizons.
|
||||
active_weeks_in_last_4 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..4
|
||||
active_weeks_in_last_8 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..8
|
||||
active_weeks_in_last_12 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..12
|
||||
active_weeks_in_last_24 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..24
|
||||
active_weeks_in_last_52 TINYINT UNSIGNED NOT NULL DEFAULT 0, -- 0..52
|
||||
consecutive_active_weeks INT UNSIGNED NOT NULL DEFAULT 0, -- current "streak"
|
||||
consecutive_inactive_weeks INT UNSIGNED NOT NULL DEFAULT 0, -- current "dip" length
|
||||
-- Recency in weeks. Subsumes the entire "is_active_spender_last_N_weeks"
|
||||
-- family: "active in the last N weeks" == (weeks_since_last_spend < N). So we
|
||||
-- intentionally store NO is_active_* booleans (redundant + poor index
|
||||
-- selectivity); we index this field instead and range-query any N. NULL = never spent.
|
||||
weeks_since_last_spend INT UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- ===== Flags =====
|
||||
is_active_subscriber BOOLEAN NOT NULL DEFAULT FALSE, -- from user_subscriptions
|
||||
maybe_subscription_interval VARCHAR(16) DEFAULT NULL, -- 'month' / 'year' (annual plans behave differently)
|
||||
-- Optional precomputed re-engagement priority (job-set).
|
||||
maybe_reengagement_score INT UNSIGNED DEFAULT NULL,
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uq_user (payments_namespace, user_token),
|
||||
KEY idx_lifetime (payments_namespace, lifetime_net_spend_usd_cents),
|
||||
KEY idx_last_payment_at (payments_namespace, last_payment_at),
|
||||
KEY idx_first_payment_at (payments_namespace, first_payment_at),
|
||||
KEY idx_spend_7d (payments_namespace, net_spend_7d_usd_cents),
|
||||
KEY idx_spend_30d (payments_namespace, net_spend_30d_usd_cents),
|
||||
KEY idx_reengagement (payments_namespace, maybe_reengagement_score),
|
||||
-- Sort / range-filter by recency. idx_weeks_since_last_spend also powers every
|
||||
-- "active in the last N weeks" query (weeks_since_last_spend < N).
|
||||
KEY idx_weeks_since_last_spend (payments_namespace, weeks_since_last_spend),
|
||||
KEY idx_days_since_last_payment (payments_namespace, days_since_last_payment)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -17,7 +17,7 @@ CREATE TABLE staff_audit_logs (
|
||||
maybe_entity_type VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- The token of the entity mutated (if any)
|
||||
maybe_entity_token VARCHAR(32) NOT NULL,
|
||||
maybe_entity_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- The staff user who performed the action.
|
||||
staff_user_token VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE uploaded_video_notes (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (used internally for lookups and as a foreign key).
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- The uploaded video this note is about (FK to uploaded_videos.token).
|
||||
uploaded_video_token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== SUBMISSION ==========
|
||||
|
||||
-- Original filename of the submitted clip, as provided by the user.
|
||||
maybe_filename VARCHAR(512) DEFAULT NULL,
|
||||
|
||||
-- The model type the submitter claims this video is.
|
||||
maybe_reported_model_type VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Free-form custom model name when the submitter's model isn't a known type.
|
||||
maybe_reported_model_name VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- The website / platform the video was obtained from.
|
||||
maybe_website VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Free-form website when it isn't a known option.
|
||||
maybe_other_website VARCHAR(128) DEFAULT NULL,
|
||||
|
||||
-- Free-form comments from the submitter.
|
||||
maybe_comments VARCHAR(1024) DEFAULT NULL,
|
||||
|
||||
-- ========== CONTACT & FLAGS ==========
|
||||
|
||||
-- Optional email address for follow-up.
|
||||
maybe_email_address VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Whether the submitter consents to us sharing the report.
|
||||
can_share_report BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- Whether the submitter believes they were scammed.
|
||||
was_scammed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
|
||||
-- ========== IP ADDRESSES ==========
|
||||
|
||||
-- IP address that submitted the note. Wide enough for IPv4/IPv6.
|
||||
comment_create_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address that last updated the note. Wide enough for IPv4/IPv6.
|
||||
maybe_comment_update_ip_address VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
-- ========== VERSION ==========
|
||||
|
||||
-- Optimistic-concurrency "vector clock". Bump by 1 on every update — set in
|
||||
-- the UPDATE statement (MySQL can't auto-increment a plain column via schema).
|
||||
version INT(8) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
KEY fk_uploaded_video_token (uploaded_video_token)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,65 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
CREATE TABLE uploaded_videos (
|
||||
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
|
||||
-- Effective "primary key" (used internally for lookups and as a foreign key).
|
||||
token VARCHAR(32) NOT NULL,
|
||||
|
||||
-- ========== FILE ==========
|
||||
|
||||
-- SHA-1 checksum of the uploaded bytes (40 hex chars). De-dupes uploads.
|
||||
sha1_checksum VARCHAR(40) NOT NULL,
|
||||
|
||||
-- Size of the uploaded file in bytes.
|
||||
filesize_bytes INT(10) UNSIGNED NOT NULL,
|
||||
|
||||
-- Original filename of the uploaded clip, as provided by the user.
|
||||
maybe_filename VARCHAR(512) DEFAULT NULL,
|
||||
|
||||
-- Pixel dimensions, when they could be determined.
|
||||
maybe_width INT(10) UNSIGNED DEFAULT NULL,
|
||||
maybe_height INT(10) UNSIGNED DEFAULT NULL,
|
||||
|
||||
-- Resolution label (e.g. "1080p", "1920x1080"), when known.
|
||||
maybe_resolution VARCHAR(12) DEFAULT NULL,
|
||||
|
||||
-- ========== DETECTION ==========
|
||||
|
||||
-- Detected provenance family (e.g. "seedance", "veo", "sora", "dreamina", "kling").
|
||||
maybe_detected_model_family VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Detected model type / variant (e.g. "full", "fast", "mini").
|
||||
maybe_detected_model_type VARCHAR(32) DEFAULT NULL,
|
||||
|
||||
-- Free-form report / analysis blob for this upload.
|
||||
maybe_report MEDIUMTEXT DEFAULT NULL,
|
||||
|
||||
-- ========== IP ADDRESSES ==========
|
||||
|
||||
-- IP address that uploaded the video. Wide enough for IPv4/IPv6.
|
||||
upload_ip_address VARCHAR(40) NOT NULL,
|
||||
|
||||
-- IP address that last updated the row. Wide enough for IPv4/IPv6.
|
||||
maybe_updated_ip_address VARCHAR(40) DEFAULT NULL,
|
||||
|
||||
-- ========== VERSION ==========
|
||||
|
||||
-- Optimistic-concurrency "vector clock". Bump by 1 on every update — set in
|
||||
-- the UPDATE statement (MySQL can't auto-increment a plain column via schema).
|
||||
version INT(8) UNSIGNED NOT NULL DEFAULT 0,
|
||||
|
||||
-- ========== TIMESTAMPS ==========
|
||||
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
|
||||
-- INDICES --
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (token),
|
||||
UNIQUE KEY (sha1_checksum),
|
||||
KEY index_maybe_detected_model_family (maybe_detected_model_family)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
@@ -0,0 +1,625 @@
|
||||
# ArtCraft Omni API
|
||||
|
||||
> # 📚 Full API reference: **<https://storyteller-docs.netlify.app/>**
|
||||
>
|
||||
> **Every ArtCraft API endpoint is documented there.** This guide covers only the Omni API video
|
||||
> endpoints — for everything else, start at the link above.
|
||||
|
||||
The **Omni API** is the API-key–authenticated surface for ArtCraft generation. It mirrors the
|
||||
in-app "Omni Gen" endpoints but is designed for programmatic use: you authenticate with an API key
|
||||
in the `Authorization` header instead of a browser session cookie.
|
||||
|
||||
This guide covers **video generation from URL inputs** — passing image, video, and audio reference
|
||||
URLs directly. The server downloads each URL, stores it as a media file on your account, and then
|
||||
runs generation against it. You never have to upload files yourself.
|
||||
|
||||
> All examples send **no cookies**. The Omni API is authenticated purely by your API key. If you
|
||||
> send session cookies they are ignored.
|
||||
|
||||
---
|
||||
|
||||
## Base URLs
|
||||
|
||||
| Environment | Base URL |
|
||||
| ------------- | ------------------------------ |
|
||||
| Development | `http://localhost:12345` |
|
||||
| Production | `https://api.storyteller.ai` |
|
||||
|
||||
All paths below are relative to the base URL (e.g. `POST /v1/omni_api/generate/video`).
|
||||
|
||||
The examples in this guide use the **development** base URL. To run against production, swap
|
||||
`http://localhost:12345` for `https://api.storyteller.ai`.
|
||||
|
||||
Note: Our APIs are hosted on "`api.storyteller.ai`", not "`api.getartcraft.com`". This is not a
|
||||
typo. Our Cloudflare CDN URLs are `cdn-2.fakeyou.com`.
|
||||
|
||||
---
|
||||
|
||||
## Getting an API key
|
||||
|
||||
Create and manage API keys from the ArtCraft web app:
|
||||
|
||||
1. Go to **<https://app.getartcraft.com/>**
|
||||
2. Click **Account → Settings → API Keys** (your account must have API keys enabled from our staff)
|
||||
3. Create a key and copy it. The secret is shown **once** — store it somewhere safe.
|
||||
|
||||
> **Don't see the API Keys section?** API access is gated per account. Contact the Artcraft team
|
||||
> to enable it.
|
||||
|
||||
### What an API key looks like
|
||||
|
||||
- Prefix: `artcraft_api_`
|
||||
- Followed by 40 random lowercase Crockford-base32 characters (`0-9 a-z`, excluding `i l o u`)
|
||||
- Total length: **53 characters**, e.g. `artcraft_api_3k7q9w0xv2hs5n8m4d6r1t8y0p2s4f6h8j0k2m4n6`
|
||||
|
||||
Treat the key like a password: never commit it to source control or paste it into logs.
|
||||
|
||||
---
|
||||
|
||||
## Authentication
|
||||
|
||||
Send the secret key in the `Authorization` header. Three forms are accepted:
|
||||
|
||||
```
|
||||
Authorization: Bearer artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
Authorization: Key artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
Authorization: artcraft_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
||||
```
|
||||
|
||||
`Bearer` is recommended. **Do not send cookies.**
|
||||
|
||||
---
|
||||
|
||||
## Video generation — `POST /v1/omni_api/generate/video`
|
||||
|
||||
Generates a video and returns an inference job token you can poll for results.
|
||||
|
||||
### Request body
|
||||
|
||||
| Field | Type | Notes |
|
||||
| ---------------------------- | --------------- | ----- |
|
||||
| `model` | string | Generation model, e.g. `seedance_2p0`. **Required.** |
|
||||
| `prompt` | string | Text prompt. |
|
||||
| `duration_seconds` | integer | Length of the output video, e.g. `6`. |
|
||||
| `aspect_ratio` | string | e.g. `wide_sixteen_by_nine`, `tall_nine_by_sixteen`, `square`. |
|
||||
| `idempotency_token` | string (UUID) | **Required.** A fresh UUID per request; prevents accidental duplicates. |
|
||||
| `negative_prompt` | string | Optional. |
|
||||
| `resolution` | string | Optional, e.g. `seven_twenty_p`, `ten_eighty_p`. |
|
||||
| `generate_audio` | boolean | Optional. |
|
||||
| **URL inputs** (this guide) | | |
|
||||
| `start_frame_image_url` | string (URL) | Starting keyframe image. |
|
||||
| `end_frame_image_url` | string (URL) | Ending keyframe image. |
|
||||
| `reference_image_urls` | array of URL | Reference images. |
|
||||
| `reference_video_urls` | array of URL | Reference videos (`mp4`). |
|
||||
| `reference_audio_urls` | array of URL | Reference audio (`wav`, `mp3`, `ogg`, …). |
|
||||
| **Media-token inputs** | | |
|
||||
| `start_frame_image_media_token`, `end_frame_image_media_token`, `reference_image_media_tokens`, `reference_video_media_tokens`, `reference_audio_media_tokens` | token(s) | Use these instead if you've already uploaded media. |
|
||||
|
||||
#### Rules for URL inputs
|
||||
|
||||
- Each URL field and its media-token counterpart are **mutually exclusive**. Sending both
|
||||
`reference_image_urls` and `reference_image_media_tokens`, for example, returns:
|
||||
`Either reference_image_media_tokens or reference_image_urls must be set, not both`.
|
||||
- Every URL must start with `http://` or `https://`, otherwise:
|
||||
`URL must start with http:// or https://, bad URL: <url>`.
|
||||
- Redirects are followed (up to 10), so CDN/redirecting URLs work.
|
||||
- The file type is detected from the downloaded bytes. Images must be a real image
|
||||
(`jpeg`/`png`/`gif`/`webp`), reference videos must be `mp4`, reference audio must be a
|
||||
supported audio type (`wav`, `mp3`, `aac`, `ogg`, `flac`, …).
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"inference_job_token": "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"all_job_tokens": ["jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"]
|
||||
}
|
||||
```
|
||||
|
||||
Use `inference_job_token` to poll for the finished video. `all_job_tokens` lists every job created
|
||||
(batch requests can create more than one).
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
The examples reuse these public sample URLs:
|
||||
|
||||
- Images (Wikimedia Commons): an Atlantic puffin, the Matterhorn, a toco toucan
|
||||
- Video: a short sample `mp4`
|
||||
- Audio: a short sample `wav`
|
||||
|
||||
Replace `YOUR_API_KEY` with your `artcraft_api_…` secret, and generate a fresh `idempotency_token`
|
||||
(UUID) for every request.
|
||||
|
||||
---
|
||||
|
||||
### Example 1 — Starting-frame Seedance (`start_frame_image_url`)
|
||||
|
||||
Animate outward from a single starting image.
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": "'"$(uuidgen)"'",
|
||||
"start_frame_image_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900"
|
||||
}'
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/v1/omni_api/generate/video",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
json={
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": str(uuid.uuid4()),
|
||||
"start_frame_image_url": "https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
},
|
||||
timeout=240,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "seedance_2p0",
|
||||
prompt:
|
||||
"An Atlantic puffin stands on a windy cliff edge and turns its head as morning light catches its colorful beak.",
|
||||
duration_seconds: 6,
|
||||
aspect_ratio: "wide_sixteen_by_nine",
|
||||
idempotency_token: randomUUID(),
|
||||
start_frame_image_url:
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(resp.status, await resp.json());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 2 — Image-reference Seedance (`reference_image_urls`)
|
||||
|
||||
Guide the generation with one or more reference images.
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": "'"$(uuidgen)"'",
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900"
|
||||
]
|
||||
}'
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/v1/omni_api/generate/video",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
json={
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": str(uuid.uuid4()),
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
},
|
||||
timeout=240,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "seedance_2p0",
|
||||
prompt:
|
||||
"A whimsical journey: an Atlantic puffin perches on a cliff beneath the snow-capped Matterhorn while a brilliant toco toucan watches from a misty rainforest canopy.",
|
||||
duration_seconds: 6,
|
||||
aspect_ratio: "wide_sixteen_by_nine",
|
||||
idempotency_token: randomUUID(),
|
||||
reference_image_urls: [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(resp.status, await resp.json());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Example 3 — Image + video reference Seedance (`reference_image_urls` + `reference_video_urls`)
|
||||
|
||||
Combine reference images with a reference video (and, optionally, reference audio). This is the
|
||||
fullest URL-driven request.
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://localhost:12345/v1/omni_api/generate/video \
|
||||
-H "Authorization: Bearer YOUR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": "'"$(uuidgen)"'",
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900"
|
||||
],
|
||||
"reference_video_urls": ["https://download.samplelib.com/mp4/sample-5s.mp4"],
|
||||
"reference_audio_urls": ["https://download.samplelib.com/wav/sample-6s.wav"]
|
||||
}'
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
|
||||
resp = requests.post(
|
||||
f"{BASE_URL}/v1/omni_api/generate/video",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
json={
|
||||
"model": "seedance_2p0",
|
||||
"prompt": "An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.",
|
||||
"duration_seconds": 6,
|
||||
"aspect_ratio": "wide_sixteen_by_nine",
|
||||
"idempotency_token": str(uuid.uuid4()),
|
||||
"reference_image_urls": [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
"reference_video_urls": ["https://download.samplelib.com/mp4/sample-5s.mp4"],
|
||||
"reference_audio_urls": ["https://download.samplelib.com/wav/sample-6s.wav"],
|
||||
},
|
||||
timeout=240,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
print(resp.json())
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
import { randomUUID } from "node:crypto";
|
||||
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/generate/video`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${API_KEY}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: "seedance_2p0",
|
||||
prompt:
|
||||
"An Atlantic puffin stands on a windy cliff edge; the scene sweeps up to the snow-capped Matterhorn at sunrise before settling on a brilliant toco toucan in a misty rainforest canopy. The camera glides with the motion of the reference video, set to the mood of the reference audio.",
|
||||
duration_seconds: 6,
|
||||
aspect_ratio: "wide_sixteen_by_nine",
|
||||
idempotency_token: randomUUID(),
|
||||
reference_image_urls: [
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Puffin_%28Fratercula_arctica%29.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Matterhorn_from_Domh%C3%BCtte_-_2.jpg?width=900",
|
||||
"https://commons.wikimedia.org/wiki/Special:FilePath/Toco_toucan.jpg?width=900",
|
||||
],
|
||||
reference_video_urls: ["https://download.samplelib.com/mp4/sample-5s.mp4"],
|
||||
reference_audio_urls: ["https://download.samplelib.com/wav/sample-6s.wav"],
|
||||
}),
|
||||
});
|
||||
|
||||
console.log(resp.status, await resp.json());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checking job status
|
||||
|
||||
Generation is asynchronous. The generate call returns an `inference_job_token`; poll the job-status
|
||||
endpoint until the job reaches a terminal state, then read the result URL.
|
||||
|
||||
### Endpoints
|
||||
|
||||
- **Single job:** `GET /v1/omni_api/job_status/job/{token}`
|
||||
- **Batch (many jobs):** `GET /v1/omni_api/job_status/batch?tokens=jinf_aaa&tokens=jinf_bbb`
|
||||
|
||||
Both require the same `Authorization: Bearer …` header (no cookies).
|
||||
|
||||
### Response shape
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"state": {
|
||||
"job_token": "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"request": {
|
||||
"inference_category": "video_generation",
|
||||
"maybe_prompt_token": "prompt_xxxxxxxxxxxxxxxxxxxxxxxxx",
|
||||
"maybe_model_type": "seedance_2p0",
|
||||
"maybe_model_token": null
|
||||
},
|
||||
"status": {
|
||||
"status": "pending",
|
||||
"maybe_first_started_at": null,
|
||||
"maybe_failure_category": null,
|
||||
"progress_percentage": 0
|
||||
},
|
||||
"maybe_result": null,
|
||||
"created_at": "2026-06-24T04:06:14Z",
|
||||
"updated_at": "2026-06-24T04:06:14Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `state.status.status` — the job state. Keep polling while it's `pending`, `started`, or
|
||||
`attempt_failed`. **Terminal** states are `complete_success`, `complete_failure`, `dead`,
|
||||
`cancelled_by_user`, and `cancelled_by_system`.
|
||||
- `state.status.progress_percentage` — an integer 0–100.
|
||||
- `state.maybe_result` — `null` until the job succeeds. On `complete_success` it contains the
|
||||
finished media; the playable/downloadable URL is **`state.maybe_result.media_links.cdn_url`**.
|
||||
For video, `media_links.maybe_video_previews` also carries still/animated preview URLs.
|
||||
|
||||
The batch endpoint returns `{ "success": true, "job_states": [ … ] }` where each entry is the same
|
||||
`state` payload as above.
|
||||
|
||||
### Where the result video / image lives
|
||||
|
||||
When `state.status.status` is `complete_success`, the finished media is under `state.maybe_result`:
|
||||
|
||||
- **`state.maybe_result.media_links.cdn_url`** — the primary, playable/downloadable URL of the
|
||||
finished asset. **This is the field you want.** For a video job it's the `.mp4`; for an image job
|
||||
it's the image file (`.jpg` / `.png` / etc.).
|
||||
- `state.maybe_result.entity_token` — the `m_…` media-file token for the result.
|
||||
- `state.maybe_result.media_links.maybe_video_previews` — **video jobs only**. Contains a `still`
|
||||
(poster `.jpg`) and an `animated` (`.gif`) preview URL, plus `{WIDTH}` thumbnail templates. This
|
||||
is `null` for image jobs.
|
||||
- `state.maybe_result.media_links.maybe_thumbnail_template` — **image jobs only**. A URL template;
|
||||
replace `{WIDTH}` with a pixel width to fetch a resized thumbnail. `null` for video jobs.
|
||||
|
||||
**Example — completed video job:**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"state": {
|
||||
"job_token": "jinf_9fcn2qv4dv3vvbrrxbnmfgen06v",
|
||||
"request": {
|
||||
"inference_category": "video_generation",
|
||||
"maybe_prompt_token": "prompt_q53tp4rh97qh8q159p21tazzt",
|
||||
"maybe_model_type": "seedance_2p0",
|
||||
"maybe_model_token": null
|
||||
},
|
||||
"status": {
|
||||
"status": "complete_success",
|
||||
"maybe_first_started_at": "2026-06-24T04:07:11Z",
|
||||
"maybe_failure_category": null,
|
||||
"progress_percentage": 100
|
||||
},
|
||||
"maybe_result": {
|
||||
"entity_type": "media_file",
|
||||
"entity_token": "m_ss682134tx19yhqs5y0nbnd0bd59a6",
|
||||
"media_links": {
|
||||
"cdn_url": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4",
|
||||
"maybe_thumbnail_template": null,
|
||||
"maybe_video_previews": {
|
||||
"still": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb.jpg",
|
||||
"animated": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb.gif",
|
||||
"still_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb-{WIDTH}.jpg",
|
||||
"animated_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4-thumb-{WIDTH}.gif"
|
||||
}
|
||||
},
|
||||
"maybe_successfully_completed_at": "2026-06-24T04:17:30Z"
|
||||
},
|
||||
"created_at": "2026-06-24T04:06:14Z",
|
||||
"updated_at": "2026-06-24T04:17:30Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ The video is at
|
||||
`https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/8/p/n/0/4/8pn04v7q3vfhf60wy173f6awcwkre1qj/artcraft_8pn04v7q3vfhf60wy173f6awcwkre1qj.mp4`
|
||||
(example, not a live URL).
|
||||
|
||||
**Example — completed image job** (from `POST /v1/omni_api/generate/image`):
|
||||
|
||||
```json
|
||||
{
|
||||
"maybe_result": {
|
||||
"entity_type": "media_file",
|
||||
"entity_token": "m_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6",
|
||||
"media_links": {
|
||||
"cdn_url": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg",
|
||||
"maybe_thumbnail_template": "https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg-thumb-{WIDTH}.jpg",
|
||||
"maybe_video_previews": null
|
||||
},
|
||||
"maybe_successfully_completed_at": "2026-06-24T04:09:02Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
→ The image is at
|
||||
`https://pub-c8a4a5bdbdb048f286b77bdf9f786ff2.r2.dev/media/3/k/q/9/w/3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6/artcraft_3kq9w0xv2hs5n8m4d6r1t8y0p2s4f6.jpg`
|
||||
(example, not a live URL).
|
||||
|
||||
### Poll until complete
|
||||
|
||||
#### bash
|
||||
|
||||
```bash
|
||||
JOB_TOKEN="jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
while :; do
|
||||
resp=$(curl -s "http://localhost:12345/v1/omni_api/job_status/job/$JOB_TOKEN" \
|
||||
-H "Authorization: Bearer YOUR_API_KEY")
|
||||
status=$(echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin)['state']['status']['status'])")
|
||||
echo "status=$status"
|
||||
case "$status" in
|
||||
complete_success)
|
||||
echo "$resp" | python3 -c "import sys,json;print(json.load(sys.stdin)['state']['maybe_result']['media_links']['cdn_url'])"
|
||||
break ;;
|
||||
complete_failure|dead|cancelled_by_user|cancelled_by_system)
|
||||
echo "job ended without a result: $status"; break ;;
|
||||
esac
|
||||
sleep 5
|
||||
done
|
||||
```
|
||||
|
||||
#### python
|
||||
|
||||
```python
|
||||
import time
|
||||
import requests
|
||||
|
||||
BASE_URL = "http://localhost:12345" # production: https://api.storyteller.ai
|
||||
API_KEY = "YOUR_API_KEY"
|
||||
JOB_TOKEN = "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx"
|
||||
|
||||
TERMINAL_FAILURES = {"complete_failure", "dead", "cancelled_by_user", "cancelled_by_system"}
|
||||
|
||||
while True:
|
||||
resp = requests.get(
|
||||
f"{BASE_URL}/v1/omni_api/job_status/job/{JOB_TOKEN}",
|
||||
headers={"Authorization": f"Bearer {API_KEY}"},
|
||||
timeout=30,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
state = resp.json()["state"]
|
||||
status = state["status"]["status"]
|
||||
print("status:", status, state["status"]["progress_percentage"], "%")
|
||||
|
||||
if status == "complete_success":
|
||||
print("result URL:", state["maybe_result"]["media_links"]["cdn_url"])
|
||||
break
|
||||
if status in TERMINAL_FAILURES:
|
||||
print("job ended without a result:", status)
|
||||
break
|
||||
time.sleep(5)
|
||||
```
|
||||
|
||||
#### javascript
|
||||
|
||||
```javascript
|
||||
const BASE_URL = "http://localhost:12345"; // production: https://api.storyteller.ai
|
||||
const API_KEY = "YOUR_API_KEY";
|
||||
const JOB_TOKEN = "jinf_xxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
|
||||
const TERMINAL_FAILURES = new Set([
|
||||
"complete_failure", "dead", "cancelled_by_user", "cancelled_by_system",
|
||||
]);
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
while (true) {
|
||||
const resp = await fetch(`${BASE_URL}/v1/omni_api/job_status/job/${JOB_TOKEN}`, {
|
||||
headers: { Authorization: `Bearer ${API_KEY}` },
|
||||
});
|
||||
const { state } = await resp.json();
|
||||
const status = state.status.status;
|
||||
console.log("status:", status, state.status.progress_percentage, "%");
|
||||
|
||||
if (status === "complete_success") {
|
||||
console.log("result URL:", state.maybe_result.media_links.cdn_url);
|
||||
break;
|
||||
}
|
||||
if (TERMINAL_FAILURES.has(status)) {
|
||||
console.log("job ended without a result:", status);
|
||||
break;
|
||||
}
|
||||
await sleep(5000);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes & troubleshooting
|
||||
|
||||
- **Always send a fresh `idempotency_token`** (UUID). Reusing one is rejected as a duplicate.
|
||||
- **No cookies.** The endpoint authenticates only via the `Authorization` header.
|
||||
- **URL must be `http(s)`** and reachable by the server; redirects are followed.
|
||||
- **Type mismatches** (e.g. a `.webm` passed as a reference video, which requires `mp4`) are
|
||||
rejected with a `400` describing the unpermitted type.
|
||||
- **`401 Unauthorized`** — missing/invalid API key, or the key's owner is banned.
|
||||
- **`402 Payment required`** — insufficient credits/balance.
|
||||
- There is a companion image endpoint, `POST /v1/omni_api/generate/image`, which accepts
|
||||
`image_urls` (mutually exclusive with `image_media_tokens`) following the same rules.
|
||||
@@ -0,0 +1,203 @@
|
||||
# PageDraw — Visual Design & Architecture Review
|
||||
|
||||
**Surface:** `@storyteller/ui-pagedraw` image editor + its `artcraft-webapp` integration (`/edit-image`).
|
||||
**Lens:** Awwwards / Linear-tier "premium app chrome" rubric, adapted for an in-product creative tool.
|
||||
**Date:** 2026-06-15 · **Branch:** `worktree-pagedraw-webapp`
|
||||
|
||||
---
|
||||
|
||||
## 0. How to read this
|
||||
|
||||
PageDraw is a **professional canvas tool**, not a marketing page. So the elite-design rubric is
|
||||
applied in its *app-chrome* register — the north star is **Figma / Linear / Photoshop-web**, not an
|
||||
Awwwards landing page. That means:
|
||||
|
||||
- **Applies:** token discipline, concentric radii, hairline + inset-highlight surfaces ("double-bezel"),
|
||||
island/pill controls, custom-eased micro-motion, z-index hygiene, premium type ramp.
|
||||
- **Does _not_ apply:** `py-40` macro-whitespace, hero typography, editorial-split layouts, scroll-reveal
|
||||
entrances. A tool surface is dense and persistent by design; forcing landing-page whitespace would hurt it.
|
||||
|
||||
Citations are `file:line` against the current tree.
|
||||
|
||||
---
|
||||
|
||||
## 1. Verdict
|
||||
|
||||
| Axis | Score | One-liner |
|
||||
|---|---|---|
|
||||
| Color system | ⚠️ 5/10 | Good token foundation, but hardcoded greys (`#1b1b1b`, `#303030`, `#404040`) leak through a *themeable lib*. |
|
||||
| Typography | ❌ 3/10 | No type ramp, no font-family ownership; rides whatever the host sets. Banned-by-rubric default stack risk. |
|
||||
| Radii | ⚠️ 4/10 | Five radius scales in play (`sm`→`2xl`→`full`) with no concentric nesting. |
|
||||
| Surfaces (border/shadow) | ⚠️ 5/10 | Flat `shadow-lg` everywhere; no inset highlights; no double-bezel. Reads "functional", not "machined". |
|
||||
| Motion | ❌ 3/10 | Only `transition-colors`/`ease-in-out`, durations 75–200ms; **zero custom easing**, no spring, no entrance choreography. |
|
||||
| Layout / z-index | ⚠️ 6/10 | Clean fixed-panel composition, but a `z-[9999999]` escape hatch and a 6-tier ad-hoc z-stack. |
|
||||
| Icons | ✅ 7/10 | FontAwesome Pro (solid + regular) — acceptable, but solid weight is heavier than the rubric's "ultra-light line" ideal. |
|
||||
| Architecture | ⚠️ 5/10 | Clean adapter boundary; but `PaintSurface.tsx` (2005 LOC), `SceneState.ts` (1539), `PageDraw.tsx` (1292) are oversized, and the lib hardcodes host-theme colors. |
|
||||
|
||||
**Bottom line:** the editor is *structurally competent and functional* but visually reads as
|
||||
"capable internal tool," not "$150k product surface." The single highest-leverage fix is a **shared
|
||||
design-token layer** (color + radius + motion + type), because today the lib both (a) reaches for
|
||||
host tokens (`bg-ui-panel`, `text-base-fg`) *and* (b) hardcodes its own greys — so it can never be
|
||||
fully reskinned by the webapp vs. the desktop app.
|
||||
|
||||
---
|
||||
|
||||
## 2. What's already good (keep)
|
||||
|
||||
- **Token-aware base.** Most surfaces use semantic tokens (`bg-ui-panel`, `bg-ui-controls`,
|
||||
`text-base-fg`, `border-ui-panel-border`) — the right instinct (SideToolbar, PromptEditor, HistoryStack).
|
||||
- **Clean platform boundary.** The lib has zero REST/Tauri imports; everything I/O flows through
|
||||
`PageDrawAdapter` (`adapter.ts`). The webapp adapter (`web-adapter.tsx`) and desktop adapter stay
|
||||
swappable. This is genuinely good architecture and should be protected.
|
||||
- **Performance-minded canvas.** Composite pre-baking on idle (`PageDraw.tsx` ~661–710) and worker
|
||||
encoding (`generatePipeline.worker.ts`) keep the generate click off the main thread. Don't regress this.
|
||||
- **CORS already handled** for browser canvas export (`crossOrigin="anonymous"` in `Node.ts:134`,
|
||||
`SceneState.ts:1473`, `imageHelpers.ts:16`) — the web port works because of this.
|
||||
|
||||
---
|
||||
|
||||
## 3. Findings & recommendations
|
||||
|
||||
### 3.1 Color — stop hardcoding greys in a themeable lib ⚠️ P0
|
||||
**Current:** semantic tokens *and* raw hex coexist:
|
||||
- `#1b1b1b` pegboard (`pagedraw.css:6`), `#1A1A1A`/`#333333` context menu (`ContextMenu.tsx:44`),
|
||||
`#303030`/`#404040` dividers + popovers (`SideToolbar.tsx:381,550`), `bg-zinc-700/600` swatch
|
||||
(`SideToolbar.tsx:173`), plus ad-hoc `bg-blue-600/500` accents (`PageDraw.tsx:157`, base selector).
|
||||
- Accent color is inconsistent: the lib's primary action uses raw **blue-600**, while the webapp's
|
||||
design system uses `--primary` / `brand-primary`. So the editor's blue doesn't match the product.
|
||||
|
||||
**Why it fails the bar:** a premium component library exposes *one* surface ramp and *one* accent, both
|
||||
driven by tokens, so it reskins per host. Hardcoded greys mean the desktop and web themes can drift, and
|
||||
the raw `blue-600` clashes with `brand-primary` used elsewhere in the webapp.
|
||||
|
||||
**Do:**
|
||||
- Replace every raw grey with the existing ramp: `#1b1b1b`→`bg-ui-background`, `#1A1A1A`→`bg-ui-panel`,
|
||||
`#303030`/`#404040`→`border-ui-panel-border` / `bg-ui-controls`. For the pegboard, add a token
|
||||
`--st-canvas` so hosts can theme the canvas backdrop.
|
||||
- Replace `blue-600/500` accents with `bg-primary` / `text-primary` (the token the rest of the webapp uses),
|
||||
so the Generate/Edit-3D buttons match `brand-primary`.
|
||||
- Move the react-colorful overrides off pure `rgb(255,255,255)` borders (`pagedraw.css:15,19`) onto
|
||||
`white/15` hairlines for consistency with the rest of the chrome.
|
||||
|
||||
### 3.2 Typography — own a type ramp ❌ P0
|
||||
**Current:** no `font-family` anywhere in the lib (grep: 0 hits); only `text-xs/sm/base/lg/xl` +
|
||||
`font-medium/semibold/bold`. The editor inherits whatever the host's body font is.
|
||||
|
||||
**Why it fails the bar:** the rubric explicitly bans the default web stack (Inter/Roboto/Arial/Helvetica).
|
||||
Inheriting an unspecified stack means the tool can render in a banned font with no recourse, and there's
|
||||
no deliberate hierarchy (numerics in sliders, labels, modal titles all share one undifferentiated ramp).
|
||||
|
||||
**Do:**
|
||||
- Adopt the product's display/grotesk token (e.g. a `font-sans` mapped to Geist / Plus Jakarta Sans at
|
||||
the app level) and reference it in the lib's root container, not per-component.
|
||||
- Define a small semantic ramp: `text-[11px] tracking-[0.16em] uppercase` for toolbar/section labels,
|
||||
`text-sm` for controls, `text-base font-semibold` for modal titles. Apply `tabular-nums` to slider
|
||||
read-outs (`SliderWithIndicator.tsx`) and dimension fields (`BlankCanvasModal`) so numbers don't jitter.
|
||||
|
||||
### 3.3 Radii — collapse to a concentric scale ⚠️ P1
|
||||
**Current:** `rounded-sm / md / lg / xl / 2xl / full` all appear, with no parent→child relationship
|
||||
(e.g. `rounded-2xl` card at `PageDraw.tsx:1123`, `rounded-xl` panels in SideToolbar, `rounded-lg`
|
||||
buttons in InpaintToolBar).
|
||||
|
||||
**Do:** pick a 3-step system — **`rounded-2xl` shells, `rounded-xl` inner cards, `rounded-lg` controls,
|
||||
`rounded-full` pills** — and apply concentrically (the "double-bezel" curve math: inner radius =
|
||||
outer − padding). Drop `rounded-sm`/`rounded-md` from the editor entirely.
|
||||
|
||||
### 3.4 Surfaces — add the "machined" double-bezel ⚠️ P1
|
||||
**Current:** panels are a single `bg-ui-panel` + `border` + `shadow-lg`. No nested enclosure, no inset
|
||||
top-highlight. Reads flat.
|
||||
|
||||
**Do (for the four floating panels — SideToolbar, HistoryStack, PromptEditor, InpaintToolBar):**
|
||||
- Outer shell: `bg-ui-panel/80 ring-1 ring-white/10 backdrop-blur-xl p-1.5 rounded-2xl`.
|
||||
- Inner core: own bg + `shadow-[inset_0_1px_0_rgba(255,255,255,0.08)] rounded-xl`.
|
||||
- This is the single change that most moves the surface from "tool" to "premium hardware." The panels are
|
||||
already `fixed` (`PageDraw.tsx:1144,1175,1195`), so `backdrop-blur` here is GPU-safe per the perf rules.
|
||||
|
||||
### 3.5 Motion — replace linear/ease with spring-eased micro-motion ❌ P0 (cheap, high impact)
|
||||
**Current:** `transition-colors`/`transition-all` with `ease-in-out` and `duration-75/150/200`
|
||||
(`SideToolbar.tsx:381,550`, `SliderWithIndicator.tsx:65`, `ContextMenu.tsx:53`). No custom easing,
|
||||
no entrance/exit on popovers, color pickers, or the context menu (they pop instantly).
|
||||
|
||||
**Why it fails the bar:** instant state changes and `ease-in-out` are explicitly banned. A pro tool still
|
||||
deserves *interpolated* motion — it's what separates Figma from a jQuery plugin.
|
||||
|
||||
**Do:**
|
||||
- Standardize on one curve: `transition-[transform,opacity,background-color] duration-300
|
||||
ease-[cubic-bezier(0.32,0.72,0,1)]`.
|
||||
- Tool buttons: `active:scale-[0.97]` press physics; selected tool gets a spring-eased indicator slide
|
||||
rather than a hard background swap.
|
||||
- Popovers / color picker / context menu: animate in with `opacity-0 translate-y-1 scale-[0.98]` →
|
||||
resolved, ~180ms. Transform+opacity only (perf rule).
|
||||
- History tiles: stagger their `opacity` reveal when a generation lands (they already use
|
||||
`transition-opacity`, just add per-index delay).
|
||||
|
||||
### 3.6 Layout & z-index — tame the stack ⚠️ P1
|
||||
**Current:** clean fixed-panel composition, but z-index is ad-hoc across 6 tiers and includes a
|
||||
`z-[9999999]` on the context menu (`ContextMenu.tsx:44`) and a one-off `z-[60]` (`PageDraw.tsx:237`).
|
||||
|
||||
**Do:** define a named scale in one place — `canvas:0, chrome:10, floating-controls:20, overlay-button:40,
|
||||
menu:50` — and replace the magic numbers. The portal menu only needs to clear the chrome, not `9999999`.
|
||||
|
||||
### 3.7 Responsive — declare intent ⚠️ P2
|
||||
**Current:** essentially desktop-only; fixed sidebars assume a wide viewport; only LightboxDetails has
|
||||
`sm:` variants. The webapp wrapper scopes the lib correctly (`pagedraw.tsx` `translateZ(0)` containing
|
||||
block) but there's no mobile story.
|
||||
|
||||
**Do:** mirror the Edit-3D page's explicit **mobile gate** (`pagescene.tsx` `MobileGate`) on `/edit-image`
|
||||
rather than letting fixed toolbars overlap on touch. A precision canvas tool is legitimately desktop-first;
|
||||
just say so deliberately instead of degrading silently.
|
||||
|
||||
---
|
||||
|
||||
## 4. Architecture notes
|
||||
|
||||
- **Oversized modules.** `PaintSurface.tsx` (2005 LOC), `SceneState.ts` (1539), `PageDraw.tsx` (1292).
|
||||
These are review/maintenance hotspots. Suggest extracting from `SceneState.ts`: history/undo slice,
|
||||
base-image-load slice, and generation/pending slice into composable Zustand slices. `PageDraw.tsx`'s
|
||||
generate-flow (cases 1–3, ~780–868) could move to a `useGenerate()` hook.
|
||||
- **Theme ownership.** Per §3.1, the lib should *only* consume tokens. Today it mixes tokens + hex, which
|
||||
defeats the adapter pattern's promise (one component, many themed hosts).
|
||||
- **Adapter surface is healthy.** `PageDrawAdapter` is small and intentional; the web vs. Tauri split is
|
||||
clean. The webapp's `enqueueInpaint` stub and 402→credits-modal handling are explicit and documented.
|
||||
- **Icon weight.** FontAwesome **solid** is the heaviest option; if a line/light set is available
|
||||
(Phosphor/Remix or FA light), the toolbar would read more refined. Low priority.
|
||||
|
||||
---
|
||||
|
||||
## 5. Prioritized roadmap
|
||||
|
||||
| P | Item | Effort | Payoff |
|
||||
|---|---|---|---|
|
||||
| **P0** | Token sweep: kill hardcoded greys; unify accent on `--primary` (§3.1) | M | Highest — enables true theming + product-consistent accent |
|
||||
| **P0** | Own a font-family + semantic type ramp at the lib root (§3.2) | S | High — removes banned-font risk, adds hierarchy |
|
||||
| **P0** | One custom easing curve + press/popover micro-motion (§3.5) | S | High — cheapest "feels expensive" win |
|
||||
| **P1** | Double-bezel + inset highlights on the 4 floating panels (§3.4) | M | High — "machined" surface upgrade |
|
||||
| **P1** | Concentric 3-step radius system (§3.3) | S | Medium — visual coherence |
|
||||
| **P1** | Named z-index scale; remove `z-[9999999]` (§3.6) | S | Medium — maintainability |
|
||||
| **P2** | Explicit mobile gate on `/edit-image` (§3.7) | S | Medium — correctness on touch |
|
||||
| **P2** | Split `SceneState.ts` / `PaintSurface.tsx` into slices (§4) | L | Medium — maintainability |
|
||||
|
||||
---
|
||||
|
||||
## 6. Appendix — proposed token additions
|
||||
|
||||
```css
|
||||
/* canvas + chrome surface ramp (lib should consume only these) */
|
||||
--st-canvas: <theme>; /* replaces #1b1b1b pegboard */
|
||||
--st-accent: var(--primary); /* replaces blue-600/500 */
|
||||
--st-hairline: rgb(255 255 255 / 0.10);
|
||||
--st-inset-hi: inset 0 1px 0 rgb(255 255 255 / 0.08);
|
||||
|
||||
/* motion */
|
||||
--st-ease: cubic-bezier(0.32, 0.72, 0, 1);
|
||||
--st-dur-fast: 180ms;
|
||||
--st-dur: 300ms;
|
||||
|
||||
/* radius scale (concentric) */
|
||||
--st-r-shell: 1.25rem; /* rounded-2xl */
|
||||
--st-r-card: 0.875rem; /* rounded-xl */
|
||||
--st-r-control: 0.5rem; /* rounded-lg */
|
||||
```
|
||||
|
||||
> Scope note: every recommendation here is **styling/architecture only** — none change the generate,
|
||||
> polling, upload, bg-removal, or adapter contracts validated during the functional debug pass.
|
||||
@@ -138,6 +138,7 @@ RUN SQLX_OFFLINE=true \
|
||||
$HOME/.cargo/bin/cargo build \
|
||||
--release \
|
||||
--bin storyteller-web \
|
||||
--bin user-spend-analytics-job \
|
||||
--bin seedance2-pro-job \
|
||||
--bin worldlabs-job \
|
||||
--bin video-thumbnail-job \
|
||||
@@ -190,6 +191,7 @@ COPY --from=builder /tmp/target/release/analytics-job /
|
||||
COPY --from=builder /tmp/target/release/email-sender-job /
|
||||
COPY --from=builder /tmp/target/release/es-update-job /
|
||||
COPY --from=builder /tmp/target/release/grok-api-job /
|
||||
COPY --from=builder /tmp/target/release/user-spend-analytics-job /
|
||||
|
||||
# Legacy apps:
|
||||
# COPY --from=builder /tmp/target/release/tts-download-job /
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// Canonical wire shape for an API key, used by the list rows and the
|
||||
/// single-key GET.
|
||||
///
|
||||
/// NB: the full secret `api_key` is NEVER included here — only
|
||||
/// `truncated_api_key` (its first 20 characters), enough to disambiguate keys
|
||||
/// in a UI. The full value is returned exactly once, at creation time.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ApiKeyInfo {
|
||||
pub token: ApiKeyToken,
|
||||
|
||||
/// First 20 characters of the API key. The full secret is never returned
|
||||
/// after creation.
|
||||
pub truncated_api_key: String,
|
||||
|
||||
pub name: String,
|
||||
pub maybe_description: Option<String>,
|
||||
|
||||
pub owner_user_token: UserToken,
|
||||
|
||||
pub ip_address_creation: String,
|
||||
pub ip_address_update: String,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
|
||||
/// Soft-delete timestamp. `None` for live keys.
|
||||
pub maybe_deleted_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
/// Path info for endpoints addressed by the API key's `token` (NOT the secret
|
||||
/// `api_key` value): `GET` / `DELETE` / `PUT` `/v1/api_keys/{api_key_token}`.
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct ApiKeyPathInfo {
|
||||
pub api_key_token: ApiKeyToken,
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
|
||||
// ── POST /v1/api_keys/create ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct CreateApiKeyRequest {
|
||||
/// Display name (title) for the key.
|
||||
pub name: String,
|
||||
|
||||
/// Optional description.
|
||||
pub maybe_description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct CreateApiKeySuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The key's `token` — used for all subsequent management
|
||||
/// (get / update / delete). Safe to store and display.
|
||||
pub api_key_token: ApiKeyToken,
|
||||
|
||||
/// The full secret API key value. This is the ONLY time it is ever
|
||||
/// returned — the caller must store it now, as it cannot be retrieved again.
|
||||
pub api_key: String,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
use serde_derive::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
// ── DELETE /v1/api_keys/{api_key_token} ──
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct DeleteApiKeySuccessResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
use serde_derive::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::api_keys::common::ApiKeyInfo;
|
||||
|
||||
// ── GET /v1/api_keys/{api_key_token} ──
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct GetApiKeySuccessResponse {
|
||||
pub success: bool,
|
||||
pub api_key: ApiKeyInfo,
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
use crate::api_keys::common::ApiKeyInfo;
|
||||
|
||||
// ── GET /v1/api_keys/list ──
|
||||
|
||||
#[derive(Deserialize, ToSchema, IntoParams)]
|
||||
pub struct ListApiKeysQueryParams {
|
||||
/// Page size. Defaults server-side if omitted.
|
||||
pub limit: Option<u32>,
|
||||
|
||||
/// Number of rows to skip. Defaults to 0.
|
||||
pub offset: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ListApiKeysSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// The user's API keys (including soft-deleted ones), newest first. Each
|
||||
/// entry's key value is truncated — the full secret is never listed.
|
||||
pub api_keys: Vec<ApiKeyInfo>,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod common;
|
||||
pub mod create_api_key;
|
||||
pub mod delete_api_key;
|
||||
pub mod get_api_key;
|
||||
pub mod list_api_keys;
|
||||
pub mod update_api_key;
|
||||
@@ -0,0 +1,15 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
// ── PUT /v1/api_keys/{api_key_token} ──
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct UpdateApiKeyRequest {
|
||||
/// New description for the key. `None` clears it.
|
||||
pub maybe_description: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct UpdateApiKeySuccessResponse {
|
||||
pub success: bool,
|
||||
}
|
||||
@@ -64,6 +64,31 @@ pub struct FolderThumbnail {
|
||||
|
||||
/// Resizing template URL (with `{{width}}` / `{{height}}` placeholders)
|
||||
/// when the media class supports it. `None` for media classes without
|
||||
/// a thumbnail variant.
|
||||
/// a thumbnail variant. (Video media files instead have video previews,
|
||||
/// which, in turn, have their own thumbnail templates.)
|
||||
pub maybe_thumbnail_template: Option<String>,
|
||||
|
||||
/// Video preview images (still and animated gif) for mp4 video files.
|
||||
/// These are only set for video media files.
|
||||
pub maybe_video_previews: Option<FolderThumbnailVideoPreviews>,
|
||||
}
|
||||
|
||||
/// Video preview images for a video media file referenced by a folder.
|
||||
/// Mirrors the shape of `MediaLinks`' video previews, but is its own type
|
||||
/// so the folder wire shape can evolve independently.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct FolderThumbnailVideoPreviews {
|
||||
/// A static single frame preview image of the video.
|
||||
pub still: Url,
|
||||
|
||||
/// An animated gif preview of the video.
|
||||
pub animated: Url,
|
||||
|
||||
/// A template used to construct the still thumbnail URL.
|
||||
/// Replace the string `{WIDTH}` with the desired width.
|
||||
pub still_thumbnail_template: String,
|
||||
|
||||
/// A template used to construct the animated thumbnail URL.
|
||||
/// Replace the string `{WIDTH}` with the desired width.
|
||||
pub animated_thumbnail_template: String,
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod analytics;
|
||||
pub mod api_keys;
|
||||
pub mod characters;
|
||||
pub mod common;
|
||||
pub mod credits;
|
||||
@@ -7,6 +8,7 @@ pub mod generate;
|
||||
pub mod jobs;
|
||||
pub mod media_file;
|
||||
pub mod moderation;
|
||||
pub mod omni_api;
|
||||
pub mod omni_gen;
|
||||
pub mod prompts;
|
||||
pub mod stripe_artcraft;
|
||||
@@ -14,4 +16,5 @@ pub mod subscriptions;
|
||||
pub mod user_referral_codes;
|
||||
pub mod users;
|
||||
pub mod utils;
|
||||
pub mod video_info;
|
||||
pub mod web_referrals;
|
||||
@@ -5,3 +5,6 @@ pub mod user_referrals;
|
||||
pub mod user_stripe_data;
|
||||
pub mod wallet_ledger_entries;
|
||||
pub mod wallets;
|
||||
pub mod user_daily_spends;
|
||||
pub mod user_spend_summaries;
|
||||
pub mod user_spend_events;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod user_daily_spends_list;
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
use chrono::{DateTime, NaiveDate, Utc};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::users::UserToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
pub const MODERATOR_USER_DAILY_SPENDS_PATH: &str =
|
||||
"/v1/moderation/user_daily_spends/user/{user_token}";
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
pub struct ModeratorUserDailySpendsQueryParams {
|
||||
/// Page size (default 200, max 5000).
|
||||
pub limit: Option<u32>,
|
||||
/// Row offset for pagination. Pass `next_offset` from the previous page.
|
||||
pub offset: Option<u64>,
|
||||
/// Payments namespace. Defaults to `artcraft`.
|
||||
pub payments_namespace: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ModeratorUserDailySpendsResponse {
|
||||
pub success: bool,
|
||||
pub user_token: UserToken,
|
||||
pub records: Vec<UserDailySpendEntry>,
|
||||
/// Offset for the next page, or `None` if this was the last page.
|
||||
pub maybe_next_offset: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct UserDailySpendEntry {
|
||||
pub payments_namespace: String,
|
||||
pub spend_date: NaiveDate,
|
||||
pub subscription_spend_usd_cents: u64,
|
||||
pub credits_spend_usd_cents: u64,
|
||||
pub gross_spend_usd_cents: u64,
|
||||
pub refund_usd_cents: u64,
|
||||
pub net_spend_usd_cents: i64,
|
||||
pub payment_count: u32,
|
||||
pub credits_granted: u64,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::user_spend_events::UserSpendEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
pub const MODERATOR_LIST_USER_SPEND_EVENTS_PATH: &str =
|
||||
"/v1/moderation/user_spend_events/list";
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
pub struct ModeratorListUserSpendEventsQueryParams {
|
||||
/// Row offset for pagination (page size is fixed at 200). Pass `next_offset` from the previous page.
|
||||
pub offset: Option<u64>,
|
||||
/// Optional namespace filter (e.g. `artcraft`). Omit to return all namespaces.
|
||||
pub payments_namespace: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ModeratorListUserSpendEventsResponse {
|
||||
pub success: bool,
|
||||
pub events: Vec<UserSpendEventListEntry>,
|
||||
/// Offset for the next page, or `None` if this was the last page.
|
||||
pub maybe_next_offset: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct UserSpendEventListEntry {
|
||||
pub token: UserSpendEventToken,
|
||||
pub payments_namespace: String,
|
||||
pub maybe_user_token: Option<UserToken>,
|
||||
pub maybe_username: Option<String>,
|
||||
pub maybe_display_name: Option<String>,
|
||||
pub maybe_email_gravatar_hash: Option<String>,
|
||||
pub event_type: String,
|
||||
pub amount_usd_cents: i64,
|
||||
pub maybe_credits_granted: Option<u32>,
|
||||
pub payment_source: String,
|
||||
pub maybe_source_object_id: Option<String>,
|
||||
pub maybe_stripe_invoice_id: Option<String>,
|
||||
pub maybe_stripe_payment_intent_id: Option<String>,
|
||||
pub maybe_stripe_charge_id: Option<String>,
|
||||
pub maybe_stripe_customer_id: Option<String>,
|
||||
pub is_production: bool,
|
||||
pub payment_occurred_at: DateTime<Utc>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod list;
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::users::UserToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
pub const MODERATOR_GET_USER_SPEND_SUMMARY_PATH: &str =
|
||||
"/v1/moderation/user_spend_summaries/summary/{user_token}";
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
pub struct ModeratorGetUserSpendSummaryQueryParams {
|
||||
/// Payments namespace. Defaults to `artcraft`.
|
||||
pub payments_namespace: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ModeratorGetUserSpendSummaryResponse {
|
||||
pub success: bool,
|
||||
/// `None` if the user has no spend summary row.
|
||||
pub maybe_summary: Option<UserSpendSummaryView>,
|
||||
}
|
||||
|
||||
/// The full `user_spend_summaries` record for a user.
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct UserSpendSummaryView {
|
||||
pub payments_namespace: String,
|
||||
pub user_token: UserToken,
|
||||
|
||||
pub lifetime_gross_spend_usd_cents: u64,
|
||||
pub lifetime_subscription_spend_usd_cents: u64,
|
||||
pub lifetime_credits_spend_usd_cents: u64,
|
||||
pub lifetime_refund_usd_cents: u64,
|
||||
pub lifetime_net_spend_usd_cents: u64,
|
||||
pub lifetime_payment_count: u32,
|
||||
pub lifetime_refund_count: u32,
|
||||
pub maybe_first_payment_at: Option<DateTime<Utc>>,
|
||||
pub first_spend_usd_cents: u64,
|
||||
pub maybe_last_payment_at: Option<DateTime<Utc>>,
|
||||
pub last_spend_usd_cents: u64,
|
||||
pub maybe_days_since_first_payment: Option<u32>,
|
||||
pub maybe_days_since_last_payment: Option<u32>,
|
||||
|
||||
pub net_spend_7d_usd_cents: i64,
|
||||
pub net_spend_prev_7d_usd_cents: i64,
|
||||
pub net_spend_14d_usd_cents: i64,
|
||||
pub net_spend_prev_14d_usd_cents: i64,
|
||||
pub net_spend_30d_usd_cents: i64,
|
||||
pub net_spend_prev_30d_usd_cents: i64,
|
||||
pub net_spend_60d_usd_cents: i64,
|
||||
pub net_spend_90d_usd_cents: i64,
|
||||
pub net_spend_this_year_usd_cents: i64,
|
||||
|
||||
pub avg_weekly_net_spend_4w_usd_cents: i64,
|
||||
pub avg_weekly_net_spend_12w_usd_cents: i64,
|
||||
pub active_weeks_in_last_4: u8,
|
||||
pub active_weeks_in_last_8: u8,
|
||||
pub active_weeks_in_last_12: u8,
|
||||
pub active_weeks_in_last_24: u8,
|
||||
pub active_weeks_in_last_52: u8,
|
||||
pub consecutive_active_weeks: u32,
|
||||
pub consecutive_inactive_weeks: u32,
|
||||
pub maybe_weeks_since_last_spend: Option<u32>,
|
||||
|
||||
pub is_active_subscriber: bool,
|
||||
pub maybe_subscription_interval: Option<String>,
|
||||
pub maybe_reengagement_score: Option<u32>,
|
||||
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod get_summary;
|
||||
pub mod reengagement_list;
|
||||
pub mod top_users_list;
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::users::UserToken;
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
pub const MODERATOR_REENGAGEMENT_LIST_PATH: &str =
|
||||
"/v1/moderation/user_spend_summaries/reengagement_list";
|
||||
|
||||
#[derive(Deserialize, IntoParams)]
|
||||
pub struct ModeratorReengagementListQueryParams {
|
||||
/// Row offset for pagination (page size is fixed at 200). Pass `next_offset` from the previous page.
|
||||
pub offset: Option<u64>,
|
||||
/// Payments namespace. Defaults to `artcraft`.
|
||||
pub payments_namespace: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ModeratorReengagementListResponse {
|
||||
pub success: bool,
|
||||
pub candidates: Vec<ReengagementCandidateEntry>,
|
||||
/// Offset for the next page, or `None` if this was the last page.
|
||||
pub maybe_next_offset: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct ReengagementCandidateEntry {
|
||||
pub user_token: UserToken,
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub email_gravatar_hash: String,
|
||||
pub reengagement_score: u32,
|
||||
pub lifetime_net_spend_usd_cents: u64,
|
||||
pub maybe_last_payment_at: Option<DateTime<Utc>>,
|
||||
pub maybe_days_since_last_payment: Option<u32>,
|
||||
pub maybe_weeks_since_last_spend: Option<u32>,
|
||||
pub is_active_subscriber: bool,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user