merge migrations

This commit is contained in:
aecsocket
2026-08-25 16:17:45 +01:00
parent 7a383b98b9
commit 87aa698148
8 changed files with 367 additions and 0 deletions
@@ -0,0 +1,17 @@
{
"db_name": "PostgreSQL",
"query": "\n\t\tINSERT INTO payout_runs (\n\t\t\tid,\n\t\t\tperiod,\n\t\t\tpayload,\n\t\t\tstatus,\n\t\t\tstarted_at,\n\t\t\tstarted_by,\n\t\t\texecute_at\n\t\t)\n\t\tVALUES (\n\t\t\t$1,\n\t\t\t$2,\n\t\t\t'{\"raw_actual_revenue_usd\": 0, \"adjustments\": []}'::jsonb,\n\t\t\t$3,\n\t\t\tNOW(),\n\t\t\t$4,\n\t\t\tNOW()\n\t\t)\n\t\t",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Date",
"Text",
"Int8"
]
},
"nullable": []
},
"hash": "0f4cefdd003516ad3260cd7b471fef35cf224e053933f4aec75f377fbdffbbcb"
}
@@ -0,0 +1,23 @@
{
"db_name": "PostgreSQL",
"query": "\n\t\t\t\tSELECT payout_run_id\n\t\t\t\tFROM payouts_values\n\t\t\t\tWHERE\n\t\t\t\t\tuser_id = $1\n\t\t\t\t\tAND (mod_id IS NULL OR mod_id = $2)\n\t\t\t\tORDER BY payout_run_id NULLS FIRST\n\t\t\t\t",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "payout_run_id",
"type_info": "Int8"
}
],
"parameters": {
"Left": [
"Int8",
"Int8"
]
},
"nullable": [
true
]
},
"hash": "2296d6f4147eaf7e2f2e101aafcd6c4b3559cd68cee070570dc7706805eeedca"
}
@@ -0,0 +1,20 @@
{
"db_name": "PostgreSQL",
"query": "\n\t\t\t\tSELECT period\n\t\t\t\tFROM payout_runs\n\t\t\t\tWHERE status = 'succeeded'\n\t\t\t\tORDER BY period\n\t\t\t\t",
"describe": {
"columns": [
{
"ordinal": 0,
"name": "period",
"type_info": "Date"
}
],
"parameters": {
"Left": []
},
"nullable": [
false
]
},
"hash": "3e27668db2331820d032e02b61f711e2caa2691460c6bb1c138ef89aa9c74561"
}
@@ -0,0 +1,14 @@
{
"db_name": "PostgreSQL",
"query": "\n\t\t\t\tINSERT INTO payout_periods (\n\t\t\t\t\tperiod,\n\t\t\t\t\traw_actual_aditude_revenue_usd,\n\t\t\t\t\tadjustments\n\t\t\t\t)\n\t\t\t\tSELECT period, 0, '[]'::jsonb\n\t\t\t\tFROM UNNEST($1::date[]) AS period\n\t\t\t\t",
"describe": {
"columns": [],
"parameters": {
"Left": [
"DateArray"
]
},
"nullable": []
},
"hash": "8d6cbbb4963ff2d0cc36c2c91e147fda4c6a6d69ec96bc4e531dc695e4e92f62"
}
@@ -0,0 +1,15 @@
{
"db_name": "PostgreSQL",
"query": "UPDATE payout_runs SET status = $1 WHERE id = $2",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Text",
"Int8"
]
},
"nullable": []
},
"hash": "96a4279eff106656a6b7b7b7e17bd52473573907006b78066dbd4a7b0dab8b73"
}
@@ -0,0 +1,16 @@
{
"db_name": "PostgreSQL",
"query": "\n\t\tINSERT INTO payouts_values (\n\t\t\tuser_id,\n\t\t\tmod_id,\n\t\t\tamount,\n\t\t\tcreated,\n\t\t\tdate_available,\n\t\t\tpayout_run_id\n\t\t)\n\t\tVALUES ($1, $2, 1, NOW(), NOW(), $3)\n\t\t",
"describe": {
"columns": [],
"parameters": {
"Left": [
"Int8",
"Int8",
"Int8"
]
},
"nullable": []
},
"hash": "cdb198ab95f7fb5a4ae685125a7a5ae391d7be351a6c4328c2a9435f53f22772"
}
@@ -19,3 +19,20 @@ CREATE INDEX payout_estimates_mod_id
CREATE INDEX payout_estimates_created
ON payout_estimates (created);
ALTER TABLE payouts_values
ADD COLUMN payout_run_id BIGINT REFERENCES payout_runs(id);
ALTER TABLE payouts_values
ADD CONSTRAINT payouts_values_payout_run_creator_only
CHECK (payout_run_id IS NULL OR mod_id IS NOT NULL);
CREATE UNIQUE INDEX payouts_values_payout_run_distribution
ON payouts_values (payout_run_id, user_id, mod_id)
WHERE payout_run_id IS NOT NULL;
DROP INDEX payout_runs_active_period;
CREATE UNIQUE INDEX payout_runs_single_active
ON payout_runs ((TRUE))
WHERE status IN ('scheduled', 'running');
+245
View File
@@ -0,0 +1,245 @@
use ariadne::ids::base62_impl::parse_base62;
use chrono::NaiveDate;
use sqlx::postgres::PgQueryResult;
use common::{
api_v3::ApiV3,
database::{FRIEND_USER_ID_PARSED, USER_USER_ID_PARSED},
environment::{TestEnvironment, with_test_environment},
};
use labrinth::database::PgPool;
pub mod common;
async fn insert_run(
pool: &PgPool,
id: i64,
period: NaiveDate,
status: &str,
) -> Result<PgQueryResult, sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO payout_runs (
id,
period,
payload,
status,
started_at,
started_by,
execute_at
)
VALUES (
$1,
$2,
'{"raw_actual_revenue_usd": 0, "adjustments": []}'::jsonb,
$3,
NOW(),
$4,
NOW()
)
"#,
id,
period,
status,
USER_USER_ID_PARSED,
)
.execute(pool)
.await
}
async fn set_run_status(pool: &PgPool, id: i64, status: &str) {
sqlx::query!(
"UPDATE payout_runs SET status = $1 WHERE id = $2",
status,
id,
)
.execute(pool)
.await
.unwrap();
}
async fn insert_payout_value(
pool: &PgPool,
payout_run_id: Option<i64>,
user_id: i64,
mod_id: Option<i64>,
) -> Result<PgQueryResult, sqlx::Error> {
sqlx::query!(
r#"
INSERT INTO payouts_values (
user_id,
mod_id,
amount,
created,
date_available,
payout_run_id
)
VALUES ($1, $2, 1, NOW(), NOW(), $3)
"#,
user_id,
mod_id,
payout_run_id,
)
.execute(pool)
.await
}
fn assert_constraint(error: sqlx::Error, expected: &str) {
assert_eq!(
error
.as_database_error()
.and_then(|error| error.constraint()),
Some(expected),
);
}
#[actix_rt::test]
async fn payout_run_lineage_and_active_run_constraints() {
with_test_environment(
None,
|test_env: TestEnvironment<ApiV3>| async move {
let pool = &test_env.db.pool;
let alpha_project_id =
parse_base62(&test_env.dummy.project_alpha.project_id).unwrap()
as i64;
let beta_project_id =
parse_base62(&test_env.dummy.project_beta.project_id).unwrap()
as i64;
let periods = [
NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
NaiveDate::from_ymd_opt(2000, 2, 1).unwrap(),
NaiveDate::from_ymd_opt(2000, 3, 1).unwrap(),
NaiveDate::from_ymd_opt(2000, 4, 1).unwrap(),
NaiveDate::from_ymd_opt(2000, 5, 1).unwrap(),
];
sqlx::query!(
r#"
INSERT INTO payout_periods (
period,
raw_actual_aditude_revenue_usd,
adjustments
)
SELECT period, 0, '[]'::jsonb
FROM UNNEST($1::date[]) AS period
"#,
&periods[..],
)
.execute(pool)
.await
.unwrap();
insert_run(pool, 1001, periods[0], "succeeded")
.await
.unwrap();
insert_payout_value(pool, None, USER_USER_ID_PARSED, None)
.await
.unwrap();
insert_payout_value(
pool,
Some(1001),
USER_USER_ID_PARSED,
Some(alpha_project_id),
)
.await
.unwrap();
insert_payout_value(
pool,
Some(1001),
FRIEND_USER_ID_PARSED,
Some(alpha_project_id),
)
.await
.unwrap();
insert_payout_value(
pool,
Some(1001),
USER_USER_ID_PARSED,
Some(beta_project_id),
)
.await
.unwrap();
let payout_run_ids = sqlx::query_scalar!(
r#"
SELECT payout_run_id
FROM payouts_values
WHERE
user_id = $1
AND (mod_id IS NULL OR mod_id = $2)
ORDER BY payout_run_id NULLS FIRST
"#,
USER_USER_ID_PARSED,
alpha_project_id,
)
.fetch_all(pool)
.await
.unwrap();
assert_eq!(payout_run_ids, vec![None, Some(1001)]);
let duplicate = insert_payout_value(
pool,
Some(1001),
USER_USER_ID_PARSED,
Some(alpha_project_id),
)
.await
.unwrap_err();
assert_constraint(
duplicate,
"payouts_values_payout_run_distribution",
);
let projectless = insert_payout_value(
pool,
Some(1001),
USER_USER_ID_PARSED,
None,
)
.await
.unwrap_err();
assert_constraint(
projectless,
"payouts_values_payout_run_creator_only",
);
insert_run(pool, 1002, periods[1], "scheduled")
.await
.unwrap();
let second_active = insert_run(pool, 1003, periods[2], "running")
.await
.unwrap_err();
assert_constraint(second_active, "payout_runs_single_active");
set_run_status(pool, 1002, "cancelled").await;
insert_run(pool, 1003, periods[2], "scheduled")
.await
.unwrap();
set_run_status(pool, 1003, "failed").await;
insert_run(pool, 1004, periods[3], "scheduled")
.await
.unwrap();
set_run_status(pool, 1004, "succeeded").await;
insert_run(pool, 1005, periods[4], "scheduled")
.await
.unwrap();
let succeeded_periods = sqlx::query_scalar!(
r#"
SELECT period
FROM payout_runs
WHERE status = 'succeeded'
ORDER BY period
"#,
)
.fetch_all(pool)
.await
.unwrap();
assert_eq!(succeeded_periods, vec![periods[0], periods[3]]);
},
)
.await;
}