Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions migrations/20260810073000_create_member_life_status.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS MemberLifeStatus (
member_id INT REFERENCES Member(member_id) ON DELETE CASCADE PRIMARY KEY,
lives INT NOT NULL DEFAULT 3,
recovery_streak INT NOT NULL DEFAULT 0,
is_probation BOOLEAN NOT NULL DEFAULT FALSE,
last_reset_month INT NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
53 changes: 51 additions & 2 deletions src/graphql/mutations/status_mutations.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
use async_graphql::{Context, Object, Result};
use async_graphql::{Context, InputObject, Object, Result};
use chrono::NaiveDate;
use sqlx::PgPool;
use std::sync::Arc;

use crate::auth::guards::{AdminGuard, AdminOrBotGuard};
use crate::models::status_update::{CreateStatusBreakInput, StatusBreakRecord, StatusUpdateRecord};
use crate::models::status_update::{
CreateStatusBreakInput, MemberLifeStatusRecord, StatusBreakRecord, StatusUpdateRecord,
};

#[derive(Default)]
pub struct StatusMutations;

#[derive(InputObject)]
struct UpdateLifeStatusInput {
member_id: i32,
lives: i32,
recovery_streak: i32,
is_probation: bool,
last_reset_month: i32,
}

#[Object]
impl StatusMutations {
#[graphql(name = "markStatusUpdate", guard = "AdminOrBotGuard")]
Expand Down Expand Up @@ -36,6 +47,44 @@ impl StatusMutations {
Ok(status)
}

#[graphql(name = "updateLifeStatus", guard = "AdminOrBotGuard")]
async fn update_life_status(
&self,
ctx: &Context<'_>,
input: UpdateLifeStatusInput,
) -> Result<MemberLifeStatusRecord> {
let pool = ctx.data::<Arc<PgPool>>().expect("Pool must be in context");

if input.lives < 0 || input.lives > 3 {
return Err("lives must be between 0 and 3".into());
}
if input.recovery_streak < 0 || input.recovery_streak > 3 {
return Err("recovery_streak must be between 0 and 3".into());
}

let record = sqlx::query_as::<_, MemberLifeStatusRecord>(
"INSERT INTO MemberLifeStatus
(member_id, lives, recovery_streak, is_probation, last_reset_month, updated_at)
VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP)
ON CONFLICT (member_id) DO UPDATE SET
lives = EXCLUDED.lives,
recovery_streak = EXCLUDED.recovery_streak,
is_probation = EXCLUDED.is_probation,
last_reset_month = EXCLUDED.last_reset_month,
updated_at = CURRENT_TIMESTAMP
RETURNING member_id, lives, recovery_streak, is_probation, last_reset_month",
)
.bind(input.member_id)
.bind(input.lives)
.bind(input.recovery_streak)
.bind(input.is_probation)
.bind(input.last_reset_month)
.fetch_one(pool.as_ref())
.await?;

Ok(record)
}

#[graphql(name = "createStatusBreak", guard = "AdminGuard")]
async fn create_status_break(
&self,
Expand Down
13 changes: 13 additions & 0 deletions src/graphql/queries/member_queries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use chrono::NaiveDate;
use sqlx::PgPool;
use std::sync::Arc;

use crate::models::status_update::MemberLifeStatusRecord;
use crate::models::{member::Member, status_update::StatusUpdateStreakRecord};

#[derive(Default)]
Expand Down Expand Up @@ -262,6 +263,18 @@ impl StatusInfo {

Ok(result)
}

async fn life_status(&self, ctx: &Context<'_>) -> Result<Option<MemberLifeStatusRecord>> {
let pool = ctx.data::<Arc<PgPool>>()?;
let status = sqlx::query_as::<_, MemberLifeStatusRecord>(
"SELECT member_id, lives, recovery_streak, is_probation, last_reset_month
FROM MemberLifeStatus WHERE member_id = $1",
)
.bind(self.member_id)
.fetch_optional(pool.as_ref())
.await?;
Ok(status)
}
}

#[ComplexObject]
Expand Down
9 changes: 9 additions & 0 deletions src/models/status_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,12 @@ pub struct CreateStatusBreakInput {
pub member_id: Option<i32>,
pub reason: Option<String>,
}

#[derive(SimpleObject, FromRow, Debug, Clone, PartialEq, Eq)]
pub struct MemberLifeStatusRecord {
pub member_id: i32,
pub lives: i32,
pub recovery_streak: i32,
pub is_probation: bool,
pub last_reset_month: i32,
}
Loading