mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-29 04:52:51 -04:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e95be9bdb | ||
|
|
b7667976df | ||
|
|
e07d302df1 | ||
|
|
0de867ad13 | ||
|
|
a1cb1b2629 | ||
|
|
8da68b009c | ||
|
|
e4bc031fbb | ||
|
|
d937a7f78f | ||
|
|
fef96c3835 | ||
|
|
b9e793098c |
+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"
|
||||
}
|
||||
+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": "\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 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"
|
||||
}
|
||||
+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"
|
||||
}
|
||||
Generated
+494
-564
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -342,11 +342,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"
|
||||
|
||||
@@ -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,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,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,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod analytics;
|
||||
pub mod api_keys;
|
||||
pub mod characters;
|
||||
pub mod common;
|
||||
pub mod credits;
|
||||
|
||||
@@ -19,7 +19,7 @@ name = "artcraft_app_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.1.1", features = [] }
|
||||
tauri-build = { version = "2.6.2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
# Workspace hack (faster compile times)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
/// A materialized `api_keys` row, minus the internal `id` and the full secret
|
||||
/// `api_key`. `api_key_prefix` is the first 20 characters of the key — enough to
|
||||
/// disambiguate keys in a UI without ever exposing the secret value.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ApiKeyRow {
|
||||
pub token: ApiKeyToken,
|
||||
|
||||
/// First 20 characters of the `api_key`. The full secret is never returned.
|
||||
pub api_key_prefix: 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>,
|
||||
pub maybe_deleted_at: Option<DateTime<Utc>>,
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
|
||||
pub struct DeleteApiKeyArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub token: &'e ApiKeyToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Soft-delete an API key by stamping `maybe_deleted_at = NOW()`. Returns the
|
||||
/// number of rows affected (0 if no live key matched the token).
|
||||
pub async fn delete_api_key<'e, 'c: 'e, E>(
|
||||
args: DeleteApiKeyArgs<'e, 'c, E>,
|
||||
) -> Result<u64, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
UPDATE api_keys
|
||||
SET maybe_deleted_at = NOW()
|
||||
WHERE token = ?
|
||||
AND maybe_deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.token.as_str(),
|
||||
)
|
||||
.execute(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::api_keys::api_key_row::ApiKeyRow;
|
||||
|
||||
pub struct GetApiKeyByTokenArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub token: &'e ApiKeyToken,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Look up an API key by its `token`. Returns every detail (including
|
||||
/// `maybe_deleted_at`, so soft-deleted keys are still visible here) except the
|
||||
/// internal `id` and the full secret `api_key` (only its first 20 characters
|
||||
/// are returned). `Ok(None)` if no row matches the token.
|
||||
pub async fn get_api_key_by_token<'e, 'c: 'e, E>(
|
||||
args: GetApiKeyByTokenArgs<'e, 'c, E>,
|
||||
) -> Result<Option<ApiKeyRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
token as `token: ApiKeyToken`,
|
||||
LEFT(api_key, 20) as `api_key_prefix!`,
|
||||
name,
|
||||
maybe_description,
|
||||
owner_user_token as `owner_user_token: UserToken`,
|
||||
ip_address_creation,
|
||||
ip_address_update,
|
||||
created_at,
|
||||
updated_at,
|
||||
maybe_deleted_at
|
||||
FROM api_keys
|
||||
WHERE token = ?
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.token.as_str(),
|
||||
)
|
||||
.fetch_optional(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| ApiKeyRow {
|
||||
token: r.token,
|
||||
api_key_prefix: r.api_key_prefix,
|
||||
name: r.name,
|
||||
maybe_description: r.maybe_description,
|
||||
owner_user_token: r.owner_user_token,
|
||||
ip_address_creation: r.ip_address_creation,
|
||||
ip_address_update: r.ip_address_update,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
maybe_deleted_at: r.maybe_deleted_at,
|
||||
}))
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::api_keys::api_key_row::ApiKeyRow;
|
||||
|
||||
pub struct GetLiveApiKeyByValueArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
/// The actual API key secret presented by the client.
|
||||
pub api_key: &'e str,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Look up a **live** API key by its secret value (for authenticating an
|
||||
/// incoming request). Returns `Ok(None)` if no row matches OR if the matching
|
||||
/// row is soft-deleted (`maybe_deleted_at IS NOT NULL`). The full secret is
|
||||
/// never echoed back — only its first 20 characters — and the internal `id` is
|
||||
/// omitted.
|
||||
pub async fn get_live_api_key_by_value<'e, 'c: 'e, E>(
|
||||
args: GetLiveApiKeyByValueArgs<'e, 'c, E>,
|
||||
) -> Result<Option<ApiKeyRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
token as `token: ApiKeyToken`,
|
||||
LEFT(api_key, 20) as `api_key_prefix!`,
|
||||
name,
|
||||
maybe_description,
|
||||
owner_user_token as `owner_user_token: UserToken`,
|
||||
ip_address_creation,
|
||||
ip_address_update,
|
||||
created_at,
|
||||
updated_at,
|
||||
maybe_deleted_at
|
||||
FROM api_keys
|
||||
WHERE api_key = ?
|
||||
AND maybe_deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.api_key,
|
||||
)
|
||||
.fetch_optional(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.map(|r| ApiKeyRow {
|
||||
token: r.token,
|
||||
api_key_prefix: r.api_key_prefix,
|
||||
name: r.name,
|
||||
maybe_description: r.maybe_description,
|
||||
owner_user_token: r.owner_user_token,
|
||||
ip_address_creation: r.ip_address_creation,
|
||||
ip_address_update: r.ip_address_update,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
maybe_deleted_at: r.maybe_deleted_at,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct InsertApiKeyArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub owner_user_token: &'e UserToken,
|
||||
pub ip_address: &'e str,
|
||||
pub name: &'e str,
|
||||
pub maybe_description: Option<&'e str>,
|
||||
pub api_key: &'e str,
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Insert a new API key. The `ApiKeyToken` is minted here (in the data-access
|
||||
/// layer, not the handler) and returned to the caller. The provided IP address
|
||||
/// is stamped on both the creation and update IP columns, and `created_at` is
|
||||
/// set to `NOW()`.
|
||||
pub async fn insert_api_key<'e, 'c: 'e, E>(
|
||||
args: InsertApiKeyArgs<'e, 'c, E>,
|
||||
) -> Result<ApiKeyToken, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let token = ApiKeyToken::generate();
|
||||
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO api_keys
|
||||
SET
|
||||
token = ?,
|
||||
api_key = ?,
|
||||
name = ?,
|
||||
maybe_description = ?,
|
||||
owner_user_token = ?,
|
||||
ip_address_creation = ?,
|
||||
ip_address_update = ?,
|
||||
created_at = NOW()
|
||||
"#,
|
||||
token.as_str(),
|
||||
args.api_key,
|
||||
args.name,
|
||||
args.maybe_description,
|
||||
args.owner_user_token.as_str(),
|
||||
args.ip_address,
|
||||
args.ip_address,
|
||||
)
|
||||
.execute(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(token)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::queries::api_keys::api_key_row::ApiKeyRow;
|
||||
|
||||
pub struct ListApiKeysForUserArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub owner_user_token: &'e UserToken,
|
||||
|
||||
/// Page size and page offset. Pagination is LIMIT/OFFSET rather than keyset
|
||||
/// so we don't have to expose the internal `id`.
|
||||
pub limit: u32,
|
||||
pub offset: u32,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// List a user's API keys, newest first, **including soft-deleted** keys.
|
||||
/// Never returns the full `api_key` — only its first 20 characters — and never
|
||||
/// returns the internal `id`.
|
||||
pub async fn list_api_keys_for_user<'e, 'c: 'e, E>(
|
||||
args: ListApiKeysForUserArgs<'e, 'c, E>,
|
||||
) -> Result<Vec<ApiKeyRow>, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let limit = args.limit as i64;
|
||||
let offset = args.offset as i64;
|
||||
|
||||
let rows = sqlx::query!(
|
||||
r#"
|
||||
SELECT
|
||||
token as `token: ApiKeyToken`,
|
||||
LEFT(api_key, 20) as `api_key_prefix!`,
|
||||
name,
|
||||
maybe_description,
|
||||
owner_user_token as `owner_user_token: UserToken`,
|
||||
ip_address_creation,
|
||||
ip_address_update,
|
||||
created_at,
|
||||
updated_at,
|
||||
maybe_deleted_at
|
||||
FROM api_keys
|
||||
WHERE owner_user_token = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT ? OFFSET ?
|
||||
"#,
|
||||
args.owner_user_token.as_str(),
|
||||
limit,
|
||||
offset,
|
||||
)
|
||||
.fetch_all(args.mysql_executor)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|r| ApiKeyRow {
|
||||
token: r.token,
|
||||
api_key_prefix: r.api_key_prefix,
|
||||
name: r.name,
|
||||
maybe_description: r.maybe_description,
|
||||
owner_user_token: r.owner_user_token,
|
||||
ip_address_creation: r.ip_address_creation,
|
||||
ip_address_update: r.ip_address_update,
|
||||
created_at: r.created_at,
|
||||
updated_at: r.updated_at,
|
||||
maybe_deleted_at: r.maybe_deleted_at,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
pub mod api_key_row;
|
||||
pub mod delete_api_key;
|
||||
pub mod get_api_key_by_token;
|
||||
pub mod get_live_api_key_by_value;
|
||||
pub mod insert_api_key;
|
||||
pub mod list_api_keys_for_user;
|
||||
pub mod update_api_key;
|
||||
@@ -0,0 +1,48 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
|
||||
pub struct UpdateApiKeyArgs<'e, 'c, E>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
pub token: &'e ApiKeyToken,
|
||||
pub maybe_description: Option<&'e str>,
|
||||
|
||||
/// IP address of the request making the update; stamped on `ip_address_update`.
|
||||
pub ip_address: &'e str,
|
||||
|
||||
pub mysql_executor: E,
|
||||
pub phantom: PhantomData<&'c E>,
|
||||
}
|
||||
|
||||
/// Update an API key's `maybe_description`, also refreshing `ip_address_update`
|
||||
/// (and, via the column's `ON UPDATE` clause, `updated_at`). Scoped to live keys
|
||||
/// only. Returns the number of rows affected (0 if no live key matched).
|
||||
pub async fn update_api_key<'e, 'c: 'e, E>(
|
||||
args: UpdateApiKeyArgs<'e, 'c, E>,
|
||||
) -> Result<u64, sqlx::Error>
|
||||
where
|
||||
E: 'e + Executor<'c, Database = MySql>,
|
||||
{
|
||||
let result = sqlx::query!(
|
||||
r#"
|
||||
UPDATE api_keys
|
||||
SET
|
||||
maybe_description = ?,
|
||||
ip_address_update = ?
|
||||
WHERE token = ?
|
||||
AND maybe_deleted_at IS NULL
|
||||
LIMIT 1
|
||||
"#,
|
||||
args.maybe_description,
|
||||
args.ip_address,
|
||||
args.token.as_str(),
|
||||
)
|
||||
.execute(args.mysql_executor)
|
||||
.await?;
|
||||
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
pub mod analytics_active_users;
|
||||
pub mod api_keys;
|
||||
pub mod api_tokens;
|
||||
pub mod audit_logs;
|
||||
pub mod batch_generations;
|
||||
|
||||
+2
-3
@@ -84,9 +84,8 @@ pub struct SessionUserRecord {
|
||||
}
|
||||
|
||||
impl SessionUserRecord {
|
||||
// TODO(bt, 2022-12-20): Convert all users of the bare record to using `UserToken`, then get rid of this method.
|
||||
pub fn get_strongly_typed_user_token(&self) -> UserToken {
|
||||
self.user_token.clone()
|
||||
pub fn get_user_token(&self) -> &UserToken {
|
||||
&self.user_token
|
||||
}
|
||||
|
||||
pub fn is_mod(&self) -> bool {
|
||||
|
||||
+7
-6
@@ -1,8 +1,7 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::common::payments_namespace::PaymentsNamespace;
|
||||
use errors::AnyhowResult;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::MySql;
|
||||
use sqlx::{Executor, MySql};
|
||||
|
||||
pub struct ActiveUserSubscription {
|
||||
pub user_token: String,
|
||||
@@ -19,10 +18,12 @@ pub struct ActiveUserSubscription {
|
||||
pub subscription_expires_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub async fn list_active_user_subscriptions(
|
||||
mysql_connection: &mut PoolConnection<MySql>,
|
||||
pub async fn list_active_user_subscriptions<'e, 'c : 'e, E>(
|
||||
mysql_executor: E,
|
||||
user_token: &str
|
||||
) -> AnyhowResult<Vec<ActiveUserSubscription>> {
|
||||
) -> AnyhowResult<Vec<ActiveUserSubscription>>
|
||||
where E: 'e + Executor<'c, Database = MySql>
|
||||
{
|
||||
// NB: "status=incomplete" subscriptions can happen when a user submits credit card info,
|
||||
// but the CRC is wrong. Eg. what happened with ftx on 2022.11.18.
|
||||
//
|
||||
@@ -45,7 +46,7 @@ WHERE
|
||||
"#,
|
||||
user_token,
|
||||
)
|
||||
.fetch_all(&mut **mysql_connection)
|
||||
.fetch_all(mysql_executor)
|
||||
.await?;
|
||||
|
||||
let records = records.into_iter()
|
||||
|
||||
@@ -23,6 +23,7 @@ use strum::EnumIter;
|
||||
#[cfg_attr(test, derive(EnumIter, EnumCount))]
|
||||
pub(crate) enum TokenPrefix {
|
||||
AnonymousVisitorTracking, // AVTs are not stored as primary keys in any table, but an index in many tables.
|
||||
ApiKey,
|
||||
AppSession, // NB: These are generated client-side (!)
|
||||
AuditLog,
|
||||
BatchGeneration,
|
||||
@@ -112,6 +113,7 @@ impl PrefixGenerator for TokenPrefix {
|
||||
fn prefix(self) -> &'static str {
|
||||
match self {
|
||||
Self::AnonymousVisitorTracking => "avt_",
|
||||
Self::ApiKey => "api_key_",
|
||||
Self::AppSession => "app_session_",
|
||||
Self::AuditLog => "audit_",
|
||||
Self::BatchGeneration => "batch_g_",
|
||||
@@ -348,6 +350,7 @@ mod tests {
|
||||
|| prefix == "INT_API:"
|
||||
|| prefix == "batch_g_"
|
||||
|| prefix == "beta_key_"
|
||||
|| prefix == "api_key_"
|
||||
|| prefix == "email_job_"
|
||||
|| prefix == "pw_reset_"
|
||||
|| prefix == "tts_task_"
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
/// The primary key for api_keys.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize, Default, ToSchema)]
|
||||
#[cfg_attr(feature = "database", derive(sqlx::Type))]
|
||||
#[cfg_attr(feature = "database", sqlx(transparent))]
|
||||
pub struct ApiKeyToken(pub String);
|
||||
|
||||
impl_mysql_token_from_row!(ApiKeyToken);
|
||||
impl_string_token!(ApiKeyToken);
|
||||
impl_crockford_generator!(ApiKeyToken, 32usize, TokenPrefix::ApiKey, CrockfordLower);
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod anonymous_visitor_tracking;
|
||||
pub mod api_keys;
|
||||
pub mod api_tokens_external;
|
||||
pub mod api_tokens_internal;
|
||||
pub mod app_session;
|
||||
|
||||
@@ -1,15 +1,36 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use log::info;
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use shared_env_var_config::mysql::env_get_mysql_connection_string_or_default;
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use sqlx::MySqlPool;
|
||||
|
||||
pub async fn connect_to_database() -> AnyhowResult<MySqlPool>{
|
||||
pub async fn connect_to_database() -> AnyhowResult<MySqlPool> {
|
||||
let db_connection_string = env_get_mysql_connection_string_or_default();
|
||||
|
||||
let pool_options = MySqlPoolOptions::new()
|
||||
.max_connections(easyenv::get_env_num("MYSQL_MAX_CONNECTIONS", 5)?);
|
||||
// NB: The managed DB's connection ceiling is far above what we use, so the binding
|
||||
// constraint is this per-pod pool. Keep `replicas * MYSQL_MAX_CONNECTIONS` (plus the
|
||||
// background jobs' small pools) comfortably under the server's `max_connections`.
|
||||
let max_connections = easyenv::get_env_num("MYSQL_MAX_CONNECTIONS", 20)?;
|
||||
let min_connections = easyenv::get_env_num("MYSQL_MIN_CONNECTIONS", 0)?;
|
||||
|
||||
// TODO(bt,2024-09-17): Configure logging, etc. after we've upgraded sqlx
|
||||
// NB: Fail fast under pool saturation rather than stacking up on sqlx's 30s default — a
|
||||
// shorter timeout sheds load and surfaces the problem instead of hiding it behind long waits.
|
||||
let acquire_timeout_seconds = easyenv::get_env_num::<u64>("MYSQL_ACQUIRE_TIMEOUT_SECONDS", 10)?;
|
||||
|
||||
// NB: Log the settings before building/connecting so a connect failure has context. We do NOT
|
||||
// log the connection string (it contains credentials).
|
||||
info!(
|
||||
"Building MySQL pool: max_connections={}, min_connections={}, acquire_timeout_seconds={}",
|
||||
max_connections, min_connections, acquire_timeout_seconds,
|
||||
);
|
||||
|
||||
let pool_options = MySqlPoolOptions::new()
|
||||
.max_connections(max_connections)
|
||||
.min_connections(min_connections)
|
||||
.acquire_timeout(Duration::from_secs(acquire_timeout_seconds));
|
||||
|
||||
let pool = pool_options
|
||||
.connect(&db_connection_string)
|
||||
|
||||
@@ -95,6 +95,19 @@ use crate::http_server::endpoints::moderation::user_bans::moderation_ban_user_ha
|
||||
use crate::http_server::endpoints::moderation::user_emails::moderator_change_user_email_handler::*;
|
||||
use crate::http_server::endpoints::moderation::user_emails::moderator_list_email_address_changes_for_user_handler::*;
|
||||
|
||||
// API key endpoints
|
||||
use crate::http_server::endpoints::api_keys::create_api_key_handler::*;
|
||||
use crate::http_server::endpoints::api_keys::list_api_keys_handler::*;
|
||||
use crate::http_server::endpoints::api_keys::get_api_key_handler::*;
|
||||
use crate::http_server::endpoints::api_keys::delete_api_key_handler::*;
|
||||
use crate::http_server::endpoints::api_keys::update_api_key_handler::*;
|
||||
use artcraft_api_defs::api_keys::common::*;
|
||||
use artcraft_api_defs::api_keys::create_api_key::*;
|
||||
use artcraft_api_defs::api_keys::list_api_keys::*;
|
||||
use artcraft_api_defs::api_keys::get_api_key::*;
|
||||
use artcraft_api_defs::api_keys::delete_api_key::*;
|
||||
use artcraft_api_defs::api_keys::update_api_key::*;
|
||||
|
||||
// Folder endpoints
|
||||
use crate::http_server::endpoints::folders::folder::color_code_folder_handler::*;
|
||||
use crate::http_server::endpoints::folders::folder::cover_image_folder_handler::*;
|
||||
@@ -561,6 +574,13 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
crate::http_server::endpoints::moderation::user_emails::moderator_list_email_address_changes_for_user_handler::moderator_list_email_address_changes_for_user_handler,
|
||||
crate::http_server::endpoints::moderation::user_stripe_data::moderator_get_user_stripe_customer_ids_handler::moderator_get_user_stripe_customer_ids_handler,
|
||||
|
||||
// API keys
|
||||
crate::http_server::endpoints::api_keys::create_api_key_handler::create_api_key_handler,
|
||||
crate::http_server::endpoints::api_keys::list_api_keys_handler::list_api_keys_handler,
|
||||
crate::http_server::endpoints::api_keys::get_api_key_handler::get_api_key_handler,
|
||||
crate::http_server::endpoints::api_keys::delete_api_key_handler::delete_api_key_handler,
|
||||
crate::http_server::endpoints::api_keys::update_api_key_handler::update_api_key_handler,
|
||||
|
||||
// Folders
|
||||
crate::http_server::endpoints::folders::folder::create_folder_handler::create_folder_handler,
|
||||
crate::http_server::endpoints::folders::folder::list_folders_handler::list_folders_handler,
|
||||
@@ -1250,6 +1270,18 @@ use crate::http_server::endpoints::media_files::list::list_batch_generated_redux
|
||||
// User Emails (Moderation)
|
||||
ModeratorListUserEmailChangesQueryParams,
|
||||
|
||||
// API keys
|
||||
ApiKeyInfo,
|
||||
ApiKeyPathInfo,
|
||||
CreateApiKeyRequest,
|
||||
CreateApiKeySuccessResponse,
|
||||
ListApiKeysQueryParams,
|
||||
ListApiKeysSuccessResponse,
|
||||
GetApiKeySuccessResponse,
|
||||
DeleteApiKeySuccessResponse,
|
||||
UpdateApiKeyRequest,
|
||||
UpdateApiKeySuccessResponse,
|
||||
|
||||
// Folders
|
||||
FolderInfo,
|
||||
FolderThumbnail,
|
||||
|
||||
+1
-27
@@ -2,7 +2,6 @@ use std::fmt::{Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::http_server::session::session_checker_error::SessionCheckerError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use actix_artcraft::sessions::anonymous_visitor_tracking::avt_cookie_payload_error::AvtCookiePayloadError;
|
||||
use actix_artcraft::sessions::user_sessions::http_user_session_payload_error::HttpUserSessionPayloadError;
|
||||
use actix_http::StatusCode;
|
||||
@@ -19,7 +18,7 @@ use serde::Serialize;
|
||||
/// ```ignore
|
||||
/// pub async fn my_handler(...) -> Result<Json<MyResponse>, CommonWebError> {
|
||||
/// // sqlx errors, anyhow errors, session errors — all convert via ?
|
||||
/// let user = require_user_session_using_connection(&req, ...)?;
|
||||
/// let user = require_user_session(&req, ...)?;
|
||||
/// let data = some_db_query(&pool).await?;
|
||||
///
|
||||
/// if data.is_none() {
|
||||
@@ -317,15 +316,6 @@ impl From<serde_json::Error> for CommonWebError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RequireUserSessionError> for CommonWebError {
|
||||
fn from(value: RequireUserSessionError) -> Self {
|
||||
match value {
|
||||
RequireUserSessionError::NotAuthorized => Self::NotAuthorized,
|
||||
RequireUserSessionError::ServerError => Self::from_error(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HttpUserSessionPayloadError> for CommonWebError {
|
||||
fn from(err: HttpUserSessionPayloadError) -> Self {
|
||||
if err.is_server_error() {
|
||||
@@ -545,22 +535,6 @@ mod tests {
|
||||
assert!(source.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn require_user_session_not_authorized_maps_to_401() {
|
||||
let error: CommonWebError = RequireUserSessionError::NotAuthorized.into();
|
||||
assert_eq!(error.status_code(), StatusCode::UNAUTHORIZED);
|
||||
assert!(!error.is_server_error());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn require_user_session_server_error_wraps_cause() {
|
||||
let error: CommonWebError = RequireUserSessionError::ServerError.into();
|
||||
assert_eq!(error.status_code(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
assert!(error.is_server_error());
|
||||
assert!(error.cause().is_some());
|
||||
assert!(format!("{}", error.cause().unwrap()).contains("ServerError"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serde_json_error_converts_to_500() {
|
||||
let bad_json = "not json at all{{{";
|
||||
|
||||
+3
-2
@@ -120,7 +120,8 @@ pub async fn create_scene_handler(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.as_ref()
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
// ==================== FILE DATA ==================== //
|
||||
|
||||
@@ -166,7 +167,7 @@ pub async fn create_scene_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Dimensional),
|
||||
media_file_type: MediaFileType::SceneRon,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Public,
|
||||
|
||||
+3
-2
@@ -127,7 +127,8 @@ pub async fn process_upload_media_file(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.as_ref()
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_file_size_bytes = upload_media_request.file_bytes
|
||||
.as_ref()
|
||||
@@ -325,7 +326,7 @@ pub async fn process_upload_media_file(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: None,
|
||||
media_file_type,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+5
-4
@@ -129,7 +129,8 @@ pub async fn handle_upload(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.as_ref()
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_file_size_bytes = upload_media_request.file_bytes
|
||||
.as_ref()
|
||||
@@ -300,7 +301,7 @@ pub async fn handle_upload(
|
||||
checksum_sha2: &hash,
|
||||
public_upload_path: &public_upload_path,
|
||||
maybe_extra_file_modification_info: None,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: None,
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
@@ -317,12 +318,12 @@ pub async fn handle_upload(
|
||||
let firehose_result = match media_upload_source {
|
||||
MediaUploadSource::DeviceApi => {
|
||||
server_state.firehose_publisher.publish_device_media_recorded(
|
||||
maybe_user_token.as_ref(),
|
||||
maybe_user_token,
|
||||
&token).await
|
||||
}
|
||||
_ => {
|
||||
server_state.firehose_publisher.publish_media_uploaded(
|
||||
maybe_user_token.as_ref(),
|
||||
maybe_user_token,
|
||||
&token).await
|
||||
}
|
||||
};
|
||||
|
||||
+3
-9
@@ -28,8 +28,7 @@ use crate::http_server::requests::request_headers::get_routing_tag_header::get_r
|
||||
use crate::http_server::requests::request_headers::has_debug_header::has_debug_header;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::cleaners::empty_media_file_token_to_null::empty_media_file_token_to_null;
|
||||
|
||||
@@ -114,15 +113,10 @@ pub async fn enqueue_face_fusion_workflow_handler(
|
||||
let maybe_avt_token = server_state.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
let user_session = require_user_session_extended(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireUserSessionError::ServerError => CommonWebError::from_error(err),
|
||||
RequireUserSessionError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
&mut *mysql_connection).await?;
|
||||
|
||||
// ==================== PAID PLAN + PRIORITY ==================== //
|
||||
|
||||
|
||||
+3
-9
@@ -29,8 +29,7 @@ use crate::http_server::requests::request_headers::get_routing_tag_header::get_r
|
||||
use crate::http_server::requests::request_headers::has_debug_header::has_debug_header;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::cleaners::empty_media_file_token_to_null::empty_media_file_token_to_null;
|
||||
|
||||
@@ -122,15 +121,10 @@ pub async fn enqueue_live_portrait_workflow_handler(
|
||||
let maybe_avt_token = server_state.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
let user_session = require_user_session_extended(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireUserSessionError::ServerError => CommonWebError::from_error(err),
|
||||
RequireUserSessionError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
&mut *mysql_connection).await?;
|
||||
|
||||
// ==================== PAID PLAN + PRIORITY ==================== //
|
||||
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
use artcraft_api_defs::api_keys::common::ApiKeyInfo;
|
||||
use mysql_queries::queries::api_keys::api_key_row::ApiKeyRow;
|
||||
|
||||
/// Convert a data-access `ApiKeyRow` into the public `ApiKeyInfo` wire shape.
|
||||
/// The row only ever carries the truncated key prefix (never the full secret),
|
||||
/// so this is a straight field map.
|
||||
pub fn api_key_row_to_info(row: ApiKeyRow) -> ApiKeyInfo {
|
||||
ApiKeyInfo {
|
||||
token: row.token,
|
||||
truncated_api_key: row.api_key_prefix,
|
||||
name: row.name,
|
||||
maybe_description: row.maybe_description,
|
||||
owner_user_token: row.owner_user_token,
|
||||
ip_address_creation: row.ip_address_creation,
|
||||
ip_address_update: row.ip_address_update,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
maybe_deleted_at: row.maybe_deleted_at,
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web::Json;
|
||||
use actix_web::{web, HttpRequest};
|
||||
use log::warn;
|
||||
|
||||
use artcraft_api_defs::api_keys::create_api_key::{CreateApiKeyRequest, CreateApiKeySuccessResponse};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::api_keys::insert_api_key::{insert_api_key, InsertApiKeyArgs};
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
use crate::util::generate_api_key::generate_api_key;
|
||||
|
||||
const MAX_NAME_LEN: usize = 255;
|
||||
const MAX_DESCRIPTION_LEN: usize = 512;
|
||||
|
||||
/// Create a new API key for the logged-in user. The full secret `api_key`
|
||||
/// value is returned exactly once in this response — it can never be retrieved
|
||||
/// again afterward.
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "API Keys",
|
||||
path = "/v1/api_keys/create",
|
||||
request_body = CreateApiKeyRequest,
|
||||
responses(
|
||||
(status = 200, body = CreateApiKeySuccessResponse),
|
||||
(status = 400, body = CommonWebError),
|
||||
(status = 401, body = CommonWebError),
|
||||
(status = 500, body = CommonWebError),
|
||||
),
|
||||
)]
|
||||
pub async fn create_api_key_handler(
|
||||
http_request: HttpRequest,
|
||||
request: Json<CreateApiKeyRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<CreateApiKeySuccessResponse>, CommonWebError> {
|
||||
let mut conn = server_state.mysql_pool.acquire().await.map_err(|err| {
|
||||
warn!("MySQL pool error: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let name = request.name.trim().to_string();
|
||||
|
||||
let maybe_description = request.maybe_description.as_ref().map(|s| s.trim().to_string());
|
||||
|
||||
if name.is_empty() {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage("name is empty".to_string()));
|
||||
}
|
||||
if name.len() > MAX_NAME_LEN {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
format!("name too long (max {} chars)", MAX_NAME_LEN),
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(description) = &maybe_description {
|
||||
if description.len() > MAX_DESCRIPTION_LEN {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
format!("description too long (max {} chars)", MAX_DESCRIPTION_LEN),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
// Generate the secret once. It is returned to the caller here and never again.
|
||||
let api_key = generate_api_key();
|
||||
|
||||
let token = insert_api_key(InsertApiKeyArgs {
|
||||
owner_user_token: &user_session.user_token,
|
||||
ip_address: &ip_address,
|
||||
name: &name,
|
||||
maybe_description: maybe_description.as_deref(),
|
||||
api_key: &api_key,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
}).await.map_err(|err| {
|
||||
warn!("insert_api_key failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
Ok(Json(CreateApiKeySuccessResponse {
|
||||
success: true,
|
||||
api_key_token: token,
|
||||
api_key,
|
||||
}))
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web::{Json, Path};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use log::warn;
|
||||
|
||||
use artcraft_api_defs::api_keys::common::ApiKeyPathInfo;
|
||||
use artcraft_api_defs::api_keys::delete_api_key::DeleteApiKeySuccessResponse;
|
||||
use mysql_queries::queries::api_keys::delete_api_key::{delete_api_key, DeleteApiKeyArgs};
|
||||
use mysql_queries::queries::api_keys::get_api_key_by_token::{
|
||||
get_api_key_by_token, GetApiKeyByTokenArgs,
|
||||
};
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Soft-delete an API key by `token`, scoped to the logged-in user.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
tag = "API Keys",
|
||||
path = "/v1/api_keys/{api_key_token}",
|
||||
params(("api_key_token" = ApiKeyToken, description = "The API key's token (not the secret value)")),
|
||||
responses(
|
||||
(status = 200, body = DeleteApiKeySuccessResponse),
|
||||
(status = 401, body = CommonWebError),
|
||||
(status = 404, body = CommonWebError),
|
||||
(status = 500, body = CommonWebError),
|
||||
),
|
||||
)]
|
||||
pub async fn delete_api_key_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<ApiKeyPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<DeleteApiKeySuccessResponse>, CommonWebError> {
|
||||
let mut conn = server_state.mysql_pool.acquire().await.map_err(|err| {
|
||||
warn!("MySQL pool error: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
// The by-token delete query isn't owner-scoped, so first look the key up and
|
||||
// confirm ownership. Return NotFound for missing OR other-owned keys, so we
|
||||
// never leak the existence of another user's key.
|
||||
let row = get_api_key_by_token(GetApiKeyByTokenArgs {
|
||||
token: &path.api_key_token,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
}).await.map_err(|err| {
|
||||
warn!("get_api_key_by_token failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?
|
||||
.filter(|row| row.owner_user_token == user_session.user_token)
|
||||
.ok_or(CommonWebError::NotFound)?;
|
||||
|
||||
// Only issue the delete if the key is still live. If it's already
|
||||
// soft-deleted the desired end state already holds, so treat it as success.
|
||||
if row.maybe_deleted_at.is_none() {
|
||||
delete_api_key(DeleteApiKeyArgs {
|
||||
token: &path.api_key_token,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
}).await.map_err(|err| {
|
||||
warn!("delete_api_key failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(Json(DeleteApiKeySuccessResponse { success: true }))
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web::{Json, Path};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use log::warn;
|
||||
|
||||
use artcraft_api_defs::api_keys::common::ApiKeyPathInfo;
|
||||
use artcraft_api_defs::api_keys::get_api_key::GetApiKeySuccessResponse;
|
||||
use mysql_queries::queries::api_keys::get_api_key_by_token::{
|
||||
get_api_key_by_token, GetApiKeyByTokenArgs,
|
||||
};
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::api_keys::api_key_info_conversion::api_key_row_to_info;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Fetch a single API key by its `token`, scoped to the logged-in user.
|
||||
/// `NotFound` if the key doesn't exist or belongs to a different user (so we
|
||||
/// don't leak the existence of others' keys). Never returns the full secret.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
tag = "API Keys",
|
||||
path = "/v1/api_keys/{api_key_token}",
|
||||
params(("api_key_token" = ApiKeyToken, description = "The API key's token (not the secret value)")),
|
||||
responses(
|
||||
(status = 200, body = GetApiKeySuccessResponse),
|
||||
(status = 401, body = CommonWebError),
|
||||
(status = 404, body = CommonWebError),
|
||||
(status = 500, body = CommonWebError),
|
||||
),
|
||||
)]
|
||||
pub async fn get_api_key_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<ApiKeyPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<GetApiKeySuccessResponse>, CommonWebError> {
|
||||
let mut conn = server_state.mysql_pool.acquire().await.map_err(|err| {
|
||||
warn!("MySQL pool error: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let row = get_api_key_by_token(GetApiKeyByTokenArgs {
|
||||
token: &path.api_key_token,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
}).await.map_err(|err| {
|
||||
warn!("get_api_key_by_token failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?
|
||||
.filter(|row| row.owner_user_token == user_session.user_token)
|
||||
.ok_or(CommonWebError::NotFound)?;
|
||||
|
||||
Ok(Json(GetApiKeySuccessResponse {
|
||||
success: true,
|
||||
api_key: api_key_row_to_info(row),
|
||||
}))
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web::{Json, Query};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use log::warn;
|
||||
|
||||
use artcraft_api_defs::api_keys::list_api_keys::{ListApiKeysQueryParams, ListApiKeysSuccessResponse};
|
||||
use mysql_queries::queries::api_keys::list_api_keys_for_user::{
|
||||
list_api_keys_for_user, ListApiKeysForUserArgs,
|
||||
};
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::api_keys::api_key_info_conversion::api_key_row_to_info;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const DEFAULT_LIMIT: u32 = 100;
|
||||
const MAX_LIMIT: u32 = 1000;
|
||||
|
||||
/// List the logged-in user's API keys, newest first, **including
|
||||
/// soft-deleted** keys. Paginated via `limit`/`offset`. Never returns the full
|
||||
/// secret — each entry carries only the truncated key prefix.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
tag = "API Keys",
|
||||
path = "/v1/api_keys/list",
|
||||
params(ListApiKeysQueryParams),
|
||||
responses(
|
||||
(status = 200, body = ListApiKeysSuccessResponse),
|
||||
(status = 401, body = CommonWebError),
|
||||
(status = 500, body = CommonWebError),
|
||||
),
|
||||
)]
|
||||
pub async fn list_api_keys_handler(
|
||||
http_request: HttpRequest,
|
||||
query: Query<ListApiKeysQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<ListApiKeysSuccessResponse>, CommonWebError> {
|
||||
let mut conn = server_state.mysql_pool.acquire().await.map_err(|err| {
|
||||
warn!("MySQL pool error: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let limit = query.limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT);
|
||||
let offset = query.offset.unwrap_or(0);
|
||||
|
||||
let rows = list_api_keys_for_user(ListApiKeysForUserArgs {
|
||||
owner_user_token: &user_session.user_token,
|
||||
limit,
|
||||
offset,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
}).await.map_err(|err| {
|
||||
warn!("list_api_keys_for_user failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let api_keys = rows.into_iter().map(api_key_row_to_info).collect();
|
||||
|
||||
Ok(Json(ListApiKeysSuccessResponse {
|
||||
success: true,
|
||||
api_keys,
|
||||
}))
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod api_key_info_conversion;
|
||||
pub mod create_api_key_handler;
|
||||
pub mod delete_api_key_handler;
|
||||
pub mod get_api_key_handler;
|
||||
pub mod list_api_keys_handler;
|
||||
pub mod update_api_key_handler;
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web::{Json, Path};
|
||||
use actix_web::{web, HttpRequest};
|
||||
use log::warn;
|
||||
|
||||
use artcraft_api_defs::api_keys::common::ApiKeyPathInfo;
|
||||
use artcraft_api_defs::api_keys::update_api_key::{UpdateApiKeyRequest, UpdateApiKeySuccessResponse};
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::api_keys::get_api_key_by_token::{
|
||||
get_api_key_by_token, GetApiKeyByTokenArgs,
|
||||
};
|
||||
use mysql_queries::queries::api_keys::update_api_key::{update_api_key, UpdateApiKeyArgs};
|
||||
use tokens::tokens::api_keys::ApiKeyToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_DESCRIPTION_LEN: usize = 512;
|
||||
|
||||
/// Update an API key's description (also refreshing its update IP), scoped to
|
||||
/// the logged-in user. `NotFound` if the key doesn't exist, belongs to another
|
||||
/// user, or has been deleted.
|
||||
#[utoipa::path(
|
||||
put,
|
||||
tag = "API Keys",
|
||||
path = "/v1/api_keys/{api_key_token}",
|
||||
params(("api_key_token" = ApiKeyToken, description = "The API key's token (not the secret value)")),
|
||||
request_body = UpdateApiKeyRequest,
|
||||
responses(
|
||||
(status = 200, body = UpdateApiKeySuccessResponse),
|
||||
(status = 400, body = CommonWebError),
|
||||
(status = 401, body = CommonWebError),
|
||||
(status = 404, body = CommonWebError),
|
||||
(status = 500, body = CommonWebError),
|
||||
),
|
||||
)]
|
||||
pub async fn update_api_key_handler(
|
||||
http_request: HttpRequest,
|
||||
path: Path<ApiKeyPathInfo>,
|
||||
request: Json<UpdateApiKeyRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<UpdateApiKeySuccessResponse>, CommonWebError> {
|
||||
let mut conn = server_state.mysql_pool.acquire().await.map_err(|err| {
|
||||
warn!("MySQL pool error: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
if let Some(description) = &request.maybe_description {
|
||||
if description.len() > MAX_DESCRIPTION_LEN {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
format!("description too long (max {} chars)", MAX_DESCRIPTION_LEN),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Confirm the key exists and is owned by this user before updating (the
|
||||
// update query isn't owner-scoped). NotFound for missing/other-owned keys.
|
||||
let existing = get_api_key_by_token(GetApiKeyByTokenArgs {
|
||||
token: &path.api_key_token,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
}).await.map_err(|err| {
|
||||
warn!("get_api_key_by_token failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?
|
||||
.filter(|row| row.owner_user_token == user_session.user_token)
|
||||
.ok_or(CommonWebError::NotFound)?;
|
||||
|
||||
if existing.maybe_deleted_at.is_some() {
|
||||
return Err(CommonWebError::NotFound);
|
||||
}
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
update_api_key(UpdateApiKeyArgs {
|
||||
token: &path.api_key_token,
|
||||
maybe_description: request.maybe_description.as_deref(),
|
||||
ip_address: &ip_address,
|
||||
mysql_executor: &mut *conn,
|
||||
phantom: PhantomData,
|
||||
}).await.map_err(|err| {
|
||||
warn!("update_api_key failed: {:?}", err);
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
Ok(Json(UpdateApiKeySuccessResponse { success: true }))
|
||||
}
|
||||
+2
-7
@@ -14,7 +14,7 @@ use mysql_queries::queries::users::user_profiles::get_user_profile_by_username::
|
||||
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, RequireModeratorError, UseDatabase};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAXIMUM_KEYS : u32 = 100;
|
||||
@@ -60,12 +60,7 @@ pub async fn create_beta_keys_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<HttpResponse, CommonWebError>
|
||||
{
|
||||
let user_session = require_moderator(&http_request, &server_state, UseDatabase::GrabNewConnection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireModeratorError::ServerError => CommonWebError::from_error(err),
|
||||
RequireModeratorError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let mut maybe_referrer_user_token = None;
|
||||
|
||||
|
||||
+2
-7
@@ -14,7 +14,7 @@ use tokens::tokens::w2l_results::W2lResultToken;
|
||||
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, RequireModeratorError, UseDatabase};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// For the URL PathInfo
|
||||
@@ -58,12 +58,7 @@ pub async fn edit_beta_key_distributed_flag_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<HttpResponse, CommonWebError>
|
||||
{
|
||||
let user_session = require_moderator(&http_request, &server_state, UseDatabase::GrabNewConnection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireModeratorError::ServerError => CommonWebError::from_error(err),
|
||||
RequireModeratorError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
edit_beta_key_distributed_flag(&path.token, request.is_distributed, &server_state.mysql_pool)
|
||||
.await
|
||||
|
||||
+2
-7
@@ -13,7 +13,7 @@ use tokens::tokens::beta_keys::BetaKeyToken;
|
||||
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, RequireModeratorError, UseDatabase};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// For the URL PathInfo
|
||||
@@ -57,12 +57,7 @@ pub async fn edit_beta_key_note_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<HttpResponse, CommonWebError>
|
||||
{
|
||||
let user_session = require_moderator(&http_request, &server_state, UseDatabase::GrabNewConnection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireModeratorError::ServerError => CommonWebError::from_error(err),
|
||||
RequireModeratorError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let maybe_note = request.note.as_deref()
|
||||
.map(|note| note.trim())
|
||||
|
||||
+3
-7
@@ -17,7 +17,7 @@ use tokens::tokens::beta_keys::BetaKeyToken;
|
||||
use crate::http_server::common_responses::pagination_page::PaginationPage;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::common_responses::user_details_lite::UserDetailsLight;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::{require_user_session, RequireUserSessionError};
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
#[derive(Copy, Clone, Deserialize, ToSchema)]
|
||||
@@ -108,12 +108,8 @@ pub async fn list_beta_keys_handler(
|
||||
query: Query<ListBetaKeysQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, CommonWebError> {
|
||||
let user_session = require_user_session(&http_request, &server_state)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireUserSessionError::ServerError => CommonWebError::from_error(err),
|
||||
RequireUserSessionError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool)
|
||||
.await?;
|
||||
|
||||
let mut is_mod = user_session.can_ban_users;
|
||||
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@ pub async fn redeem_beta_key_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<HttpResponse, CommonWebError>
|
||||
{
|
||||
let user_session = require_user_session(&http_request, &server_state).await?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let rate_limiter = &server_state.redis_rate_limiters.logged_out;
|
||||
|
||||
|
||||
+2
-6
@@ -10,7 +10,7 @@ use mysql_queries::queries::characters::delete_character::delete_character;
|
||||
use mysql_queries::queries::characters::get_character_by_token_including_deleted::get_character_by_token_including_deleted;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Delete a character.
|
||||
@@ -40,11 +40,7 @@ pub async fn delete_character_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *mysql_connection).await?;
|
||||
|
||||
let user_token = &user_session.user_token;
|
||||
let is_mod = user_session.is_mod();
|
||||
|
||||
+2
-6
@@ -11,7 +11,7 @@ use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use seedance2pro_client::requests::update_character::update_character::{update_character, UpdateCharacterArgs};
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Edit a character's name or description.
|
||||
@@ -38,11 +38,7 @@ pub async fn edit_character_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *mysql_connection).await?;
|
||||
|
||||
let user_token = &user_session.user_token;
|
||||
let is_mod = user_session.is_mod();
|
||||
|
||||
+2
-6
@@ -16,7 +16,7 @@ use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// List characters for the current session.
|
||||
@@ -39,11 +39,7 @@ pub async fn list_characters_handler(
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection,
|
||||
).await?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *mysql_connection).await?;
|
||||
|
||||
let user_token = &user_session.user_token;
|
||||
|
||||
|
||||
+2
-4
@@ -17,7 +17,7 @@ use mysql_queries::queries::folders::folder::update_folder_color_code::{
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_COLOR_CODE_LEN: usize = 16;
|
||||
@@ -47,9 +47,7 @@ pub async fn color_code_folder_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
// Normalize: trim, treat empty after trim as `None`. This lets clients
|
||||
// clear by passing `""` or by omitting the field entirely.
|
||||
|
||||
+2
-4
@@ -23,7 +23,7 @@ use tokens::tokens::folders::FolderToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Set or clear the custom cover image on a folder.
|
||||
@@ -64,9 +64,7 @@ pub async fn cover_image_folder_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
// Confirm the folder exists + is owned. Authoritative 404 source.
|
||||
get_folder_for_owner(GetFolderForOwnerArgs {
|
||||
|
||||
+2
-4
@@ -17,7 +17,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{
|
||||
build_folder_thumbnails_lookup, folder_row_to_info,
|
||||
};
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_NAME_LEN: usize = 255;
|
||||
@@ -45,9 +45,7 @@ pub async fn create_folder_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let name = request.name.trim().to_string();
|
||||
|
||||
|
||||
+2
-4
@@ -12,7 +12,7 @@ use mysql_queries::queries::folders::folder::soft_delete_folder::{
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Soft-delete a folder. Children retain their parent pointer and become
|
||||
@@ -39,9 +39,7 @@ pub async fn delete_folder_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
|
||||
let rows_affected = soft_delete_folder(SoftDeleteFolderArgs {
|
||||
|
||||
+2
-4
@@ -16,7 +16,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{
|
||||
build_folder_thumbnails_lookup, folder_row_to_info,
|
||||
};
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Fetch a single folder owned by the logged-in user.
|
||||
@@ -42,9 +42,7 @@ pub async fn get_folder_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let row = get_folder_for_owner(GetFolderForOwnerArgs {
|
||||
folder_token: &path.folder_token,
|
||||
|
||||
+2
-4
@@ -17,7 +17,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{
|
||||
build_folder_thumbnails_lookup, folder_row_to_info,
|
||||
};
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const CURSOR_NAME: &str = "folders_all";
|
||||
@@ -48,9 +48,7 @@ pub async fn list_folders_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let limit = query.limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT);
|
||||
|
||||
|
||||
+2
-4
@@ -17,7 +17,7 @@ use mysql_queries::queries::folders::folder::update_folder_name::{
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_NAME_LEN: usize = 255;
|
||||
@@ -47,9 +47,7 @@ pub async fn rename_folder_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let new_name = request.new_name.trim().to_string();
|
||||
|
||||
|
||||
+2
-4
@@ -17,7 +17,7 @@ use mysql_queries::queries::folders::folder::update_folder_has_star::{
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
#[utoipa::path(
|
||||
@@ -44,9 +44,7 @@ pub async fn star_folder_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
// Confirm the folder exists + is owned before issuing the update.
|
||||
// A 404 here is the authoritative "not found" signal — the update
|
||||
|
||||
+2
-4
@@ -27,7 +27,7 @@ use mysql_queries::queries::folders::media_files::recompute_folder_last_media_fi
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_BULK: usize = 500;
|
||||
@@ -61,9 +61,7 @@ pub async fn bulk_add_folder_media_files_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
if request.media_file_tokens.len() > MAX_BULK {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
|
||||
+2
-4
@@ -30,7 +30,7 @@ use mysql_queries::queries::folders::media_files::recompute_folder_last_media_fi
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_BULK: usize = 500;
|
||||
@@ -72,9 +72,7 @@ pub async fn bulk_move_folder_media_files_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
if request.media_file_tokens.len() > MAX_BULK {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
|
||||
+2
-4
@@ -24,7 +24,7 @@ use tokens::tokens::folders::FolderToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_BULK: usize = 500;
|
||||
@@ -56,9 +56,7 @@ pub async fn bulk_remove_folder_media_files_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
if request.media_file_tokens.len() > MAX_BULK {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
|
||||
+2
-4
@@ -32,7 +32,7 @@ use crate::http_server::common_responses::media::media_domain::MediaDomain;
|
||||
use crate::http_server::common_responses::media::media_file_cover_image_details::MediaFileCoverImageDetails;
|
||||
use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder;
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const CURSOR_NAME: &str = "folder_mf";
|
||||
@@ -127,9 +127,7 @@ pub async fn list_folder_media_files_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
let folder = get_folder_for_owner(GetFolderForOwnerArgs {
|
||||
folder_token: &path.folder_token,
|
||||
|
||||
+2
-4
@@ -25,7 +25,7 @@ use mysql_queries::queries::folders::subfolder::filter_existing_owned_folder_tok
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_BULK: usize = 500;
|
||||
@@ -58,9 +58,7 @@ pub async fn bulk_add_subfolders_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
if request.subfolder_tokens.len() > MAX_BULK {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
|
||||
+2
-4
@@ -14,7 +14,7 @@ use mysql_queries::queries::folders::subfolder::bulk_clear_parent_folder::{
|
||||
use tokens::tokens::folders::FolderToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const MAX_BULK: usize = 500;
|
||||
@@ -45,9 +45,7 @@ pub async fn bulk_remove_subfolders_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
if request.subfolder_tokens.len() > MAX_BULK {
|
||||
return Err(CommonWebError::BadInputWithSimpleMessage(
|
||||
|
||||
+2
-4
@@ -21,7 +21,7 @@ use crate::http_server::endpoints::folders::folder::folder_info_conversion::{
|
||||
build_folder_thumbnails_lookup, folder_row_to_info,
|
||||
};
|
||||
use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const CURSOR_NAME: &str = "subfolders";
|
||||
@@ -55,9 +55,7 @@ pub async fn list_subfolders_handler(
|
||||
CommonWebError::from_error(err)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut conn,
|
||||
).await.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *conn).await?;
|
||||
|
||||
|
||||
// Confirm the parent exists + is owned by the caller before listing.
|
||||
|
||||
+2
-2
@@ -119,7 +119,7 @@ pub async fn upload_snapshot_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -247,7 +247,7 @@ pub async fn upload_snapshot_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Image),
|
||||
media_file_type,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+2
-3
@@ -8,7 +8,7 @@ use utoipa::ToSchema;
|
||||
use mysql_queries::queries::generic_inference::web::dismiss_finished_jobs_for_user::dismiss_finished_jobs_for_user;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_using_connection::require_user_session_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::require_user_session;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
@@ -40,8 +40,7 @@ pub async fn dismiss_finished_session_jobs_handler(
|
||||
CommonWebError::from_error(e)
|
||||
})?;
|
||||
|
||||
let user_session = require_user_session_using_connection(
|
||||
&http_request, &server_state.session_checker, &mut mysql_connection)
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &mut *mysql_connection)
|
||||
.await?;
|
||||
|
||||
dismiss_finished_jobs_for_user(&mut mysql_connection, &user_session.user_token)
|
||||
|
||||
+2
-6
@@ -56,12 +56,8 @@ pub async fn change_media_file_animation_type_handler(
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<SimpleResponse>, CommonWebError> {
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Not authorized: {:?}", e);
|
||||
CommonWebError::NotAuthorized
|
||||
})?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool)
|
||||
.await?;
|
||||
|
||||
let media_file_token = path.token.clone();
|
||||
let is_mod = user_session.is_mod();
|
||||
|
||||
+2
-6
@@ -53,12 +53,8 @@ pub async fn change_media_file_engine_category_handler(
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<SimpleResponse>, CommonWebError> {
|
||||
|
||||
let user_session = require_user_session(&http_request, &server_state)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Not authorized: {:?}", e);
|
||||
CommonWebError::NotAuthorized
|
||||
})?;
|
||||
let user_session = require_user_session(&http_request, &server_state.session_checker, &server_state.mysql_pool)
|
||||
.await?;
|
||||
|
||||
let media_file_token = path.token.clone();
|
||||
let is_mod = user_session.is_mod();
|
||||
|
||||
+2
-2
@@ -135,7 +135,7 @@ pub async fn upload_audio_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -334,7 +334,7 @@ pub async fn upload_audio_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Audio),
|
||||
media_file_type: MediaFileType::Audio,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+3
-2
@@ -140,7 +140,8 @@ pub async fn upload_engine_asset_media_file_handler(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.as_ref()
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
// ==================== FILE DATA ==================== //
|
||||
|
||||
@@ -205,7 +206,7 @@ pub async fn upload_engine_asset_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Dimensional),
|
||||
media_file_type,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+4
-3
@@ -142,7 +142,8 @@ pub async fn process_upload_media_file(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.as_ref()
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_file_size_bytes = upload_media_request.file_bytes
|
||||
.as_ref()
|
||||
@@ -392,7 +393,7 @@ pub async fn process_upload_media_file(
|
||||
})?;
|
||||
|
||||
let media_token = MediaFileInsertBuilder::new()
|
||||
.maybe_creator_user(maybe_user_token.as_ref())
|
||||
.maybe_creator_user(maybe_user_token)
|
||||
.maybe_creator_anonymous_visitor(maybe_avt_token.as_ref())
|
||||
.creator_ip_address(&ip_address)
|
||||
.public_bucket_directory_hash(&public_upload_path)
|
||||
@@ -417,7 +418,7 @@ pub async fn process_upload_media_file(
|
||||
//let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
// maybe_media_class: Some(media_file_class), // DONE
|
||||
// media_file_type, // DONE
|
||||
// maybe_creator_user_token: maybe_user_token.as_ref(), // DONE
|
||||
// maybe_creator_user_token: maybe_user_token, // DONE
|
||||
// maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(), // DONE
|
||||
// creator_ip_address: &ip_address, // DONE
|
||||
// creator_set_visibility, // DONE
|
||||
|
||||
+2
-2
@@ -145,7 +145,7 @@ pub async fn upload_image_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -310,7 +310,7 @@ pub async fn upload_image_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Image),
|
||||
media_file_type,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+2
-2
@@ -179,7 +179,7 @@ pub async fn upload_new_engine_asset_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
// ==================== FILE DATA ==================== //
|
||||
|
||||
@@ -211,7 +211,7 @@ pub async fn upload_new_engine_asset_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(file_info.media_class),
|
||||
media_file_type: file_info.media_type,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: file_info.creator_set_visibility,
|
||||
|
||||
+2
-2
@@ -112,7 +112,7 @@ pub async fn upload_new_scene_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -226,7 +226,7 @@ pub async fn upload_new_scene_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Dimensional),
|
||||
media_file_type: MediaFileType::SceneJson,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+7
-6
@@ -23,7 +23,8 @@ use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError;
|
||||
use crate::http_server::endpoints::media_files::upload::upload_pmx::extract_and_upload_pmx_files::{extract_and_upload_pmx_files, PmxError};
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, RequireModeratorError, UseDatabase};
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Form-multipart request fields.
|
||||
@@ -115,11 +116,11 @@ pub async fn upload_pmx_media_file_handler(
|
||||
// ==================== READ SESSION ==================== //
|
||||
|
||||
// NB: We require a moderator to upload PMX files.
|
||||
let user_session = require_moderator(&http_request, &server_state, UseDatabase::FromPool(&mut mysql_connection))
|
||||
let user_session = require_moderator(&http_request, &server_state.session_checker, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireModeratorError::ServerError => MediaFileUploadError::ServerError,
|
||||
RequireModeratorError::NotAuthorized => MediaFileUploadError::NotAuthorized,
|
||||
CommonWebError::NotAuthorized => MediaFileUploadError::NotAuthorized,
|
||||
_ => MediaFileUploadError::ServerError,
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
@@ -206,7 +207,7 @@ pub async fn upload_pmx_media_file_handler(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
//let maybe_user_token = maybe_user_session
|
||||
// .map(|session| session.get_strongly_typed_user_token());
|
||||
// .map(|session| session.get_user_token());
|
||||
|
||||
// ==================== FILE DATA ==================== //
|
||||
|
||||
@@ -257,7 +258,7 @@ pub async fn upload_pmx_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Dimensional),
|
||||
media_file_type: MediaFileType::Pmx,
|
||||
maybe_creator_user_token: Some(&user_session.get_strongly_typed_user_token()),
|
||||
maybe_creator_user_token: Some(user_session.get_user_token()),
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+2
-2
@@ -109,7 +109,7 @@ pub async fn upload_saved_scene_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -148,7 +148,7 @@ pub async fn upload_saved_scene_media_file_handler(
|
||||
|
||||
let creator_check = check_creator_tokens(CheckCreatorTokenArgs {
|
||||
maybe_creator_user_token: media_file.maybe_creator_user_token.as_ref(),
|
||||
maybe_current_request_user_token: maybe_user_token.as_ref(),
|
||||
maybe_current_request_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: media_file.maybe_creator_anonymous_visitor_token.as_ref(),
|
||||
maybe_current_request_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
});
|
||||
|
||||
+2
-2
@@ -111,7 +111,7 @@ pub async fn upload_scene_snapshot_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -219,7 +219,7 @@ pub async fn upload_scene_snapshot_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Dimensional),
|
||||
media_file_type: MediaFileType::SceneJson,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility: Visibility::Hidden,
|
||||
|
||||
+2
-2
@@ -119,7 +119,7 @@ pub async fn upload_spz_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -255,7 +255,7 @@ pub async fn upload_spz_media_file_handler(
|
||||
let media_token = MediaFileInsertBuilder::new()
|
||||
.media_file_class(MediaFileClass::Dimensional)
|
||||
.media_file_type(MediaFileType::Spz)
|
||||
.maybe_creator_user(maybe_user_token.as_ref())
|
||||
.maybe_creator_user(maybe_user_token)
|
||||
.maybe_creator_anonymous_visitor(maybe_avt_token.as_ref())
|
||||
.creator_ip_address(&ip_address)
|
||||
.creator_set_visibility(creator_set_visibility)
|
||||
|
||||
+8
-5
@@ -19,13 +19,13 @@ use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
|
||||
use mysql_queries::queries::media_files::create::insert_media_file_from_studio_scene_render::{insert_media_file_from_studio_scene_render, InsertStudioSceneRenderArgs};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::endpoints::media_files::upload::upload_error::MediaFileUploadError;
|
||||
use crate::http_server::endpoints::media_files::upload::upload_studio_shot::extract_frames_from_zip::{extract_frames_from_zip, ExtractFramesError};
|
||||
use crate::http_server::endpoints::media_files::upload::upload_studio_shot::ffmpeg_frames_to_mp4::ffmpeg_frames_to_mp4;
|
||||
use crate::http_server::requests::request_headers::has_debug_header::has_debug_header;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const DEFAULT_FPS : u8 = 12;
|
||||
@@ -111,11 +111,14 @@ pub async fn upload_studio_shot_media_file_handler(
|
||||
// ==================== READ SESSION ==================== //
|
||||
|
||||
// NB: We require a moderator to upload PMX files.
|
||||
let user_session = require_user_session_extended_using_connection(&http_request, &server_state.session_checker, &mut mysql_connection)
|
||||
let user_session = require_user_session_extended(&http_request, &server_state.session_checker, &mut *mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("User session error: {:?}", e);
|
||||
MediaFileUploadError::NotAuthorized
|
||||
match e {
|
||||
CommonWebError::NotAuthorized => MediaFileUploadError::NotAuthorized,
|
||||
_ => MediaFileUploadError::ServerError,
|
||||
}
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
@@ -176,7 +179,7 @@ pub async fn upload_studio_shot_media_file_handler(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
//let maybe_user_token = maybe_user_session
|
||||
// .map(|session| session.get_strongly_typed_user_token());
|
||||
// .map(|session| session.get_user_token());
|
||||
|
||||
// ==================== FILE DATA ==================== //
|
||||
|
||||
|
||||
+2
-2
@@ -177,7 +177,7 @@ pub async fn upload_new_video_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -507,7 +507,7 @@ pub async fn upload_new_video_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Video),
|
||||
media_file_type: MediaFileType::Video,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+3
-2
@@ -158,7 +158,8 @@ pub async fn upload_video_media_file_handler(
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.as_ref()
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
// ==================== FILE DATA ==================== //
|
||||
|
||||
@@ -221,7 +222,7 @@ pub async fn upload_video_media_file_handler(
|
||||
let (token, record_id) = insert_media_file_from_file_upload(InsertMediaFileFromUploadArgs {
|
||||
maybe_media_class: Some(MediaFileClass::Video),
|
||||
media_file_type: MediaFileType::Video,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+3
-3
@@ -73,7 +73,7 @@ pub async fn write_engine_asset_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -117,7 +117,7 @@ pub async fn write_engine_asset_media_file_handler(
|
||||
|
||||
let creator_check = check_creator_tokens(CheckCreatorTokenArgs {
|
||||
maybe_creator_user_token: media_file.maybe_creator_user_token.as_ref(),
|
||||
maybe_current_request_user_token: maybe_user_token.as_ref(),
|
||||
maybe_current_request_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: media_file.maybe_creator_anonymous_visitor_token.as_ref(),
|
||||
maybe_current_request_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
});
|
||||
@@ -232,7 +232,7 @@ pub async fn write_engine_asset_media_file_handler(
|
||||
maybe_engine_category: upload_media_request.maybe_engine_category,
|
||||
maybe_animation_type: upload_media_request.maybe_animation_type,
|
||||
maybe_media_subtype: None,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
+3
-3
@@ -75,7 +75,7 @@ pub async fn write_scene_file_media_file_handler(
|
||||
|
||||
let maybe_user_token = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| session.get_strongly_typed_user_token());
|
||||
.map(|session| session.get_user_token());
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
@@ -119,7 +119,7 @@ pub async fn write_scene_file_media_file_handler(
|
||||
|
||||
let creator_check = check_creator_tokens(CheckCreatorTokenArgs {
|
||||
maybe_creator_user_token: media_file.maybe_creator_user_token.as_ref(),
|
||||
maybe_current_request_user_token: maybe_user_token.as_ref(),
|
||||
maybe_current_request_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: media_file.maybe_creator_anonymous_visitor_token.as_ref(),
|
||||
maybe_current_request_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
});
|
||||
@@ -220,7 +220,7 @@ pub async fn write_scene_file_media_file_handler(
|
||||
maybe_engine_category: Some(MediaFileEngineCategory::Scene),
|
||||
maybe_animation_type: None,
|
||||
maybe_media_subtype: None,
|
||||
maybe_creator_user_token: maybe_user_token.as_ref(),
|
||||
maybe_creator_user_token: maybe_user_token,
|
||||
maybe_creator_anonymous_visitor_token: maybe_avt_token.as_ref(),
|
||||
creator_ip_address: &ip_address,
|
||||
creator_set_visibility,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod analytics;
|
||||
pub mod api_keys;
|
||||
pub mod app_state;
|
||||
pub mod beta_keys;
|
||||
pub mod billing_fakeyou;
|
||||
|
||||
+7
-9
@@ -28,8 +28,7 @@ use crate::http_server::requests::request_headers::get_routing_tag_header::get_r
|
||||
use crate::http_server::requests::request_headers::has_debug_header::has_debug_header;
|
||||
use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format;
|
||||
use crate::http_server::validations::validate_model_title::validate_model_title;
|
||||
use crate::http_server::web_utils::user_session::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended_using_connection::require_user_session_extended_using_connection;
|
||||
use crate::http_server::web_utils::user_session::require_user_session_extended::require_user_session_extended;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
@@ -82,15 +81,14 @@ pub async fn enqueue_gptsovits_model_download_handler(
|
||||
let maybe_avt_token = server_state.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let user_session = require_user_session_extended_using_connection(
|
||||
let user_session = require_user_session_extended(
|
||||
&http_request,
|
||||
&server_state.session_checker,
|
||||
&mut mysql_connection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireUserSessionError::ServerError => CommonWebError::from_error(err),
|
||||
RequireUserSessionError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
&mut *mysql_connection).await?;
|
||||
|
||||
// NB: The connection isn't needed past this point (the enqueue below uses the pool directly),
|
||||
// so release it now rather than holding a pool slot for the rest of the handler.
|
||||
drop(mysql_connection);
|
||||
|
||||
// ==================== PAID PLAN + PRIORITY ==================== //
|
||||
|
||||
|
||||
+2
-7
@@ -14,7 +14,7 @@ use mysql_queries::queries::staff_audit_logs::insert_staff_audit_log::{
|
||||
use pager::notification::notification_details_builder::NotificationDetailsBuilder;
|
||||
use pager::notification::notification_urgency::NotificationUrgency;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, UseDatabase};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// Send a test alert via the pager system. Moderators only.
|
||||
@@ -35,12 +35,7 @@ pub async fn moderation_send_alert_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<ModerationSendAlertResponse>, CommonWebError> {
|
||||
|
||||
let user_session = require_moderator(&http_request, &server_state, UseDatabase::GrabNewConnection)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Moderator check failed: {:?}", err);
|
||||
CommonWebError::NotAuthorized
|
||||
})?;
|
||||
let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
|
||||
+2
-11
@@ -15,9 +15,7 @@ use tokens::tokens::non_unique::debug_logs_event_token::DebugLogEventToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{
|
||||
require_moderator, UseDatabase,
|
||||
};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
// ── Path params ──
|
||||
@@ -74,14 +72,7 @@ pub async fn moderation_list_debug_logs_for_token_handler(
|
||||
query: web::Query<ListDebugLogsQueryParams>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<ListDebugLogsSuccessResponse>, CommonWebError> {
|
||||
let _user_session = require_moderator(
|
||||
&http_request,
|
||||
&server_state,
|
||||
UseDatabase::GrabNewConnection,
|
||||
).await.map_err(|err| {
|
||||
warn!("Moderator check failed: {:?}", err);
|
||||
CommonWebError::NotAuthorized
|
||||
})?;
|
||||
let _user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
|
||||
+2
-7
@@ -25,7 +25,7 @@ use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::web_utils::serialize_as_json_error::serialize_as_json_error;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, RequireModeratorError, UseDatabase};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// For the URL PathInfo
|
||||
@@ -60,12 +60,7 @@ pub async fn moderator_get_token_info_handler(
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<HttpResponse, CommonWebError> {
|
||||
|
||||
let user_session = require_moderator(&http_request, &server_state, UseDatabase::GrabNewConnection)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
RequireModeratorError::ServerError => CommonWebError::from_error(err),
|
||||
RequireModeratorError::NotAuthorized => CommonWebError::NotAuthorized,
|
||||
})?;
|
||||
let user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let token = path.token.trim();
|
||||
let maybe_result = get_entity_from_token(&server_state.mysql_pool, &path.token)
|
||||
|
||||
+2
-11
@@ -16,9 +16,7 @@ use tokens::tokens::prompts::PromptToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{
|
||||
require_moderator, UseDatabase,
|
||||
};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
// ── Path params ──
|
||||
@@ -92,14 +90,7 @@ pub async fn moderation_get_job_by_token_handler(
|
||||
path: web::Path<GetJobByTokenPathInfo>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<GetJobByTokenSuccessResponse>, CommonWebError> {
|
||||
let _user_session = require_moderator(
|
||||
&http_request,
|
||||
&server_state,
|
||||
UseDatabase::GrabNewConnection,
|
||||
).await.map_err(|err| {
|
||||
warn!("Moderator check failed: {:?}", err);
|
||||
CommonWebError::NotAuthorized
|
||||
})?;
|
||||
let _user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire().await?;
|
||||
|
||||
|
||||
+2
-4
@@ -13,7 +13,7 @@ use mysql_queries::queries::generic_inference::web::list_user_jobs_for_moderatio
|
||||
|
||||
use tokens::tokens::users::UserToken;
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, UseDatabase};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// List jobs for a user (moderation)
|
||||
@@ -36,9 +36,7 @@ pub async fn list_user_jobs_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<ListUserJobsResponse>, CommonWebError> {
|
||||
|
||||
let _user_session = require_moderator(&http_request, &server_state, UseDatabase::GrabNewConnection)
|
||||
.await
|
||||
.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let _user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let results = list_user_jobs_for_moderation(&path.user_token, &server_state.mysql_pool)
|
||||
.await
|
||||
|
||||
+2
-11
@@ -13,9 +13,7 @@ use tokens::tokens::staff_audit_logs::StaffAuditLogToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{
|
||||
require_moderator, UseDatabase,
|
||||
};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
const CURSOR_NAME: &str = "modstfaud";
|
||||
@@ -76,14 +74,7 @@ pub async fn moderator_list_staff_audit_logs_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<ListStaffAuditLogsSuccessResponse>, CommonWebError> {
|
||||
|
||||
let _user_session = require_moderator(
|
||||
&http_request,
|
||||
&server_state,
|
||||
UseDatabase::GrabNewConnection,
|
||||
).await.map_err(|err| {
|
||||
warn!("Moderator check failed: {:?}", err);
|
||||
CommonWebError::NotAuthorized
|
||||
})?;
|
||||
let _user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let limit = query.limit
|
||||
.unwrap_or(DEFAULT_LIMIT)
|
||||
|
||||
+2
-4
@@ -15,7 +15,7 @@ use mysql_queries::queries::users::user::list::list_subscribing_users_by_signup_
|
||||
};
|
||||
|
||||
use crate::http_server::common_responses::common_web_error::CommonWebError;
|
||||
use crate::http_server::web_utils::user_session::require_moderator::{require_moderator, UseDatabase};
|
||||
use crate::http_server::web_utils::user_session::require_moderator::require_moderator;
|
||||
use crate::state::server_state::ServerState;
|
||||
|
||||
/// List subscribing users by signup date (descending), for moderators.
|
||||
@@ -36,9 +36,7 @@ pub async fn moderator_list_subscribing_users_by_signup_date_handler(
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<Json<ModeratorListSubscribingUsersBySignupDateResponse>, CommonWebError> {
|
||||
|
||||
let _user_session = require_moderator(&http_request, &server_state, UseDatabase::GrabNewConnection)
|
||||
.await
|
||||
.map_err(|_| CommonWebError::NotAuthorized)?;
|
||||
let _user_session = require_moderator(&http_request, &server_state.session_checker, &server_state.mysql_pool).await?;
|
||||
|
||||
let result = list_subscribing_users_by_signup_date_for_moderation(
|
||||
ListSubscribingUsersBySignupDateArgs {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user