Compare commits

...
Author SHA1 Message Date
Brandon Thomas f587e19d25 remove gpu loading error 2025-09-27 18:27:33 -04:00
Brandon Thomas cea29135ef codegen db 2025-09-27 18:18:47 -04:00
Brandon Thomas 8f2ebf0e69 Veo 3 / Veo 3 Fast (#856) 2025-09-27 18:18:07 -04:00
Brandon Thomas ddfe83c514 analytics enhancements - track individual sessions (#855) 2025-09-27 17:52:35 -04:00
Brandon Thomas a05cd8414a refactor endpoint calls in storyteller_client (#854) 2025-09-27 17:07:30 -04:00
Brandon Thomas c628d791e9 purge artcraft credentials command (#853) 2025-09-27 13:18:39 -04:00
Brandon Thomas 04e3a56d18 script to build javascript 2025-09-26 18:02:31 -04:00
Brandon Thomas 7374076506 fix nx singleton mangling (#851) 2025-09-25 19:39:12 -04:00
Brandon Thomas 41940839fd reuse app version config 2025-09-25 02:58:34 -04:00
Brandon Thomas c43f63ff26 new binary version on website 2025-09-25 02:17:57 -04:00
Brandon Thomas a89ae797e3 codegen db 2025-09-25 01:51:33 -04:00
Brandon Thomas 7eaffa7041 app info in settings (#850) 2025-09-25 01:50:57 -04:00
Brandon Thomas ecfe9d417b log OS version (#849) 2025-09-25 01:03:15 -04:00
Brandon Thomas fa04e633fe artcraft activity analytics (#848)
we need to know if anyone is using the app
2025-09-24 22:07:52 -04:00
Brandon Thomas 54b01a1e3f website analytics + meta + title (#846) 2025-09-24 16:49:51 -04:00
BFlat d3382f61fe implement image preloading in HistoryStack and update button styles (#845)
this fixes image flickering when switching images with the history stack
2025-09-24 14:17:28 +07:00
BFlat 0f74242b2e history stack no shrink and divider styling update (#844)
- history stack now doesn't get squashed
- clearer divider color
2025-09-24 14:07:12 +07:00
Brandon Thomas 528677cd47 record signup source from artcraft (#843) 2025-09-24 01:25:10 -04:00
BFlat 88769baa4f added lines to clearly separate sections of image prompt row (#842)
<img width="767" height="102" alt="image"
src="https://github.com/user-attachments/assets/25f5e8cc-72b6-4889-aa2c-2fbfcdaa184b"
/>
2025-09-24 11:26:26 +07:00
BFlat f4d85363cb image copy paste fix in 3d editor (#841) 2025-09-24 11:22:31 +07:00
Brandon Thomas c3dadcfd1d longer delay between IP ban polls (we use cloudflare now anyway) 2025-09-23 23:33:06 -04:00
Brandon Thomas 517abc0462 server route cleanup (#840) 2025-09-23 23:07:35 -04:00
Brandon Thomas bbbd921eab rename debug log 2025-09-23 20:19:22 -04:00
Brandon Thomas 4ae2500207 update artcraft website links 2025-09-23 17:48:36 -04:00
Brandon Thomas e54c29cd43 script to push release branch 2025-09-23 14:41:58 -04:00
Brandon Thomas 59f5da1be3 change model orders (#839) 2025-09-23 14:39:36 -04:00
Brandon Thomas f6dfd02cfc fix production issues from minification / instanceof (#838) 2025-09-23 10:54:14 -04:00
Brandon Thomas 5d05309d95 clarity in CSP connect source 2025-09-22 21:39:56 -04:00
Brandon Thomas 56e43f9f1d fix windows tauri build in CI -
```
Run # Build the frontend app from the correct directory
Building with npx nx...

 NX   Failed to process project graph.

 An error occurred while processing files for the @nx/eslint/plugin
 plugin (Defined at nx.json#plugins[4])
 .
   - apps/editor3d/.eslintrc.cjs: Failed to load plugin 'storybook'
     declared in '.eslintrc.cjs': Cannot find module
     'eslint-plugin-storybook'
     Require stack:
     -
     D:\a\storyteller-rust\storyteller-rust\frontend\apps\editor3d\__placeholder__.js
     Referenced from:
     D:\a\storyteller-rust\storyteller-rust\frontend\apps\editor3d\.eslintrc.cjs
```
2025-09-22 21:00:26 -04:00
Brandon Thomas 66a805c53f fix file size 2025-09-22 16:01:28 -04:00
Brandon Thomas a5c5161939 increase upload limit 2025-09-22 15:45:31 -04:00
BFlat f0fd411640 upload image button in topbar (#836)
- upload multiple images at the same time from the topbar button
- pops a promise toast when uploading and checks when complete
2025-09-22 23:12:29 +07:00
BFlat a2d81ddf0c inpainting page lightmode fixes (#835) 2025-09-22 19:19:08 +07:00
BFlat 676eb12d56 remaining promptbox updates and inpainting centering fix (#834)
- 2d, 3d and inpainting promptboxes now support the same model-dynamic
image prompt row
- fixerd the centering issue for the inpainting canvas
2025-09-22 18:49:00 +07:00
Brandon Thomas bab9cf76f0 Restore zustand (#833) 2025-09-19 19:20:42 -04:00
Brandon Thomas 9633a0ba85 temp fix for main poduction builds (zustand state commented out) (#832) 2025-09-19 11:38:03 -04:00
Brandon Thomas 7d14c3c01c clean dist modules 2025-09-18 16:40:37 -04:00
Brandon Thomas 8477991327 misc: clarity csp, handle billing errors, notes (#831) 2025-09-18 14:49:46 -04:00
Brandon Thomas 5e5aa01ce7 roll back build links 2025-09-18 13:01:50 -04:00
Brandon Thomas ca21b69610 roll back dmg 2025-09-17 23:05:50 -04:00
Brandon Thomas 41348dd585 cleanup deps 2025-09-17 23:05:21 -04:00
Brandon Thomas f6c630cc49 remove storybook (#830) 2025-09-17 21:13:35 -04:00
Brandon Thomas 2b3be7c172 MS clarity script CSP 2025-09-17 17:47:45 -04:00
Brandon Thomas 677ed59005 updated download links 2025-09-17 17:14:23 -04:00
Brandon Thomas 4849b4d6bb codegen db 2025-09-17 12:19:14 -04:00
BFlat dbda51bd29 theme switcher in settings appearance tab (#828)
- huge css changes throughout app to support theme switching
- 5 themes: light (white), gray(default), black, and gradient themes:
aurora and sunset

note:
- light theme still needs a second pass since some styling / text color
on white isn't fully updated yet there's some area that hasn't been
covered but most have been
2025-09-17 23:09:14 +07:00
Brandon Thomas 9d9ec36341 stripe payments and subscriptions (#829) 2025-09-17 11:43:00 -04:00
BFlat 0163504aad fixed characters upload and gallery dnd + added 3d meme models (#827)
- added 6 memes models and custom thumbnails
- upload modal now has checkbox to allow character uploads
- gallery drag and drop now works properly for characters
2025-09-17 12:26:35 +07:00
bflatastic 5093361f2c added artcraft icon and updated provider priority to use model icons 2025-09-17 09:23:25 +07:00
BFlat b59909c1aa fixed inpainting upload from file being stuck (#825) 2025-09-15 14:09:52 +07:00
BFlat 9d8eedc77a Feature/dnd image prompts (#823)
- hide/show end frame input depending on the model selected
- image prompt drag and drop reordering if there's more than one image
added
2025-09-12 14:12:35 +07:00
BFlat 0e7ebd3914 updated landing page videos (#822)
- changed videos for 3d and 2d canvas
- added a video for inpainting (copy text needs to be updated)
2025-09-11 14:51:22 +07:00
bflatastic d508bf55e2 nx workspace sync 2025-09-11 14:21:15 +07:00
Tanish Manku cac0c0b188 Auto-switch to edit mode if model supports it (#818) 2025-09-10 13:50:02 +07:00
bflatastic f837f79404 fixed title disappearing on generate 2025-09-10 13:47:34 +07:00
BFlat b632d79833 image/video promptbox generation bug fixes (#821)
- make top image prompt row always show for video
- video promptbox pops a toast when reference image is empty
- description changes for ending frame section
- hid the add image button for video generation for now since that's
almost always required
2025-09-10 13:45:00 +07:00
Tanish Mankuandbflatastic d909d55e6f Support history item deletion (#819)
Co-authored-by: bflatastic <bombayapirat@gmail.com>
2025-09-10 13:23:05 +07:00
Tanish Manku 1847533e8a Add an image removal button in history stack 2025-09-09 12:44:17 -07:00
BFlat 95894a87b9 video page update (#817)
- video prompt box now supports image prompt (starting and ending frame)
- shared image prompts section between image / video generation prompt
boxes
- video history / pending generation like text to image
- background change

- the history stack is commented out for now because backend doesnt
support it yet.
2025-09-09 19:03:26 +07:00
Tanish Manku 3ad642f150 Make mouse dragging pixel-perfect (#816) 2025-09-04 14:38:19 -07:00
bflatastic 3fb683185a console errors and log spams fixed 2025-09-04 22:32:36 +07:00
BFlat 36780e5669 ux update inpainting history (#815)
- reverted to previous ux
- added placeholder loaders for generating images (matched by frontend
subscriber id)
- can now generate multiple images at a time
- auto scrolls to top of history stack on enqueue
2025-09-03 23:05:05 +07:00
BFlat 77614e06d9 credits UI refill (#814) 2025-09-03 21:49:36 +07:00
Brandon Thomas 4c1d1007a2 icon change, metadata change (#813) 2025-08-31 13:13:58 -04:00
bflatastic 9740121012 history items to only show after enqueue succeeds 2025-08-31 11:07:48 +07:00
BFlat 27d6794d22 credits ui - topbar and settings modal (#804)
- new progress bar and progress circle component
- clicking the credits in the top opens the settings modal on the "Plans
& Credits" tab
2025-08-31 11:06:29 +07:00
991 changed files with 19174 additions and 12527 deletions
@@ -0,0 +1,168 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `token: tokens::tokens::user_subscriptions::UserSubscriptionToken`,\n user_token as `user_token: tokens::tokens::users::UserToken`,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n maybe_stripe_customer_id,\n maybe_stripe_product_id,\n maybe_stripe_subscription_id,\n maybe_stripe_subscription_status,\n maybe_stripe_invoice_is_paid,\n subscription_start_at,\n subscription_expires_at,\n current_billing_period_end_at,\n maybe_cancel_at,\n maybe_canceled_at\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_namespace = ?\n AND maybe_stripe_customer_id IS NOT NULL\n AND maybe_stripe_subscription_id IS NOT NULL\n AND maybe_stripe_subscription_status IS NOT NULL\n AND maybe_stripe_subscription_status NOT IN ('canceled', 'incomplete_expired')\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n LIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::user_subscriptions::UserSubscriptionToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 2,
"name": "subscription_namespace: enums::common::payments_namespace::PaymentsNamespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 3,
"name": "subscription_product_slug",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 4,
"name": "maybe_stripe_customer_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 5,
"name": "maybe_stripe_product_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 6,
"name": "maybe_stripe_subscription_id",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 7,
"name": "maybe_stripe_subscription_status",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 8,
"name": "maybe_stripe_invoice_is_paid",
"type_info": {
"type": "Tiny",
"flags": "",
"char_set": 63,
"max_size": 1
}
},
{
"ordinal": 9,
"name": "subscription_start_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 10,
"name": "subscription_expires_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 11,
"name": "current_billing_period_end_at",
"type_info": {
"type": "Timestamp",
"flags": "NOT_NULL | BINARY | TIMESTAMP",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 12,
"name": "maybe_cancel_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"char_set": 63,
"max_size": 19
}
},
{
"ordinal": 13,
"name": "maybe_canceled_at",
"type_info": {
"type": "Timestamp",
"flags": "BINARY",
"char_set": 63,
"max_size": 19
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false,
true,
true,
true,
true,
true,
false,
false,
false,
true,
true
]
},
"hash": "1e08676857e999a58d3c85474c2a2acec38a413e53ca4207aade690e0bfffe25"
}
@@ -0,0 +1,69 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `token: tokens::tokens::wallets::WalletToken`,\n wallet_namespace as `namespace: enums::common::payments_namespace::PaymentsNamespace`,\n owner_user_token as `owner_user_token: tokens::tokens::users::UserToken`,\n banked_credits,\n monthly_credits\nFROM wallets\nWHERE token = ?\nLIMIT 1\nFOR UPDATE\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::wallets::WalletToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "namespace: enums::common::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": "owner_user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 3,
"name": "banked_credits",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 4,
"name": "monthly_credits",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false
]
},
"hash": "2be78dc9fada8cda24df59ee9911118aa6f40247b10c6a76e07cfa74fd7f1412"
}
@@ -0,0 +1,25 @@
{
"db_name": "MySQL",
"query": "\n SELECT\n token as `token: tokens::tokens::wallets::WalletToken`\n FROM wallets\n WHERE owner_user_token = ?\n AND wallet_namespace = ?\n ORDER BY id ASC\n LIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::wallets::WalletToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false
]
},
"hash": "2c98facb81a43364997db5558ad0f12300230691b09ce96d49e530f8361c237f"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\n UPDATE wallets\n SET\n monthly_credits = ?,\n version = version + 1\n WHERE token = ?\n LIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "47b14121f41b98b0dc92772e9f58a3f4a3821b11ebb10b768595d7635e472dbb"
}
@@ -0,0 +1,102 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `token: tokens::tokens::user_subscriptions::UserSubscriptionToken`,\n user_token as `user_token: tokens::tokens::users::UserToken`,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n maybe_stripe_subscription_status,\n maybe_stripe_customer_id,\n maybe_stripe_product_id,\n maybe_stripe_subscription_id\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_namespace = ?\n AND maybe_stripe_customer_id IS NOT NULL\n AND maybe_stripe_subscription_id IS NOT NULL\n AND maybe_stripe_subscription_status IS NOT NULL\n AND maybe_stripe_subscription_status NOT IN ('canceled', 'incomplete_expired')\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n LIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::user_subscriptions::UserSubscriptionToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 2,
"name": "subscription_namespace: enums::common::payments_namespace::PaymentsNamespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 3,
"name": "subscription_product_slug",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 4,
"name": "maybe_stripe_subscription_status",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_stripe_customer_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 6,
"name": "maybe_stripe_product_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 7,
"name": "maybe_stripe_subscription_id",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false,
true,
true,
true,
true
]
},
"hash": "4bfa96682bd1e7576c26c89e3d1a17162193f7df3c1530122ca8e715afc5f385"
}
@@ -0,0 +1,102 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n token as `token: tokens::tokens::user_subscriptions::UserSubscriptionToken`,\n user_token as `user_token: tokens::tokens::users::UserToken`,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n maybe_stripe_subscription_status,\n maybe_stripe_customer_id,\n maybe_stripe_product_id,\n maybe_stripe_subscription_id\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_namespace = ?\n AND maybe_stripe_customer_id IS NOT NULL\n AND maybe_stripe_subscription_id IS NOT NULL\n AND maybe_stripe_subscription_status IS NOT NULL\n ORDER BY id ASC\n LIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::user_subscriptions::UserSubscriptionToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 2,
"name": "subscription_namespace: enums::common::payments_namespace::PaymentsNamespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 3,
"name": "subscription_product_slug",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 4,
"name": "maybe_stripe_subscription_status",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 5,
"name": "maybe_stripe_customer_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 6,
"name": "maybe_stripe_product_id",
"type_info": {
"type": "VarString",
"flags": "MULTIPLE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
},
{
"ordinal": 7,
"name": "maybe_stripe_subscription_id",
"type_info": {
"type": "VarString",
"flags": "UNIQUE_KEY | BINARY",
"char_set": 224,
"max_size": 1020
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false,
true,
true,
true,
true
]
},
"hash": "4c2a71386362fb54f38bd6633f8aebe75e0bd8edf1b1b8773c6298c1f6018332"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO wallets\nSET\n token = ?,\n wallet_namespace = ?,\n\n owner_user_token = ?,\n \n banked_credits = 0,\n monthly_credits = 0\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "4f5100a2cfb465964e77eaacb1ae394daae94de20c56a0966a8222dc9bea63c6"
}
@@ -1,6 +1,6 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n user_token,\n subscription_namespace,\n subscription_product_slug,\n subscription_expires_at\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n ",
"query": "\nSELECT\n user_token,\n subscription_namespace as `subscription_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n subscription_product_slug,\n subscription_expires_at\n\nFROM user_subscriptions\n\nWHERE\n user_token = ?\n AND subscription_expires_at > CURRENT_TIMESTAMP\n ORDER BY id ASC\n ",
"describe": {
"columns": [
{
@@ -15,7 +15,7 @@
},
{
"ordinal": 1,
"name": "subscription_namespace",
"name": "subscription_namespace: enums::common::payments_namespace::PaymentsNamespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
@@ -54,5 +54,5 @@
false
]
},
"hash": "46e1cb058bffbea5a5d3ffa5dd904416d6f34ccefdb394030f19cc762b507d68"
"hash": "680d60fcda0f6fdf269f3525a3fe582db847e56ca7380c0ff5696cbd6efb7db3"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO user_stripe_customer_links\nSET\n user_token = ?,\n payments_namespace = ?,\n stripe_customer_id = ?\n\nON DUPLICATE KEY UPDATE\n updated_at = CURRENT_TIMESTAMP\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "6df1b3d2cd145fa3199898fe53565eef28a74206093f4fc6585aad6cc2e9ae71"
}
@@ -0,0 +1,47 @@
{
"db_name": "MySQL",
"query": "\nSELECT\n user_token as `user_token: tokens::tokens::users::UserToken`,\n payments_namespace as `payments_namespace: enums::common::payments_namespace::PaymentsNamespace`,\n stripe_customer_id\n\nFROM user_stripe_customer_links\n\nWHERE\n user_token = ?\n AND payments_namespace = ?\n ORDER BY id ASC\n LIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "user_token: tokens::tokens::users::UserToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | MULTIPLE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "payments_namespace: enums::common::payments_namespace::PaymentsNamespace",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 2,
"name": "stripe_customer_id",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 1020
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false
]
},
"hash": "771ebd8c236cc3bfeee2df7168c05f639b666e5d67a46593332ce02a0316b4f9"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO user_subscriptions\nSET\n token = ?,\n\n maybe_stripe_invoice_is_paid = ?,\n\n user_token = ?,\n subscription_namespace = ?,\n subscription_product_slug = ?,\n\n maybe_stripe_subscription_id = ?,\n maybe_stripe_customer_id = ?,\n\n maybe_stripe_product_id = ?,\n maybe_stripe_price_id = ?,\n\n maybe_stripe_recurring_interval = ?,\n maybe_stripe_subscription_status = ?,\n maybe_stripe_is_production = ?,\n \n maybe_stripe_billing_cycle_anchor = ?,\n\n subscription_start_at = ?,\n current_billing_period_start_at = ?,\n current_billing_period_end_at = ?,\n subscription_expires_at = ?,\n maybe_cancel_at = ?,\n maybe_canceled_at = ?,\n\n version = version + 1\n\nON DUPLICATE KEY UPDATE\n maybe_stripe_invoice_is_paid = ?,\n\n subscription_namespace = ?,\n subscription_product_slug= ?,\n\n maybe_stripe_product_id = ?,\n maybe_stripe_price_id = ?,\n\n maybe_stripe_recurring_interval = ?,\n maybe_stripe_subscription_status = ?,\n \n maybe_stripe_billing_cycle_anchor = ?,\n\n current_billing_period_start_at = ?,\n current_billing_period_end_at = ?,\n subscription_expires_at = ?,\n maybe_cancel_at = ?,\n maybe_canceled_at = ?,\n\n version = version + 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 32
},
"nullable": []
},
"hash": "871163ef30d2f6767f836a05f46d2ce7fb4733583eedddbf07732ebb5c1f1b53"
}
@@ -1,12 +0,0 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO user_subscriptions\nSET\n token = ?,\n user_token = ?,\n subscription_namespace = ?,\n subscription_product_slug = ?,\n\n maybe_stripe_subscription_id = ?,\n maybe_stripe_customer_id = ?,\n\n maybe_stripe_product_id = ?,\n maybe_stripe_price_id = ?,\n\n maybe_stripe_recurring_interval = ?,\n maybe_stripe_subscription_status = ?,\n maybe_stripe_is_production = ?,\n\n subscription_start_at = ?,\n current_billing_period_start_at = ?,\n current_billing_period_end_at = ?,\n subscription_expires_at = ?,\n maybe_cancel_at = ?,\n maybe_canceled_at = ?,\n\n version = version + 1\n\nON DUPLICATE KEY UPDATE\n subscription_namespace = ?,\n subscription_product_slug= ?,\n\n maybe_stripe_product_id = ?,\n maybe_stripe_price_id = ?,\n\n maybe_stripe_recurring_interval = ?,\n maybe_stripe_subscription_status = ?,\n\n current_billing_period_start_at = ?,\n current_billing_period_end_at = ?,\n subscription_expires_at = ?,\n maybe_cancel_at = ?,\n maybe_canceled_at = ?,\n\n version = version + 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 28
},
"nullable": []
},
"hash": "8d267a609157e6459393ffa32ea83996dcf135c52ee8a930d5be628802d97371"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO wallet_ledger_entries\nSET\n token = ?,\n wallet_token = ?,\n entry_type = ?,\n maybe_entity_ref = ?,\n credits_delta = ?,\n banked_credits_before = ?,\n banked_credits_after = ?,\n monthly_credits_before = ?,\n monthly_credits_after = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 9
},
"nullable": []
},
"hash": "9d26917668d4f0ecd2492a94a7775e4561fc5d8b33194274a7715b9853ecea00"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO user_subscriptions\nSET\n token = ?,\n user_token = ?,\n subscription_namespace = ?,\n subscription_product_slug = ?,\n\n maybe_stripe_subscription_id = ?,\n maybe_stripe_customer_id = ?,\n\n maybe_stripe_product_id = ?,\n maybe_stripe_price_id = ?,\n\n maybe_stripe_recurring_interval = ?,\n maybe_stripe_subscription_status = ?,\n maybe_stripe_is_production = ?,\n \n maybe_stripe_billing_cycle_anchor = ?,\n\n subscription_start_at = ?,\n current_billing_period_start_at = ?,\n current_billing_period_end_at = ?,\n subscription_expires_at = ?,\n maybe_cancel_at = ?,\n maybe_canceled_at = ?,\n\n version = version + 1\n\nON DUPLICATE KEY UPDATE\n subscription_namespace = ?,\n subscription_product_slug= ?,\n\n maybe_stripe_product_id = ?,\n maybe_stripe_price_id = ?,\n\n maybe_stripe_recurring_interval = ?,\n maybe_stripe_subscription_status = ?,\n \n maybe_stripe_billing_cycle_anchor = ?,\n\n current_billing_period_start_at = ?,\n current_billing_period_end_at = ?,\n subscription_expires_at = ?,\n maybe_cancel_at = ?,\n maybe_canceled_at = ?,\n\n version = version + 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 30
},
"nullable": []
},
"hash": "a28d6de7424b269e64e97692a4c67a12fcfb789a3e5a512a103a78dea60e75ce"
}
@@ -0,0 +1,58 @@
{
"db_name": "MySQL",
"query": "\n SELECT\n token as `token: tokens::tokens::wallets::WalletToken`,\n wallet_namespace as `namespace: enums::common::payments_namespace::PaymentsNamespace`,\n banked_credits,\n monthly_credits\n FROM wallets\n WHERE owner_user_token = ?\n AND wallet_namespace = ?\n ORDER BY id ASC\n LIMIT 1\n ",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "token: tokens::tokens::wallets::WalletToken",
"type_info": {
"type": "VarString",
"flags": "NOT_NULL | UNIQUE_KEY | BINARY | NO_DEFAULT_VALUE",
"char_set": 224,
"max_size": 128
}
},
{
"ordinal": 1,
"name": "namespace: enums::common::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": "banked_credits",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
},
{
"ordinal": 3,
"name": "monthly_credits",
"type_info": {
"type": "Long",
"flags": "NOT_NULL | UNSIGNED",
"char_set": 63,
"max_size": 10
}
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
false
]
},
"hash": "ac8e3bc9f4e0722ec2f805dbb52b8e43ec8f7a1aa923b9f8f5f6c0770a315ee6"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO analytics_app_active_users\nSET\n app_namespace = ?,\n user_token = ?,\n app_version = ?,\n os_platform = ?,\n os_version = ?,\n session_duration_seconds = ?,\n ip_address = ?,\n measurement_count = measurement_count + 1,\n first_active_at = NOW(),\n last_active_at = NOW()\nON DUPLICATE KEY UPDATE\n app_version = ?,\n os_platform = ?,\n os_version = ?,\n session_duration_seconds = ?,\n ip_address = ?,\n measurement_count = measurement_count + 1,\n last_active_at = NOW()\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 12
},
"nullable": []
},
"hash": "b48490837d568002305551353704c8e8a4fc78459e5a9aa754277b989107bc5c"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\n UPDATE wallets\n SET\n banked_credits = ?,\n version = version + 1\n WHERE token = ?\n LIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "b721e3fda6d80df5041c918b1a127236c802ed18ccc961cc6ac96d156bf085da"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\n UPDATE wallets\n SET\n banked_credits = ?,\n monthly_credits = ?,\n version = version + 1\n WHERE token = ?\n LIMIT 1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 3
},
"nullable": []
},
"hash": "c7f20753f9704d5ff3358586240ecc8a505990f4634ba3af9de6c99c22aa7368"
}
@@ -0,0 +1,12 @@
{
"db_name": "MySQL",
"query": "\nINSERT INTO analytics_app_sessions\nSET\n session_token = ?,\n app_namespace = ?,\n user_token = ?,\n app_version = ?,\n os_platform = ?,\n os_version = ?,\n session_duration_seconds = ?,\n ip_address = ?,\n measurement_count = measurement_count + 1,\n first_active_at = NOW(),\n last_active_at = NOW()\nON DUPLICATE KEY UPDATE\n app_namespace = ?,\n user_token = ?,\n app_version = ?,\n os_platform = ?,\n os_version = ?,\n session_duration_seconds = ?,\n ip_address = ?,\n measurement_count = measurement_count + 1,\n last_active_at = NOW()\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 15
},
"nullable": []
},
"hash": "ff39576a5ec6a23c67f6f61e7eba8392e70b05c42b540bea260a9a4cdc478a5b"
}
Generated
+278 -3
View File
@@ -366,6 +366,7 @@ dependencies = [
"cidr-utils",
"errors",
"futures-util",
"log",
"num-traits",
"serde",
"serde_json",
@@ -678,6 +679,7 @@ dependencies = [
"mimetypes",
"once_cell",
"openai_sora_client",
"os_info",
"primitives",
"rand 0.9.1",
"reqwest 0.12.22",
@@ -926,11 +928,155 @@ dependencies = [
"serde_qs 0.10.1",
"sha2 0.10.8",
"smart-default 0.6.0",
"smol_str",
"smol_str 0.1.24",
"thiserror 1.0.63",
"tokio",
]
[[package]]
name = "async-stripe"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecbddf002ad7a13d2041eadf1b234cb3f57653ffdd901a01bc3f1c65aa77440"
dependencies = [
"chrono",
"futures-util",
"hex",
"hmac 0.12.1",
"http-types",
"hyper 0.14.28",
"hyper-tls 0.5.0",
"serde",
"serde_json",
"serde_path_to_error",
"serde_qs 0.10.1",
"sha2 0.10.8",
"smart-default 0.6.0",
"smol_str 0.1.24",
"thiserror 1.0.63",
"tokio",
]
[[package]]
name = "async-stripe"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a92140fa7152f9dcde7554cb1da37c7ae93b0c724db5214c754c55a9765afbe8"
dependencies = [
"async-stripe-client-core",
"async-stripe-shared",
"bytes 1.10.1",
"http-body-util",
"hyper 1.6.0",
"hyper-tls 0.6.0",
"hyper-util",
"miniserde",
"thiserror 2.0.12",
"tokio",
]
[[package]]
name = "async-stripe-billing"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c4cd0ac61e0dcb39da4f3c04b049c0d8a3a98868ca976e18ff56ecac1b6e08"
dependencies = [
"async-stripe-client-core",
"async-stripe-shared",
"async-stripe-types",
"miniserde",
"serde",
"smol_str 0.3.2",
]
[[package]]
name = "async-stripe-checkout"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b115740c799730d4caac099d48be4908d2d99611d56a874d2d3555b5d4306a97"
dependencies = [
"async-stripe-client-core",
"async-stripe-shared",
"async-stripe-types",
"miniserde",
"serde",
"smol_str 0.3.2",
]
[[package]]
name = "async-stripe-client-core"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb1f6b6693946e650a9361feb319389a1408534ec1b618cd9239f1e2e20b8668"
dependencies = [
"async-stripe-shared",
"async-stripe-types",
"bytes 1.10.1",
"futures-util",
"miniserde",
"serde",
"serde_json",
"serde_qs 0.13.0",
"thiserror 2.0.12",
"tracing",
]
[[package]]
name = "async-stripe-core"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81d0445a38170bcc3e4adf52fd80c94b70b02dc73a102afcd0bc9d6051eada18"
dependencies = [
"async-stripe-client-core",
"async-stripe-shared",
"async-stripe-types",
"miniserde",
"serde",
"smol_str 0.3.2",
]
[[package]]
name = "async-stripe-shared"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b4328e5c0deeacd7cd5a95286d4f5dce045791b30be87583dd706c2342236c3"
dependencies = [
"async-stripe-types",
"miniserde",
"serde",
"serde_json",
"smol_str 0.3.2",
]
[[package]]
name = "async-stripe-types"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985b4f00fd57ce9cbd015c6410d05dfbe5d2bf90513e20b417e70b6f022e3f88"
dependencies = [
"miniserde",
"serde",
"serde_json",
"smol_str 0.3.2",
]
[[package]]
name = "async-stripe-webhook"
version = "1.0.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75f8382af92463fa8e0b86e825d7d08eecbcd8715c197f1a66173fcc18e35774"
dependencies = [
"async-stripe-shared",
"async-stripe-types",
"chrono",
"hex",
"hmac 0.12.1",
"miniserde",
"serde",
"sha2 0.10.8",
"thiserror 1.0.63",
]
[[package]]
name = "async-task"
version = "4.7.1"
@@ -1201,6 +1347,41 @@ dependencies = [
"num-traits",
]
[[package]]
name = "billing_artcraft_component"
version = "0.0.1"
dependencies = [
"actix-http",
"actix-web",
"anyhow",
"artcraft_api_defs",
"async-stripe 1.0.0-alpha.2",
"async-stripe-billing",
"async-stripe-checkout",
"async-stripe-core",
"async-stripe-shared",
"async-stripe-types",
"async-stripe-webhook",
"chrono",
"component_traits",
"enums",
"errors",
"http_server_common",
"log",
"mockall",
"mysql_queries",
"once_cell",
"reusable_types",
"serde",
"serde_derive",
"serde_json",
"sqlx",
"tokens",
"tokio",
"user_traits_component",
"workspace-build-acceleration",
]
[[package]]
name = "billing_component"
version = "0.0.1"
@@ -1208,9 +1389,11 @@ dependencies = [
"actix-http",
"actix-web",
"anyhow",
"async-stripe",
"async-stripe 0.26.0",
"async-trait",
"chrono",
"component_traits",
"enums",
"errors",
"http_server_common",
"log",
@@ -2089,6 +2272,34 @@ dependencies = [
"workspace-build-acceleration",
]
[[package]]
name = "component_traits"
version = "0.0.1"
dependencies = [
"actix-http",
"actix-web",
"anyhow",
"artcraft_api_defs",
"async-trait",
"chrono",
"enums",
"errors",
"http_server_common",
"log",
"mockall",
"mysql_queries",
"once_cell",
"reusable_types",
"serde",
"serde_derive",
"serde_json",
"sqlx",
"tokens",
"tokio",
"user_traits_component",
"workspace-build-acceleration",
]
[[package]]
name = "composite_identifiers"
version = "0.0.1"
@@ -6398,6 +6609,17 @@ dependencies = [
"workspace-build-acceleration",
]
[[package]]
name = "mini-internal"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6c74ab4f1a0c0ab045260ee4727b23c00cc17e5eff5095262d08eef8c3c8d49"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]]
name = "minidom"
version = "0.16.0"
@@ -6413,6 +6635,17 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniserde"
version = "0.1.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08ec68bf2ad170a53a6efa92c3df7187968d6e475fe7a935725868154074ca0f"
dependencies = [
"itoa 1.0.11",
"mini-internal",
"ryu",
]
[[package]]
name = "miniz_oxide"
version = "0.7.2"
@@ -7378,6 +7611,17 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "os_info"
version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3"
dependencies = [
"log",
"plist",
"windows-sys 0.52.0",
]
[[package]]
name = "overload"
version = "0.1.1"
@@ -9528,6 +9772,17 @@ dependencies = [
"thiserror 1.0.63",
]
[[package]]
name = "serde_qs"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
dependencies = [
"percent-encoding",
"serde",
"thiserror 1.0.63",
]
[[package]]
name = "serde_repr"
version = "0.1.19"
@@ -9904,6 +10159,16 @@ dependencies = [
"serde",
]
[[package]]
name = "smol_str"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9676b89cd56310a87b93dec47b11af744f34d5fc9f367b829474eec0a891350d"
dependencies = [
"borsh",
"serde",
]
[[package]]
name = "socket2"
version = "0.5.10"
@@ -10292,9 +10557,11 @@ dependencies = [
"actix_helpers",
"anyhow",
"artcraft_api_defs",
"async-stripe",
"async-stripe 0.26.0",
"async-stripe 0.41.0",
"async-trait",
"base64 0.22.1",
"billing_artcraft_component",
"billing_component",
"bootstrap",
"bucket_paths",
@@ -10302,6 +10569,7 @@ dependencies = [
"chrono",
"cloud_storage",
"collections",
"component_traits",
"composite_identifiers",
"config",
"cookies",
@@ -10406,6 +10674,7 @@ dependencies = [
"tokens",
"tokio",
"url",
"url-builder",
"uuid 1.8.0",
"workspace-build-acceleration",
]
@@ -12179,6 +12448,12 @@ dependencies = [
"serde",
]
[[package]]
name = "url-builder"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c560d19163937c53680c515b82a9311c5a796691c67890b8ad579423123e194"
[[package]]
name = "url_config"
version = "0.0.1"
+37 -32
View File
@@ -6,6 +6,7 @@ resolver = "2"
members = [
# API clients for external services
# These might be custom implementations or wrappers over other client libraries.
"crates/api_clients/artcraft_api_defs", # TODO: api_clients/{artcraft}/{api_defs,client}
"crates/api_clients/cloud_storage",
"crates/api_clients/fal_client",
"crates/api_clients/google_drive_common",
@@ -97,7 +98,6 @@ members = [
#"crates/schema/database/sqlite_queries",
# Schema (public): common strongly-typed tokens, ids, payloads, etc.
"crates/schema/public/api/artcraft_api_defs",
"crates/schema/public/composite_identifiers",
"crates/schema/public/enums",
"crates/schema/public/enums_public",
@@ -119,6 +119,8 @@ members = [
# Service Plugins: complete subsystems that can be plugged into services (users, billing, etc.)
"crates/service/plugins/billing",
"crates/service/plugins/billing_artcraft",
"crates/service/plugins/component_traits",
"crates/service/plugins/inference_workloads",
"crates/service/plugins/user_traits_component",
@@ -176,6 +178,40 @@ publish = false
#
workspace-build-acceleration = { path = "crates/workspace-build-acceleration" }
# Internal libs
# Setting paths here ensures we don't need to do so in every crate's Cargo.toml
# It makes refactoring much easier.
artcraft_api_defs = { path = "crates/api_clients/artcraft_api_defs" }
build_metadata = { path = "crates/lib/build_metadata" }
cloud_storage = { path = "crates/api_clients/cloud_storage" }
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
component_traits = { path = "crates/service/plugins/component_traits" }
cookie_store = { path = "crates/lib/cookie_store" }
crockford = { path = "crates/lib/data/crockford" }
enums = { path = "crates/schema/public/enums" }
enums_public = { path = "crates/schema/public/enums_public" }
errors = { path = "crates/lib/errors" }
fal_client = { path = "crates/api_clients/fal_client" }
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
filesys = { path = "crates/lib/files/filesys" }
idempotency = { path = "crates/lib/idempotency" }
images = { path = "crates/lib/files/images" }
jwt_light = { path = "crates/lib/jwt_light" }
memory_caching = { path = "crates/lib/caching/memory_caching" }
memory_store = { path = "crates/lib/caching/memory_store" }
midjourney_client = { path = "crates/api_clients/midjourney_client" }
mimetypes = { path = "crates/lib/files/mimetypes" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
primitives = { path = "crates/lib/data/primitives" }
shared_service_components = { path = "crates/lib/shared_service_components" }
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
storyteller_client = { path = "crates/api_clients/storyteller_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
test_data = { path = "crates/lib/test_data" }
testing = { path = "crates/lib/testing" }
tokens = { path = "crates/schema/public/tokens" }
web_base64 = { path = "crates/lib/data/web_base64" }
# 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
@@ -219,34 +255,3 @@ thiserror = "2.0.12"
tokio = { version = "1.45.1", features = ["macros"] }
toml = "0.8.22"
url = { version = "2.5.4", features = ["serde"] }
# Internal libs
artcraft_api_defs = { path = "crates/schema/public/api/artcraft_api_defs" }
build_metadata = { path = "crates/lib/build_metadata" }
cloud_storage = { path = "crates/api_clients/cloud_storage" }
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
cookie_store = { path = "crates/lib/cookie_store" }
crockford = { path = "crates/lib/data/crockford" }
enums = { path = "crates/schema/public/enums" }
enums_public = { path = "crates/schema/public/enums_public" }
errors = { path = "crates/lib/errors" }
fal_client = { path = "crates/api_clients/fal_client" }
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
filesys = { path = "crates/lib/files/filesys" }
idempotency = { path = "crates/lib/idempotency" }
images = { path = "crates/lib/files/images" }
jwt_light = { path = "crates/lib/jwt_light" }
memory_caching = { path = "crates/lib/caching/memory_caching" }
memory_store = { path = "crates/lib/caching/memory_store" }
midjourney_client = { path = "crates/api_clients/midjourney_client" }
mimetypes = { path = "crates/lib/files/mimetypes" }
openai_sora_client = { path = "crates/api_clients/openai_sora_client" }
primitives = { path = "crates/lib/data/primitives" }
shared_service_components = { path = "crates/lib/shared_service_components" }
sqlite_tasks = { path = "crates/schema/database/sqlite_tasks" }
storyteller_client = { path = "crates/api_clients/storyteller_client" }
subprocess_common = { path = "crates/lib/subprocess_common" }
test_data = { path = "crates/lib/test_data" }
testing = { path = "crates/lib/testing" }
tokens = { path = "crates/schema/public/tokens" }
web_base64 = { path = "crates/lib/data/web_base64" }
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE user_subscriptions DROP COLUMN maybe_stripe_invoice_is_paid;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE user_subscriptions
ADD COLUMN maybe_stripe_invoice_is_paid BOOLEAN DEFAULT NULL
AFTER maybe_stripe_subscription_status;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE wallets;
@@ -0,0 +1,66 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE wallets (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate wallets. This is effectively a wallet namespace.
-- NB: Uses the `PaymentsNamespace` enum, which is also shared with subscriptions.
wallet_namespace VARCHAR(32) NOT NULL,
-- Who owns the wallet
-- In the future, we might allow other users to use the same wallet,
-- but the wallet would still have an owner.
-- For now, we'll keep this 1:1 with users, but we may want 1:n in the future.
owner_user_token VARCHAR(32) NOT NULL,
-- The durable credits bucket.
-- This bucket is persistent and is refilled by user credit purchases.
-- Max value unsigned: 4,294,967,295 (U32)
banked_credits INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- The monthly credits bucket.
-- This bucket is ephemeral and is refilled monthly.
-- Max value unsigned: 4,294,967,295 (U32)
monthly_credits INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Whether the stripe subscription is active.
-- subscription_is_active BOOLEAN NOT NULL DEFAULT FALSE,
-- Whether the stripe subscription is paid.
-- If the subscription is not paid, we should not allow usage of monthly credits.
-- subscription_is_paid BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- ========== PAYMENTS AND SUBSCRIPTIONS TIMESTAMPS ==========
-- When the user last purchased banked (durable) credits.
-- banked_credits_last_purchased_at TIMESTAMP DEFAULT NULL,
-- When we last refilled the monthly bucket.
-- This has to do with subscription logic.
-- monthly_wallet_refilled_at TIMESTAMP DEFAULT NULL,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (wallet_namespace, owner_user_token), -- For now
INDEX idx_wallet_namespace (wallet_namespace),
INDEX idx_owner_user_token (owner_user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE wallet_ledger_entries;
@@ -0,0 +1,52 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE wallet_ledger_entries (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- Which wallet this entry corresponds to.
wallet_token VARCHAR(32) NOT NULL,
-- The type of ledger entry this is.
entry_type VARCHAR(32) NOT NULL,
-- A record we can look up to attribute the credit or deduction against
-- This could be an internal entity token (prompt_token, job token) or an
-- external token (stripe event_id, stripe invoice.id, etc.)
-- Length is VARCHAR(255) to accommodate Stripe IDs.
maybe_entity_ref VARCHAR(255) DEFAULT NULL,
-- Signed integer that shows the delta of credits for this event.
-- This combines impacts to both banked_credits and monthly_credits.
-- This is less authoritative than the before/after fields, but is useful
credits_delta INTEGER NOT NULL DEFAULT 0,
-- Balance of banked credits before the event.
-- Max value unsigned: 4,294,967,295 (U32)
banked_credits_before INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Balance of banked credits after the event.
banked_credits_after INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Balance of monthly credits before the event.
-- Max value unsigned: 4,294,967,295 (U32)
monthly_credits_before INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Balance of monthly credits after the event.
monthly_credits_after INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY index_wallet_token (wallet_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE user_subscriptions DROP COLUMN maybe_stripe_billing_cycle_anchor;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE user_subscriptions
ADD COLUMN maybe_stripe_billing_cycle_anchor TIMESTAMP DEFAULT NULL
AFTER maybe_stripe_invoice_is_paid;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE stripe_webhook_event_logs DROP COLUMN maybe_internal_entity_token;
@@ -0,0 +1,7 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
ALTER TABLE stripe_webhook_event_logs
ADD COLUMN maybe_internal_entity_token VARCHAR(32) DEFAULT NULL
AFTER maybe_user_token;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
DROP TABLE user_stripe_customer_links;
@@ -0,0 +1,36 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- 1:1 links between a user and a stripe customer (per payments namespace)
-- We can hard delete records if we have to.
-- If users need a new stripe customer ID, we need to hard delete the existing record.
CREATE TABLE user_stripe_customer_links (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Composite primary key
user_token VARCHAR(32) NOT NULL,
-- Composite primary key
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate accounts.
-- NB: Uses the `PaymentsNamespace` enum, which is also shared with subscriptions.
payments_namespace VARCHAR(32) NOT NULL,
-- Unique key. (We can loosen to `stripe_customer_id, wallet_namespace` later if
-- we want to share billing.)
stripe_customer_id VARCHAR(255) NOT NULL,
-- ========== RECORD 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 (user_token, payments_namespace),
-- UNIQUE KEY (stripe_customer_id, payments_namespace)
UNIQUE KEY (stripe_customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
drop table analytics_app_active_users;
@@ -0,0 +1,59 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Track user activity over time
-- User clients ping the endpoint that writes this data.
CREATE TABLE analytics_app_active_users (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate analytics.
app_namespace VARCHAR(32) NOT NULL,
-- Who sent the event.
user_token VARCHAR(32) NOT NULL,
-- Updated on every write.
-- The "user agent" or app version string of the client.
app_version VARCHAR(255) DEFAULT NULL,
-- Updated on every write.
-- The OS platform for the client.
os_platform VARCHAR(16) DEFAULT NULL,
-- Updated on every write.
-- The OS platform version for the client.
os_version VARCHAR(64) DEFAULT NULL,
-- Updated on every write.
-- How long the current user's session has been open
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
-- Updated on every write.
-- The user's last known IP address.
ip_address VARCHAR(40) NOT NULL,
-- Incrementing count of events.
-- NB: This is kind of an "hours spent" measure, but will lose information if we ever
-- change the ping cadence (eg. 1 minute pings --> 5 minute pings).
measurement_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
-- Written at create only.
first_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Updated on every ping.
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (app_namespace, user_token), -- For now
INDEX idx_os_platform (os_platform),
INDEX idx_os_version (os_version),
INDEX idx_app_version (app_version),
INDEX idx_last_active_at (last_active_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,5 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
drop table analytics_app_sessions;
@@ -0,0 +1,65 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Track user session durations
-- User clients ping the endpoint that writes this data.
CREATE TABLE analytics_app_sessions (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Generated client-side
-- Wide enough for a UUID, but typically one of our internal tokens.
session_token VARCHAR(36) NOT NULL,
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate analytics.
app_namespace VARCHAR(32) NOT NULL,
-- Who sent the event.
user_token VARCHAR(32) NOT NULL,
-- Updated on every write.
-- The "user agent" or app version string of the client.
app_version VARCHAR(255) DEFAULT NULL,
-- Updated on every write.
-- The OS platform for the client.
os_platform VARCHAR(16) DEFAULT NULL,
-- Updated on every write.
-- The OS platform version for the client.
os_version VARCHAR(64) DEFAULT NULL,
-- Updated on every write.
-- How long the current user's session has been open
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
-- Updated on every write.
-- The user's last known IP address.
ip_address VARCHAR(40) NOT NULL,
-- Incrementing count of events.
-- NB: This is kind of an "hours spent" measure, but will lose information if we ever
-- change the ping cadence (eg. 1 minute pings --> 5 minute pings).
measurement_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
-- Written at create only.
first_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Updated on every ping.
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (session_token),
INDEX idx_app_namespace (app_namespace),
INDEX idx_user_token (user_token),
INDEX idx_os_platform (os_platform),
INDEX idx_os_version (os_version),
INDEX idx_app_version (app_version),
INDEX idx_last_active_at (last_active_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,57 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE analytics_app_active_users (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate analytics.
app_namespace VARCHAR(32) NOT NULL,
-- Who sent the event.
user_token VARCHAR(32) NOT NULL,
-- Updated on every write.
-- The "user agent" or app version string of the client.
app_version VARCHAR(255) DEFAULT NULL,
-- Updated on every write.
-- The OS platform for the client.
os_platform VARCHAR(16) DEFAULT NULL,
-- Updated on every write.
-- The OS platform version for the client.
os_version VARCHAR(64) DEFAULT NULL,
-- Updated on every write.
-- How long the current user's session has been open
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
-- Updated on every write.
-- The user's last known IP address.
ip_address VARCHAR(40) NOT NULL,
-- Incrementing count of events.
-- NB: This is kind of an "hours spent" measure, but will lose information if we ever
-- change the ping cadence (eg. 1 minute pings --> 5 minute pings).
measurement_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
-- Written at create only.
first_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Updated on every ping.
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (app_namespace, user_token), -- For now
INDEX idx_app_version (app_version),
INDEX idx_os_platform (os_platform),
INDEX idx_os_version (os_version),
INDEX idx_last_active_at (last_active_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,65 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- Track user session durations
-- User clients ping the endpoint that writes this data.
CREATE TABLE analytics_app_sessions (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Generated client-side
-- Wide enough for a UUID, but typically one of our internal tokens.
session_token VARCHAR(36) NOT NULL,
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate analytics.
app_namespace VARCHAR(32) NOT NULL,
-- Who sent the event.
user_token VARCHAR(32) NOT NULL,
-- Updated on every write.
-- The "user agent" or app version string of the client.
app_version VARCHAR(255) DEFAULT NULL,
-- Updated on every write.
-- The OS platform for the client.
os_platform VARCHAR(16) DEFAULT NULL,
-- Updated on every write.
-- The OS platform version for the client.
os_version VARCHAR(64) DEFAULT NULL,
-- Updated on every write.
-- How long the current user's session has been open
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
-- Updated on every write.
-- The user's last known IP address.
ip_address VARCHAR(40) NOT NULL,
-- Incrementing count of events.
-- NB: This is kind of an "hours spent" measure, but will lose information if we ever
-- change the ping cadence (eg. 1 minute pings --> 5 minute pings).
measurement_count BIGINT UNSIGNED NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
-- Written at create only.
first_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Updated on every ping.
last_active_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (session_token),
INDEX idx_app_namespace (app_namespace),
INDEX idx_user_token (user_token),
INDEX idx_os_platform (os_platform),
INDEX idx_os_version (os_version),
INDEX idx_app_version (app_version),
INDEX idx_last_active_at (last_active_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,66 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE stripe_webhook_event_logs (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- ========== STRIPE DATA ==========
-- You can safely assume object IDs we generate will never exceed 255 characters,
-- but you should be able to handle IDs of up to that length. If for example you’re using MySQL,
-- you should store IDs in a VARCHAR(255) COLLATE utf8_bin column (the COLLATE configuration ensures
-- case-sensitivity in lookups).
-- https://stackoverflow.com/a/61809494
stripe_event_id VARCHAR(255) NOT NULL,
-- The type of webhook event
stripe_event_type VARCHAR(255) NOT NULL,
-- The id of the entity that was the subject of the event.
-- We don't always extract this.
maybe_stripe_event_entity_id VARCHAR(255) DEFAULT NULL,
-- The id of the entity that was the subject of the event.
-- We don't always extract this.
maybe_stripe_customer_id VARCHAR(255) DEFAULT NULL,
-- Whether this is in production or test mode in Stripe.
-- This is controlled by which API keys are used.
-- "livemode"=true in production.
stripe_is_production BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== INTERNAL SYSTEM METADATA ==========
-- Whether we handled the event with any sort of action
action_was_taken BOOLEAN NOT NULL DEFAULT FALSE,
-- Whether we should ignore any future retries by Stripe replaying the event.
-- This helps us treat events with idempotency.
should_ignore_retry BOOLEAN NOT NULL DEFAULT FALSE,
-- Some event types may attach a user token
maybe_user_token VARCHAR(32) DEFAULT NULL,
-- An internal optional entity token this resolves to.
-- eg. `wallets.token` or `user_subscriptions.token`.
maybe_internal_entity_token VARCHAR(32) DEFAULT NULL,
-- ========== TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- When the event was created on Stripe's end
stripe_event_created_at TIMESTAMP NOT NULL,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (stripe_event_id),
KEY index_stripe_event_type (stripe_event_type),
KEY index_maybe_stripe_event_entity_id (maybe_stripe_event_entity_id),
KEY index_maybe_stripe_customer_id (maybe_stripe_customer_id),
KEY fk_maybe_user_token (maybe_user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,36 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
-- 1:1 links between a user and a stripe customer (per payments namespace)
-- We can hard delete records if we have to.
-- If users need a new stripe customer ID, we need to hard delete the existing record.
CREATE TABLE user_stripe_customer_links (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Composite primary key
user_token VARCHAR(32) NOT NULL,
-- Composite primary key
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate accounts.
-- NB: Uses the `PaymentsNamespace` enum, which is also shared with subscriptions.
payments_namespace VARCHAR(32) NOT NULL,
-- Unique key. (We can loosen to `stripe_customer_id, wallet_namespace` later if
-- we want to share billing.)
stripe_customer_id VARCHAR(255) NOT NULL,
-- ========== RECORD 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 (user_token, payments_namespace),
-- UNIQUE KEY (stripe_customer_id, payments_namespace)
UNIQUE KEY (stripe_customer_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -53,6 +53,18 @@ CREATE TABLE user_subscriptions (
-- Subscription object status enum as string
maybe_stripe_subscription_status VARCHAR(32) DEFAULT NULL,
-- Primarily managed by the `invoice.paid` and `invoice.payment_failed` webhooks.
-- NB: Not applicable to FakeYou subscriptions!
maybe_stripe_invoice_is_paid BOOLEAN DEFAULT NULL,
-- Which day of the month, or month of the year, etc. to anchor the subscription to.
-- See docs on the subscription object.
-- Stripe:
-- "The reference point that aligns future billing cycle dates. It sets the day
-- of week for week intervals, the day of month for month and year intervals, and
-- the month of year for year intervals. The timestamp is in UTC format."
maybe_stripe_billing_cycle_anchor TIMESTAMP DEFAULT NULL,
maybe_stripe_is_production BOOLEAN DEFAULT NULL,
-- ========== VECTOR CLOCK ==========
@@ -64,6 +76,8 @@ CREATE TABLE user_subscriptions (
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- NB(bt, 2025-09-16): I don't think we write to the soft deleted_at field for user_subscriptions.
deleted_at TIMESTAMP DEFAULT NULL,
-- ========== SUBSCRIPTION TIMESTAMPS ==========
@@ -72,7 +86,10 @@ CREATE TABLE user_subscriptions (
-- This may predate the Stripe object `created` timestamp due to backdating.
subscription_start_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- We'll use this to determine if the subscription is active.
-- Our synthetic "expire" date. Moved forward for active subscriptions,
-- backdated for canceled subscriptions.
--
-- TODO: We'll (maybe?) use this to determine if the subscription is active.
--
-- Always compare against MySQL's clock rather than the app's clock so that we
-- don't get weird clock skew behaviors across multiple requests.
@@ -82,8 +99,12 @@ CREATE TABLE user_subscriptions (
subscription_expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Billing periods.
-- Maybe useful for debugging.
-- Stripe API: subscription.items[0].data.current_period_start
current_billing_period_start_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Stripe API: subscription.items[0].data.current_period_end
-- current_period_end is when the subscription renews (or cancels, if the
-- subscription is set to cancel).
current_billing_period_end_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- Subscription cancellation (future and past)
@@ -186,8 +186,8 @@ CREATE TABLE users (
-- ========== TRACKING ==========
-- Where the user signed up from (NOT NECESSARILY AN ENUM!)
-- For now this will be "fakeyou" and "storyteller", but we may extend
-- or overload this to handle other cases or metadata.
-- For now this will be "artcraft", "fakeyou", or "storyteller",
-- though we may extend or overload this to handle other cases or metadata.
maybe_source VARCHAR(255) DEFAULT NULL,
-- How users created their account
@@ -0,0 +1,52 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE wallet_ledger_entries (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- Which wallet this entry corresponds to.
wallet_token VARCHAR(32) NOT NULL,
-- The type of ledger entry this is.
entry_type VARCHAR(16) NOT NULL,
-- A record we can look up to attribute the credit or deduction against
-- This could be an internal entity token (prompt_token, job token) or an
-- external token (stripe event_id, stripe invoice.id, etc.)
-- Length is VARCHAR(255) to accommodate Stripe IDs.
maybe_entity_ref VARCHAR(255) DEFAULT NULL,
-- Signed integer that shows the delta of credits for this event.
-- This combines impacts to both banked_credits and monthly_credits.
-- This is less authoritative than the before/after fields, but is useful
credits_delta INTEGER NOT NULL DEFAULT 0,
-- Balance of banked credits before the event.
-- Max value unsigned: 4,294,967,295 (U32)
banked_credits_before INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Balance of banked credits after the event.
banked_credits_after INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Balance of monthly credits before the event.
-- Max value unsigned: 4,294,967,295 (U32)
monthly_credits_before INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Balance of monthly credits after the event.
monthly_credits_after INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (token),
KEY index_wallet_token (wallet_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,66 @@
-- noinspection SqlDialectInspectionForFile
-- noinspection SqlNoDataSourceInspectionForFile
-- noinspection SqlResolveForFile
CREATE TABLE wallets (
-- Not used for anything except replication.
id BIGINT(20) NOT NULL AUTO_INCREMENT,
-- Effective "primary key" (PUBLIC)
token VARCHAR(32) NOT NULL,
-- If we host multiple sites with distinct categories, this will enable us to
-- segregate wallets. This is effectively a wallet namespace.
-- NB: Uses the `PaymentsNamespace` enum, which is also shared with subscriptions.
wallet_namespace VARCHAR(32) NOT NULL,
-- Who owns the wallet
-- In the future, we might allow other users to use the same wallet,
-- but the wallet would still have an owner.
-- For now, we'll keep this 1:1 with users, but we may want 1:n in the future.
owner_user_token VARCHAR(32) NOT NULL,
-- The durable credits bucket.
-- This bucket is persistent and is refilled by user credit purchases.
-- Max value unsigned: 4,294,967,295 (U32)
banked_credits INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- The monthly credits bucket.
-- This bucket is ephemeral and is refilled monthly.
-- Max value unsigned: 4,294,967,295 (U32)
monthly_credits INTEGER UNSIGNED NOT NULL DEFAULT 0,
-- Whether the stripe subscription is active.
-- subscription_is_active BOOLEAN NOT NULL DEFAULT FALSE,
-- Whether the stripe subscription is paid.
-- If the subscription is not paid, we should not allow usage of monthly credits.
-- subscription_is_paid BOOLEAN NOT NULL DEFAULT FALSE,
-- ========== VECTOR CLOCK ==========
-- Incremented with every update.
version INT NOT NULL DEFAULT 0,
-- ========== RECORD TIMESTAMPS ==========
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-- ========== PAYMENTS AND SUBSCRIPTIONS TIMESTAMPS ==========
-- When the user last purchased banked (durable) credits.
-- banked_credits_last_purchased_at TIMESTAMP DEFAULT NULL,
-- When we last refilled the monthly bucket.
-- This has to do with subscription logic.
-- monthly_wallet_refilled_at TIMESTAMP DEFAULT NULL,
-- ========== INDICES ==========
PRIMARY KEY (id),
UNIQUE KEY (token),
UNIQUE KEY (wallet_namespace, owner_user_token), -- For now
INDEX idx_wallet_namespace (wallet_namespace),
INDEX idx_owner_user_token (owner_user_token)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
@@ -0,0 +1,40 @@
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::app_session::AppSessionToken;
use utoipa::ToSchema;
pub const LOG_ACTIVE_USER_PATH: &str = "/v1/analytics/active_user";
#[derive(Serialize, Deserialize, ToSchema, Debug)]
pub struct LogAppActiveUserRequest {
/// Clientside-generated session token.
/// If present, it'll be validated against an expected format.
pub maybe_app_session_token: Option<AppSessionToken>,
/// An override for the application platform/OS (windows, mac, linux).
pub maybe_os_platform: Option<String>,
/// An override for the version of the OS (e.g. 10.15.7, 11, 22.04).
pub maybe_os_version: Option<String>,
/// An override for the application name.
/// If set together with `maybe_app_version`, the two will be
/// concatenated as `{maybe_app_name}/{maybe_app_version}`.
pub maybe_app_name: Option<String>,
/// An override for the application version.
/// If set together with `maybe_app_name`, the two will be
/// concatenated as `{maybe_app_name}/{maybe_app_version}`.
pub maybe_app_version: Option<String>,
/// How long the user has been active in the app, in seconds.
pub maybe_session_duration_seconds: Option<u64>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct LogAppActiveUserResponse {
pub success: bool,
/// How long to wait until the next analytics event, in milliseconds.
/// The client should honor this and is free to add jitter.
pub wait_for_retry_millis: u64,
}
@@ -0,0 +1 @@
pub mod log_active_user;
@@ -0,0 +1,20 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Serialize, Deserialize, ToSchema)]
pub struct GetSessionCreditsResponse {
pub success: bool,
/// Any free credits the user might have
/// We might support daily free credits.
pub free_credits: u64,
/// Any monthly refilling credits the user might have
pub monthly_credits: u64,
/// Any banked credits the user might have.
pub banked_credits: u64,
/// All the credit amounts added together.
pub sum_total_credits: u64,
}
@@ -0,0 +1 @@
pub mod get_session_credits;
@@ -0,0 +1,9 @@
pub mod analytics;
pub mod common;
pub mod credits;
pub mod generate;
pub mod jobs;
pub mod media_file;
pub mod prompts;
pub mod stripe_artcraft;
pub mod subscriptions;
@@ -0,0 +1,17 @@
use enums::common::artcraft_credits_pack_slug::ArtcraftCreditsPackSlug;
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
pub const CREATE_CREDITS_PACK_CHECKOUT_URL_PATH: &str = "/v1/stripe_artcraft/checkout/credits_pack";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCreateCreditsPackCheckoutRequest {
pub credits_pack: Option<ArtcraftCreditsPackSlug>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCreateCreditsPackCheckoutResponse {
pub success: bool,
pub stripe_checkout_redirect_url: String,
}
@@ -0,0 +1,30 @@
use enums::common::artcraft_subscription_slug::ArtcraftSubscriptionSlug;
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
pub const CREATE_SUBSCRIPTION_CHECKOUT_URL_PATH: &str = "/v1/stripe_artcraft/checkout/subscription";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCreateSubscriptionCheckoutRequest {
/// The (non-Stripe) internal identifier for the product or subscription.
/// This will be translated into a Stripe identifier.
pub plan: Option<ArtcraftSubscriptionSlug>,
pub cadence: Option<PlanBillingCadence>,
}
#[derive(Serialize, Deserialize, ToSchema, Debug, Copy, Clone)]
pub enum PlanBillingCadence {
#[serde(rename = "monthly")]
Monthly,
#[serde(rename = "yearly")]
Yearly,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCreateSubscriptionCheckoutResponse {
pub success: bool,
pub stripe_checkout_redirect_url: String,
}
@@ -0,0 +1,17 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
pub const CUSTOMER_PORTAL_CANCEL_PLAN_URL_PATH: &str = "/v1/stripe_artcraft/portal/cancel_plan";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalCancelPlanRequest {
// TODO: Not sure if this is needed
pub portal_config_id: Option<String>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalCancelPlanResponse {
pub success: bool,
pub stripe_portal_url: String,
}
@@ -0,0 +1,17 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
pub const CUSTOMER_PORTAL_MANAGE_PLAN_URL_PATH: &str = "/v1/stripe_artcraft/portal/manage_plan";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalManagePlanRequest {
// TODO: Not sure if this is needed
pub portal_config_id: Option<String>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalManagePlanResponse {
pub success: bool,
pub stripe_portal_url: String,
}
@@ -0,0 +1,37 @@
use enums::common::artcraft_subscription_slug::ArtcraftSubscriptionSlug;
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
pub const CUSTOMER_PORTAL_SWITCH_PLAN_URL_PATH: &str = "/v1/stripe_artcraft/portal/switch_plan";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalSwitchPlanRequest {
// TODO: Not sure if this is needed
pub portal_config_id: Option<String>,
/// REQUIRED
/// The plan we're switching to.
pub plan: Option<ArtcraftSubscriptionSlug>,
/// REQUIRED
/// The cadence for the plan billing.
pub cadence: Option<PlanBillingCadenceConfirmation>,
}
#[derive(Serialize, Deserialize, ToSchema, Debug, Copy, Clone)]
pub enum PlanBillingCadenceConfirmation {
#[serde(rename = "monthly")]
Monthly,
#[serde(rename = "yearly")]
Yearly,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalSwitchPlanResponse {
pub success: bool,
pub stripe_portal_url: String,
}
@@ -0,0 +1,17 @@
use serde_derive::{Deserialize, Serialize};
use utoipa::ToSchema;
pub const CUSTOMER_PORTAL_UPDATE_PAYMENT_METHOD_URL_PATH: &str = "/v1/stripe_artcraft/portal/update_payment_method";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalUpdatePaymentMethodRequest {
// TODO: Not sure if this is needed
pub portal_config_id: Option<String>,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct StripeArtcraftCustomerPortalUpdatePaymentMethodResponse {
pub success: bool,
pub stripe_portal_url: String,
}

Some files were not shown because too many files have changed in this diff Show More