diff --git a/apps/labrinth/.sqlx/query-066e14c739f3467fbeef939879074a4816912b069894c4b06972300f19ebd191.json b/apps/labrinth/.sqlx/query-066e14c739f3467fbeef939879074a4816912b069894c4b06972300f19ebd191.json deleted file mode 100644 index e2c91004d8..0000000000 --- a/apps/labrinth/.sqlx/query-066e14c739f3467fbeef939879074a4816912b069894c4b06972300f19ebd191.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n\t\t\tSELECT\n\t\t\t\tpayout_periods.period,\n\t\t\t\tpayout_periods.raw_actual_aditude_revenue_usd,\n\t\t\t\tpayout_periods.adjustments,\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'scheduled'\n\t\t\t\t) AS \"has_scheduled_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'running'\n\t\t\t\t) AS \"has_running_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'succeeded'\n\t\t\t\t) AS \"has_succeeded_run!\"\n\t\t\tFROM payout_periods\n\t\t\tWHERE payout_periods.period = ANY($1)\n\t\t\t", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "period", - "type_info": "Date" - }, - { - "ordinal": 1, - "name": "raw_actual_aditude_revenue_usd", - "type_info": "Numeric" - }, - { - "ordinal": 2, - "name": "adjustments", - "type_info": "Jsonb" - }, - { - "ordinal": 3, - "name": "has_scheduled_run!", - "type_info": "Bool" - }, - { - "ordinal": 4, - "name": "has_running_run!", - "type_info": "Bool" - }, - { - "ordinal": 5, - "name": "has_succeeded_run!", - "type_info": "Bool" - } - ], - "parameters": { - "Left": [ - "DateArray" - ] - }, - "nullable": [ - false, - false, - false, - null, - null, - null - ] - }, - "hash": "066e14c739f3467fbeef939879074a4816912b069894c4b06972300f19ebd191" -} diff --git a/apps/labrinth/.sqlx/query-1d68f3585ac26a38955abf04703e26538972770ea7454fab132da87dad747669.json b/apps/labrinth/.sqlx/query-1d68f3585ac26a38955abf04703e26538972770ea7454fab132da87dad747669.json new file mode 100644 index 0000000000..e38f03e144 --- /dev/null +++ b/apps/labrinth/.sqlx/query-1d68f3585ac26a38955abf04703e26538972770ea7454fab132da87dad747669.json @@ -0,0 +1,58 @@ +{ + "db_name": "PostgreSQL", + "query": "\n\t\t\tSELECT\n\t\t\t\tpayout_periods.period,\n\t\t\t\tpayout_periods.raw_actual_aditude_revenue_usd,\n\t\t\t\tpayout_periods.adjustments AS \"adjustments: Json>\",\n\t\t\t\t(\n\t\t\t\t\tSELECT payout_runs.payload\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status IN ('scheduled', 'running')\n\t\t\t\t\tLIMIT 1\n\t\t\t\t) AS \"active_run_payload: Json\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'scheduled'\n\t\t\t\t) AS \"has_scheduled_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'running'\n\t\t\t\t) AS \"has_running_run!\",\n\t\t\t\tEXISTS (\n\t\t\t\t\tSELECT 1\n\t\t\t\t\tFROM payout_runs\n\t\t\t\t\tWHERE payout_runs.period = payout_periods.period\n\t\t\t\t\t\tAND payout_runs.status = 'succeeded'\n\t\t\t\t) AS \"has_succeeded_run!\"\n\t\t\tFROM payout_periods\n\t\t\tWHERE payout_periods.period = ANY($1)\n\t\t\t", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "period", + "type_info": "Date" + }, + { + "ordinal": 1, + "name": "raw_actual_aditude_revenue_usd", + "type_info": "Numeric" + }, + { + "ordinal": 2, + "name": "adjustments: Json>", + "type_info": "Jsonb" + }, + { + "ordinal": 3, + "name": "active_run_payload: Json", + "type_info": "Jsonb" + }, + { + "ordinal": 4, + "name": "has_scheduled_run!", + "type_info": "Bool" + }, + { + "ordinal": 5, + "name": "has_running_run!", + "type_info": "Bool" + }, + { + "ordinal": 6, + "name": "has_succeeded_run!", + "type_info": "Bool" + } + ], + "parameters": { + "Left": [ + "DateArray" + ] + }, + "nullable": [ + false, + false, + false, + null, + null, + null, + null + ] + }, + "hash": "1d68f3585ac26a38955abf04703e26538972770ea7454fab132da87dad747669" +} diff --git a/apps/labrinth/.sqlx/query-3de025bb99952ac0ba5d1bbf270a42b68c675c0f38cb760c42acd1786a20c241.json b/apps/labrinth/.sqlx/query-3de025bb99952ac0ba5d1bbf270a42b68c675c0f38cb760c42acd1786a20c241.json deleted file mode 100644 index 33b482037e..0000000000 --- a/apps/labrinth/.sqlx/query-3de025bb99952ac0ba5d1bbf270a42b68c675c0f38cb760c42acd1786a20c241.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n\t\t\tINSERT INTO payout_runs (\n\t\t\t\tid,\n\t\t\t\tperiod,\n\t\t\t\tstatus,\n\t\t\t\tstarted_at,\n\t\t\t\tstarted_by,\n\t\t\t\texecute_at,\n\t\t\t\tprocessing_started_at,\n\t\t\t\tfinished_at,\n\t\t\t\tcancelled_at,\n\t\t\t\tcancelled_by,\n\t\t\t\terror\n\t\t\t)\n\t\t\tVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)\n\t\t\tON CONFLICT (id) DO UPDATE SET\n\t\t\t\tperiod = EXCLUDED.period,\n\t\t\t\tstatus = EXCLUDED.status,\n\t\t\t\tstarted_at = EXCLUDED.started_at,\n\t\t\t\tstarted_by = EXCLUDED.started_by,\n\t\t\t\texecute_at = EXCLUDED.execute_at,\n\t\t\t\tprocessing_started_at = EXCLUDED.processing_started_at,\n\t\t\t\tfinished_at = EXCLUDED.finished_at,\n\t\t\t\tcancelled_at = EXCLUDED.cancelled_at,\n\t\t\t\tcancelled_by = EXCLUDED.cancelled_by,\n\t\t\t\terror = EXCLUDED.error\n\t\t\t", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Int8", - "Date", - "Text", - "Timestamptz", - "Int8", - "Timestamptz", - "Timestamptz", - "Timestamptz", - "Timestamptz", - "Int8", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "3de025bb99952ac0ba5d1bbf270a42b68c675c0f38cb760c42acd1786a20c241" -} diff --git a/apps/labrinth/.sqlx/query-4798f2780f28c4329d96af43e4902d97972573a8f4954ceab284b8d8989641a7.json b/apps/labrinth/.sqlx/query-4798f2780f28c4329d96af43e4902d97972573a8f4954ceab284b8d8989641a7.json new file mode 100644 index 0000000000..32408bbe65 --- /dev/null +++ b/apps/labrinth/.sqlx/query-4798f2780f28c4329d96af43e4902d97972573a8f4954ceab284b8d8989641a7.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO payout_periods (\n period,\n raw_actual_aditude_revenue_usd,\n adjustments\n )\n VALUES ($1, $2, '[]'::jsonb)\n ON CONFLICT (period) DO UPDATE SET\n raw_actual_aditude_revenue_usd =\n EXCLUDED.raw_actual_aditude_revenue_usd\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Date", + "Numeric" + ] + }, + "nullable": [] + }, + "hash": "4798f2780f28c4329d96af43e4902d97972573a8f4954ceab284b8d8989641a7" +} diff --git a/apps/labrinth/.sqlx/query-5236bdf4ab31a7a49ae274e965b7310b757083d0c7870a3e5ae11ab5eb57d1aa.json b/apps/labrinth/.sqlx/query-5236bdf4ab31a7a49ae274e965b7310b757083d0c7870a3e5ae11ab5eb57d1aa.json new file mode 100644 index 0000000000..f7089c9353 --- /dev/null +++ b/apps/labrinth/.sqlx/query-5236bdf4ab31a7a49ae274e965b7310b757083d0c7870a3e5ae11ab5eb57d1aa.json @@ -0,0 +1,25 @@ +{ + "db_name": "PostgreSQL", + "query": "\n\t\t\tINSERT INTO payout_runs (\n\t\t\t\tid,\n\t\t\t\tperiod,\n\t\t\t\tpayload,\n\t\t\t\tstatus,\n\t\t\t\tstarted_at,\n\t\t\t\tstarted_by,\n\t\t\t\texecute_at,\n\t\t\t\tprocessing_started_at,\n\t\t\t\tfinished_at,\n\t\t\t\tcancelled_at,\n\t\t\t\tcancelled_by,\n\t\t\t\terror\n\t\t\t)\n\t\t\tVALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\n\t\t\tON CONFLICT (id) DO UPDATE SET\n\t\t\t\tperiod = EXCLUDED.period,\n\t\t\t\tpayload = EXCLUDED.payload,\n\t\t\t\tstatus = EXCLUDED.status,\n\t\t\t\tstarted_at = EXCLUDED.started_at,\n\t\t\t\tstarted_by = EXCLUDED.started_by,\n\t\t\t\texecute_at = EXCLUDED.execute_at,\n\t\t\t\tprocessing_started_at = EXCLUDED.processing_started_at,\n\t\t\t\tfinished_at = EXCLUDED.finished_at,\n\t\t\t\tcancelled_at = EXCLUDED.cancelled_at,\n\t\t\t\tcancelled_by = EXCLUDED.cancelled_by,\n\t\t\t\terror = EXCLUDED.error\n\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Int8", + "Date", + "Jsonb", + "Text", + "Timestamptz", + "Int8", + "Timestamptz", + "Timestamptz", + "Timestamptz", + "Timestamptz", + "Int8", + "Jsonb" + ] + }, + "nullable": [] + }, + "hash": "5236bdf4ab31a7a49ae274e965b7310b757083d0c7870a3e5ae11ab5eb57d1aa" +} diff --git a/apps/labrinth/.sqlx/query-df7d47467f7344cb4538ffddaf3805bc9666c9651152f6ed648434b6e2b7ee61.json b/apps/labrinth/.sqlx/query-df7d47467f7344cb4538ffddaf3805bc9666c9651152f6ed648434b6e2b7ee61.json deleted file mode 100644 index b9d887984e..0000000000 --- a/apps/labrinth/.sqlx/query-df7d47467f7344cb4538ffddaf3805bc9666c9651152f6ed648434b6e2b7ee61.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO payout_periods (\n period,\n raw_actual_aditude_revenue_usd,\n adjustments\n )\n VALUES ($1, $2, $3)\n ON CONFLICT (period) DO UPDATE SET\n raw_actual_aditude_revenue_usd =\n EXCLUDED.raw_actual_aditude_revenue_usd,\n adjustments = EXCLUDED.adjustments\n ", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Date", - "Numeric", - "Jsonb" - ] - }, - "nullable": [] - }, - "hash": "df7d47467f7344cb4538ffddaf3805bc9666c9651152f6ed648434b6e2b7ee61" -} diff --git a/apps/labrinth/migrations/20260817120000_payout-period-revenue.sql b/apps/labrinth/migrations/20260817120000_payout-period-revenue.sql index aa84557d32..c4e43e5746 100644 --- a/apps/labrinth/migrations/20260817120000_payout-period-revenue.sql +++ b/apps/labrinth/migrations/20260817120000_payout-period-revenue.sql @@ -20,6 +20,7 @@ CREATE TABLE payout_period_days ( CREATE TABLE payout_runs ( id BIGINT PRIMARY KEY, period DATE NOT NULL REFERENCES payout_periods(period), + payload JSONB NOT NULL, status TEXT NOT NULL, started_at TIMESTAMPTZ NOT NULL, started_by BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, diff --git a/apps/labrinth/src/database/models/payout_period_item.rs b/apps/labrinth/src/database/models/payout_period_item.rs index 0bede7376d..9537d495ef 100644 --- a/apps/labrinth/src/database/models/payout_period_item.rs +++ b/apps/labrinth/src/database/models/payout_period_item.rs @@ -2,14 +2,17 @@ use std::collections::HashMap; use chrono::NaiveDate; use rust_decimal::Decimal; +use sqlx::types::Json; use super::DatabaseError; +use crate::queue::payout_run::{Adjustment, PayoutRunPayload}; #[derive(Debug, Clone)] pub struct DBPayoutPeriod { pub period: NaiveDate, pub raw_actual_aditude_revenue_usd: Decimal, - pub adjustments: serde_json::Value, + pub adjustments: Vec, + pub active_run_payload: Option, pub days: Vec, pub has_scheduled_run: bool, pub has_running_run: bool, @@ -37,7 +40,14 @@ impl DBPayoutPeriod { SELECT payout_periods.period, payout_periods.raw_actual_aditude_revenue_usd, - payout_periods.adjustments, + payout_periods.adjustments AS "adjustments: Json>", + ( + SELECT payout_runs.payload + FROM payout_runs + WHERE payout_runs.period = payout_periods.period + AND payout_runs.status IN ('scheduled', 'running') + LIMIT 1 + ) AS "active_run_payload: Json", EXISTS ( SELECT 1 FROM payout_runs @@ -73,7 +83,10 @@ impl DBPayoutPeriod { period: row.period, raw_actual_aditude_revenue_usd: row .raw_actual_aditude_revenue_usd, - adjustments: row.adjustments, + adjustments: row.adjustments.0, + active_run_payload: row + .active_run_payload + .map(|payload| payload.0), days: Vec::new(), has_scheduled_run: row.has_scheduled_run, has_running_run: row.has_running_run, diff --git a/apps/labrinth/src/database/models/payout_run_item.rs b/apps/labrinth/src/database/models/payout_run_item.rs index d66bd520cd..d455ee0d31 100644 --- a/apps/labrinth/src/database/models/payout_run_item.rs +++ b/apps/labrinth/src/database/models/payout_run_item.rs @@ -4,6 +4,7 @@ use sqlx::types::Json; use strum::{EnumString, IntoStaticStr}; use super::{DBPayoutRunId, DBUserId, DatabaseError}; +use crate::queue::payout_run::PayoutRunPayload; #[derive( Debug, @@ -48,6 +49,7 @@ impl From> for PayoutRunError { pub struct DBPayoutRun { pub id: DBPayoutRunId, pub period: NaiveDate, + pub payload: PayoutRunPayload, pub status: PayoutRunStatus, pub started_at: DateTime, pub started_by: DBUserId, @@ -69,6 +71,7 @@ impl DBPayoutRun { INSERT INTO payout_runs ( id, period, + payload, status, started_at, started_by, @@ -79,9 +82,10 @@ impl DBPayoutRun { cancelled_by, error ) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ON CONFLICT (id) DO UPDATE SET period = EXCLUDED.period, + payload = EXCLUDED.payload, status = EXCLUDED.status, started_at = EXCLUDED.started_at, started_by = EXCLUDED.started_by, @@ -94,6 +98,7 @@ impl DBPayoutRun { "#, self.id.0, self.period, + Json(&self.payload) as Json<&PayoutRunPayload>, <&'static str>::from(self.status), self.started_at, self.started_by.0, diff --git a/apps/labrinth/src/queue/payout_run/mod.rs b/apps/labrinth/src/queue/payout_run/mod.rs index ad304ad3e2..931583fbdd 100644 --- a/apps/labrinth/src/queue/payout_run/mod.rs +++ b/apps/labrinth/src/queue/payout_run/mod.rs @@ -71,13 +71,36 @@ mod estimate; pub use estimate::*; +/// Inputs supplied by an admin when scheduling a payout run. +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] +pub struct PayoutRunPayload { + /// Actual raw revenue received from the ad provider for the period. + #[serde(with = "rust_decimal::serde::float")] + pub raw_actual_revenue_usd: Decimal, + /// Manual adjustments to apply on top of actual revenue. + pub adjustments: Vec, +} + +/// Manual admin-input adjustment to a payout period. +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] +pub struct Adjustment { + /// Total value of the adjustment. + #[serde(with = "rust_decimal::serde::float")] + pub amount_usd: Decimal, + /// Why this adjustment was applied. + /// + /// Only visible to admins. + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, +} + /// Fraction defining much of the net revenue goes to the platform. const PLATFORM_REVENUE_SPLIT: Decimal = dec!(0.25); /// How input revenue is distributed for a specific day. /// /// This may refer to either estimated or actual revenue. -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct DayDistribution { /// Amount of revenue input into the algorithm. #[serde(with = "rust_decimal::serde::float")] diff --git a/apps/labrinth/src/routes/internal/mod.rs b/apps/labrinth/src/routes/internal/mod.rs index 89b60974e4..e8c207d67c 100644 --- a/apps/labrinth/src/routes/internal/mod.rs +++ b/apps/labrinth/src/routes/internal/mod.rs @@ -185,6 +185,9 @@ pub fn config(cfg: &mut web::ServiceConfig) { analytics_event::analytics_event_create, analytics_event::analytics_event_edit, analytics_event::analytics_event_delete, + payout_runs::get_runs, + payout_runs::start_run, + payout_runs::cancel_runs, ), modifiers(&InternalPathModifier, &SecurityAddon) )] diff --git a/apps/labrinth/src/routes/internal/payout_runs/admin.rs b/apps/labrinth/src/routes/internal/payout_runs/admin.rs index f8d246ecc6..9a4f68485b 100644 --- a/apps/labrinth/src/routes/internal/payout_runs/admin.rs +++ b/apps/labrinth/src/routes/internal/payout_runs/admin.rs @@ -4,7 +4,7 @@ use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use xredis::RedisPool; -use super::Adjustment; +use super::{Adjustment, PayoutRunPayload}; use crate::{ auth::{ AuthenticationError, get_user_from_headers, two_factor::verify_2fa_code, @@ -25,7 +25,7 @@ use crate::{ }, }; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct StartPayoutRun { pub period: YearMonth, pub two_factor_code: Option, @@ -40,12 +40,26 @@ pub struct StartPayoutRun { pub adjustments: Vec, } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] pub struct StartPayoutRunResponse { pub id: PayoutRunId, pub execute_at: DateTime, } +/// Start a payout run. +/// +/// Admin-only. +#[utoipa::path( + tag = "payout runs", + request_body = StartPayoutRun, + responses( + (status = OK, body = StartPayoutRunResponse), + (status = BAD_REQUEST, description = "Invalid payout run input"), + (status = UNAUTHORIZED, description = "Invalid authentication or TOTP code"), + (status = CONFLICT, description = "Payout period is unavailable or another run is active"), + ), + security(("bearer_auth" = ["SESSION_ACCESS"])), +)] #[post("/start")] pub async fn start_run( req: HttpRequest, @@ -126,8 +140,10 @@ pub async fn start_run( let estimate = estimates .pop() .wrap_internal_err("missing requested payout estimate")?; - let adjustments = serde_json::to_value(&body.adjustments) - .wrap_internal_err("serializing payout adjustments")?; + let payload = PayoutRunPayload { + raw_actual_revenue_usd: body.raw_actual_revenue_usd, + adjustments: body.adjustments.clone(), + }; let mut transaction = pool .begin() @@ -141,15 +157,13 @@ pub async fn start_run( raw_actual_aditude_revenue_usd, adjustments ) - VALUES ($1, $2, $3) + VALUES ($1, $2, '[]'::jsonb) ON CONFLICT (period) DO UPDATE SET raw_actual_aditude_revenue_usd = - EXCLUDED.raw_actual_aditude_revenue_usd, - adjustments = EXCLUDED.adjustments + EXCLUDED.raw_actual_aditude_revenue_usd "#, body.period.date(), body.raw_actual_revenue_usd, - &adjustments, ) .execute(&mut transaction) .await @@ -175,9 +189,14 @@ pub async fn start_run( .fetch_one(&mut transaction) .await .wrap_internal_err("checking payout period run status")?; - if run_state.has_active_run || run_state.has_succeeded_run { + if run_state.has_active_run { return Err(ApiError::Conflict(eyre::eyre!( - "payout period already has an active or succeeded run", + "another payout run is already scheduled or running", + ))); + } + if run_state.has_succeeded_run { + return Err(ApiError::Conflict(eyre::eyre!( + "payout period already has a succeeded run", ))); } @@ -231,6 +250,7 @@ pub async fn start_run( let run = DBPayoutRun { id, period: body.period.date(), + payload, status: PayoutRunStatus::Scheduled, started_at: timing.started_at, started_by: user_id, @@ -256,11 +276,22 @@ pub async fn start_run( })) } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, utoipa::ToSchema)] pub struct CancelPayoutRunsResponse { pub cancelled: u64, } +/// Cancel all scheduled payout runs. +/// +/// Admin-only. +#[utoipa::path( + tag = "payout runs", + responses( + (status = OK, body = CancelPayoutRunsResponse), + (status = UNAUTHORIZED, description = "Invalid authentication"), + ), + security(("bearer_auth" = ["SESSION_ACCESS"])), +)] #[post("/cancel")] pub async fn cancel_runs( req: HttpRequest, diff --git a/apps/labrinth/src/routes/internal/payout_runs/fetch.rs b/apps/labrinth/src/routes/internal/payout_runs/fetch.rs index 1221f3de58..a3b15d27f3 100644 --- a/apps/labrinth/src/routes/internal/payout_runs/fetch.rs +++ b/apps/labrinth/src/routes/internal/payout_runs/fetch.rs @@ -30,12 +30,12 @@ use crate::{ }, }; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] pub struct PayoutRuns { pub periods: Vec, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] pub struct PayoutRunPeriod { pub period: YearMonth, pub status: PayoutPeriodStatus, @@ -44,7 +44,9 @@ pub struct PayoutRunPeriod { } /// Has revenue been distributed for a specific payout period month yet? -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[derive( + Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, utoipa::ToSchema, +)] #[serde(rename_all = "snake_case")] pub enum PayoutPeriodStatus { /// We are still waiting on the NET 60 cycle to complete for this month; @@ -61,13 +63,18 @@ pub enum PayoutPeriodStatus { Paid, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] pub struct PayoutRunDay { pub date: NaiveDate, pub estimated: DayDistribution, pub actual: Option, } +/// Fetch all payout runs. +#[utoipa::path( + tag = "payout runs", + responses((status = OK, body = PayoutRuns)), +)] #[get("")] pub async fn get_runs( req: HttpRequest, @@ -150,10 +157,11 @@ pub async fn get_runs( .map(|estimate| -> Result<_, ApiError> { if let Some(period) = stored_periods.get(&estimate.period.date()) { let mut adjustments = - serde_json::from_value::>( - period.adjustments.clone(), - ) - .wrap_internal_err("deserializing payout adjustments")?; + if let Some(payload) = &period.active_run_payload { + payload.adjustments.clone() + } else { + period.adjustments.clone() + }; if !show_adjustment_descriptions { for adjustment in &mut adjustments { adjustment.description = None; diff --git a/apps/labrinth/src/routes/internal/payout_runs/mod.rs b/apps/labrinth/src/routes/internal/payout_runs/mod.rs index f1584c8022..a35bf9da73 100644 --- a/apps/labrinth/src/routes/internal/payout_runs/mod.rs +++ b/apps/labrinth/src/routes/internal/payout_runs/mod.rs @@ -1,25 +1,12 @@ -use actix_web::web; -use rust_decimal::Decimal; -use serde::{Deserialize, Serialize}; - mod admin; mod fetch; -pub fn config(cfg: &mut web::ServiceConfig) { - cfg.service(fetch::get_runs) - .service(admin::start_run) - .service(admin::cancel_runs); -} +pub use crate::queue::payout_run::{Adjustment, PayoutRunPayload}; +pub use admin::*; +pub use fetch::*; -/// Manual admin-input adjustment to a payout period. -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct Adjustment { - /// Total value of the adjustment. - #[serde(with = "rust_decimal::serde::float")] - pub amount_usd: Decimal, - /// Why this adjustment was applied. - /// - /// Only visible to admins. - #[serde(skip_serializing_if = "Option::is_none")] - pub description: Option, +pub fn config(cfg: &mut actix_web::web::ServiceConfig) { + cfg.service(get_runs) + .service(start_run) + .service(cancel_runs); }