Skip to content
Merged
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
2 changes: 2 additions & 0 deletions migrations/007_raid_submissions_target_id_nullable.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE raid_submissions
ALTER COLUMN target_id DROP NOT NULL;
63 changes: 25 additions & 38 deletions src/handlers/raid_quest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
raid_leaderboard::RaidLeaderboard,
raid_quest::{CreateRaidQuest, RaidQuest, RaidQuestFilter, RaidQuestSortColumn},
raid_submission::{CreateRaidSubmission, RaidSubmissionInput, RaiderSubmissions},
x_association::XAssociation,
},
utils::x_url::{build_x_status_url, parse_x_status_url},
AppError,
Expand Down Expand Up @@ -158,16 +159,7 @@ pub async fn handle_get_active_raid_raider_submissions(
State(state): State<AppState>,
Extension(user): Extension<Address>,
) -> Result<Json<SuccessResponse<RaiderSubmissions>>, AppError> {
let Some(current_active_raid) = state.db.raid_quests.find_active().await? else {
return Err(AppError::Database(DbError::RecordNotFound(format!(
"No active raid is found"
))));
};
let Some(user_x) = state.db.x_associations.find_by_address(&user.quan_address).await? else {
return Err(AppError::Database(DbError::RecordNotFound(format!(
"User doesn't have X association"
))));
};
let (current_active_raid, user_x) = get_active_raid_and_x_association(&state, &user).await?;

let submissions = state
.db
Expand All @@ -190,31 +182,13 @@ pub async fn handle_create_raid_submission(
Extension(user): Extension<Address>,
extract::Json(payload): Json<RaidSubmissionInput>,
) -> Result<(StatusCode, Json<SuccessResponse<String>>), AppError> {
let Some((_target_username, target_id)) = parse_x_status_url(&payload.target_tweet_link) else {
return Err(AppError::Handler(HandlerError::InvalidBody(format!(
"Couldn't parse target tweet link"
))));
};
let Some(_) = state.db.relevant_tweets.find_by_id(&target_id).await? else {
return Err(AppError::Database(DbError::RecordNotFound(format!(
"Not a valid target tweet"
))));
};
let (current_active_raid, user_x) = get_active_raid_and_x_association(&state, &user).await?;

let Some((reply_username, reply_id)) = parse_x_status_url(&payload.tweet_reply_link) else {
return Err(AppError::Handler(HandlerError::InvalidBody(format!(
"Couldn't parse tweet reply link"
))));
};
let Some(current_active_raid) = state.db.raid_quests.find_active().await? else {
return Err(AppError::Database(DbError::RecordNotFound(format!(
"No active raid is found"
))));
};
let Some(user_x) = state.db.x_associations.find_by_address(&user.quan_address).await? else {
return Err(AppError::Database(DbError::RecordNotFound(format!(
"User doesn't have X association"
))));
};
if user_x.username != reply_username {
return Err(AppError::Handler(HandlerError::Auth(AuthHandlerError::Unauthorized(
format!("Only tweet reply author is eligible to submit"),
Expand All @@ -225,7 +199,6 @@ pub async fn handle_create_raid_submission(
id: reply_id,
raid_id: current_active_raid.id,
raider_id: user.quan_address.0,
target_id: target_id,
};

let created_id = state.db.raid_submissions.create(&new_raid_submission).await?;
Expand Down Expand Up @@ -256,6 +229,23 @@ pub async fn handle_delete_raid_submission(
Ok(NoContent)
}

async fn get_active_raid_and_x_association(
state: &AppState,
user: &Address,
) -> Result<(RaidQuest, XAssociation), AppError> {
let Some(current_active_raid) = state.db.raid_quests.find_active().await? else {
return Err(AppError::Database(DbError::RecordNotFound(format!(
"No active raid is found"
))));
};
let Some(user_x) = state.db.x_associations.find_by_address(&user.quan_address).await? else {
return Err(AppError::Database(DbError::RecordNotFound(format!(
"User doesn't have X association"
))));
};
Ok((current_active_raid, user_x))
}

#[cfg(test)]
mod tests {
use axum::{
Expand Down Expand Up @@ -570,10 +560,8 @@ mod tests {
.with_state(state.clone());

// 5. Payload
// Target Link -> ID 1868000000000000000
// Reply Link -> ID 999999999, Username "me"
let payload = RaidSubmissionInput {
target_tweet_link: format!("https://x.com/someone/status/{}", target_tweet_id),
tweet_reply_link: "https://x.com/me/status/999999999".to_string(),
};

Expand All @@ -596,7 +584,8 @@ mod tests {
assert!(sub.is_some());
let sub = sub.unwrap();
assert_eq!(sub.raid_id, raid_id);
assert_eq!(sub.target_id, target_tweet_id);
assert_eq!(&sub.id, "999999999");
assert!(sub.target_id.is_none());
}

#[tokio::test]
Expand All @@ -621,7 +610,6 @@ mod tests {
.with_state(state);

let payload = RaidSubmissionInput {
target_tweet_link: "https://x.com/a/status/100".into(),
tweet_reply_link: "https://x.com/b/status/200".into(),
};

Expand Down Expand Up @@ -660,8 +648,7 @@ mod tests {
.with_state(state);

let payload = RaidSubmissionInput {
target_tweet_link: "not_a_valid_url".into(),
tweet_reply_link: "https://x.com/b/status/200".into(),
tweet_reply_link: "https://x.com/b/dwdwdwt/dwdwd".into(),
};

let response = router
Expand All @@ -677,7 +664,7 @@ mod tests {
.unwrap();

// 400 Bad Request / Handler Error
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
assert_eq!(response.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
Expand Down
4 changes: 1 addition & 3 deletions src/models/raid_submission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::models::raid_quest::RaidQuest;
pub struct RaidSubmission {
pub id: String,
pub raid_id: i32,
pub target_id: String,
pub target_id: Option<String>,
pub raider_id: String,
pub impression_count: i32,
pub reply_count: i32,
Expand Down Expand Up @@ -54,13 +54,11 @@ impl<'r> FromRow<'r, PgRow> for RaidSubmission {
pub struct CreateRaidSubmission {
pub id: String,
pub raid_id: i32,
pub target_id: String,
pub raider_id: String,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct RaidSubmissionInput {
pub target_tweet_link: String,
pub tweet_reply_link: String,
}

Expand Down
14 changes: 3 additions & 11 deletions src/repositories/raid_submission.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@ impl RaidSubmissionRepository {
let created_id = sqlx::query_scalar::<_, String>(
"
INSERT INTO raid_submissions (
id, raid_id, target_id, raider_id
id, raid_id, raider_id
)
VALUES ($1, $2, $3, $4)
VALUES ($1, $2, $3)
RETURNING id
",
)
.bind(&submission.id)
.bind(submission.raid_id)
.bind(&submission.target_id)
.bind(&submission.raider_id)
.fetch_optional(&self.pool)
.await?;
Expand Down Expand Up @@ -186,7 +185,6 @@ mod tests {
struct SeedData {
raid_id: i32,
raider_id: String,
target_id: String,
}

// Helper to satisfy the strict Foreign Key chain:
Expand Down Expand Up @@ -232,18 +230,13 @@ mod tests {
.await
.expect("Failed to seed relevant tweet");

SeedData {
raid_id,
raider_id,
target_id,
}
SeedData { raid_id, raider_id }
}

fn create_mock_submission_input(seed: &SeedData) -> CreateRaidSubmission {
CreateRaidSubmission {
id: Uuid::new_v4().to_string(),
raid_id: seed.raid_id,
target_id: seed.target_id.clone(),
raider_id: seed.raider_id.clone(),
}
}
Expand Down Expand Up @@ -423,7 +416,6 @@ mod tests {
let input = CreateRaidSubmission {
id: Uuid::new_v4().to_string(),
raid_id: 9999, // Non-existent Raid
target_id: "fake_tweet".to_string(),
raider_id: "fake_user".to_string(),
};

Expand Down
Loading